From 918b79e875d80451b53a0f215da05e9e7223f177 Mon Sep 17 00:00:00 2001 From: Erik Martin-Dorel Date: Wed, 28 Dec 2022 19:43:19 +0100 Subject: [PATCH] fix(web-app): Add Reload button that replaces Mechanism-2 of PR #372 This button triggers a "Fetch_save" and makes a menu appear, allowing end users to reuse their latest graded code, their latest saved code, or the initial template code. This button only shows up when a non-static backend is detected. Update the French translation as well. This fixes "bug 3" of issue #505. Close #493 Close #505 --- src/app/learnocaml_common.ml | 110 ++++++++- src/app/learnocaml_common.mli | 14 +- src/app/learnocaml_exercise_main.ml | 10 +- src/app/learnocaml_index_main.ml | 6 +- src/app/learnocaml_local_storage.ml | 10 +- src/app/learnocaml_local_storage.mli | 13 +- src/state/learnocaml_data.mli | 5 +- static/css/learnocaml_exercise.css | 27 +++ translations/fr.po | 342 ++++++++++++++------------- 9 files changed, 364 insertions(+), 173 deletions(-) diff --git a/src/app/learnocaml_common.ml b/src/app/learnocaml_common.ml index 787753f56..fb6fce4c2 100644 --- a/src/app/learnocaml_common.ml +++ b/src/app/learnocaml_common.ml @@ -283,13 +283,13 @@ let disable_with_button_group component (buttons, _, _) = ((component :> < disabled : bool Js.t Js.prop > Js.t), ref false) :: !buttons -let button ~container ~theme ?group ?state ~icon lbl cb = +let button ?id ~container ~theme ?group ?state ~icon lbl cb = let (others, mutex, cnt) as group = match group with | None -> button_group () | Some group -> group in let button = - H.(button [ + H.(button ~a:(match id with Some id -> [ H.a_id id ] | _ -> []) [ img ~alt:"" ~src:(api_server ^ "/icons/icon_" ^ icon ^ "_" ^ theme ^ ".svg") () ; txt " " ; span ~a:[ a_class [ "label" ] ] [ txt lbl ] @@ -337,6 +337,32 @@ let dropdown ~id ~title items = H.div ~a: [H.a_id id; H.a_class ["dropdown_content"]] items ] +let button_dropup ~container ~theme ?state ~icon ~id_menu ~items lbl cb_before = + let btn_id = id_menu ^ "-btn" in (* assumed to be unique *) + let toggle cb_before () = + let menu = find_component id_menu in + let disp = + match Manip.Css.display menu with + | "block" -> "none" + | _ -> + Lwt.dont_wait (fun () -> cb_before ()) (fun _exc -> ()); + Lwt_js_events.async (fun () -> + Lwt_js_events.click window >|= fun ev -> + Js.Opt.case ev##.target (fun () -> ()) + (fun e -> + if Js.to_string e##.id <> btn_id then + Manip.SetCss.display menu "none")); + "block" + in + Manip.SetCss.display menu disp; + Lwt.return_unit + in + let cb = toggle cb_before in + let div_content = + H.div ~a: [H.a_id id_menu; H.a_class ["dropup_content"]] items in + button ~id:btn_id ~container:container ~theme ?state ~icon lbl cb ; + Manip.appendChild container div_content + let gettimeofday () = (new%js Js.date_now)##getTime /. 1000. @@ -391,6 +417,8 @@ let set_state_from_save_file ?token save = let open Learnocaml_local_storage in (match token with None -> () | Some t -> store sync_token t); store nickname save.nickname; + store all_graded_solutions + (SMap.map (fun ans -> ans.Answer.solution) save.all_exercise_states); store all_exercise_states (SMap.merge (fun _ ans edi -> match ans, edi with @@ -881,6 +909,9 @@ module Editor_button (E : Editor_info) = struct let editor_button = button ~container:E.buttons_container ~theme:"light" + let editor_button_dropup = + button_dropup ~container:E.buttons_container ~theme:"light" + let cleanup template = editor_button ~icon: "cleanup" [%i"Reset"] @@ fun () -> @@ -890,6 +921,81 @@ module Editor_button (E : Editor_info) = struct Ace.set_contents E.ace template); Lwt.return () + let reload token id template = + let rec fetch_draft_solution tok () = + match tok with + | token -> + Server_caller.request (Learnocaml_api.Fetch_save token) >>= function + | Ok save -> + set_state_from_save_file ~token save; + Lwt.return_some (save.Save.nickname) + | Error (`Not_found _) -> + alert ~title:[%i"TOKEN NOT FOUND"] + [%i"The entered token couldn't be recognised."]; + Lwt.return_none + | Error e -> + lwt_alert ~title:[%i"REQUEST ERROR"] [ + H.p [H.txt [%i"Could not retrieve data from server"]]; + H.code [H.txt (Server_caller.string_of_error e)]; + ] ~buttons:[ + [%i"Retry"], (fun () -> fetch_draft_solution tok ()); + [%i"Cancel"], (fun () -> Lwt.return_none); + ] + in + let id_menu = "reload-button-dropup" in (* assumed to be unique *) + editor_button_dropup + ~icon: "down" + ~id_menu + ~items: [ + H.ul [ + H.li ~a: [ H.a_id (id_menu ^ "-graded"); H.a_onclick (fun _ -> + confirm ~title:[%i"Reload latest graded code"] + [H.txt [%i"This will replace your code with your last graded code. Are you sure?"]] + (fun () -> + let graded = Learnocaml_local_storage.(retrieve (graded_solution id)) in + Ace.set_contents E.ace graded; Ace.focus E.ace) ; true) ] + [ H.txt [%i"Reload latest graded code"] ]; + + H.li ~a: [ H.a_id (id_menu ^ "-draft"); H.a_onclick (fun _ -> + confirm ~title:[%i"Reload latest saved draft"] + [H.txt [%i"This will replace your code with your last saved draft. Are you sure?"]] + (fun () -> + let draft = Learnocaml_local_storage.(retrieve (exercise_state id)).Answer.solution in + Ace.set_contents E.ace draft; Ace.focus E.ace) ; true) ] + [ H.txt [%i"Reload latest saved draft"] ]; + + H.li ~a: [ H.a_onclick (fun _ -> + confirm ~title:[%i"START FROM SCRATCH"] + [H.txt [%i"This will discard all your edits. Are you sure?"]] + (fun () -> + Ace.set_contents E.ace template; Ace.focus E.ace) ; true) ] + [ H.txt [%i"Reset to initial template"] ]; + ] + ] + [%i"Reload"] @@ fun () -> + token >>= function + None -> + (* We may want to only show "Reset to initial template" in this case, + though there is already this code in learnocaml_exercise_main.ml: + {| if has_server then EB.reload ... else EB.cleanup ... |}. *) + Lwt.return_unit + | Some tok -> + let found f = + match f () with + | _val -> true + | exception Not_found -> false + in + fetch_draft_solution tok () >|= fun _save -> + let menu_draft = find_component (id_menu ^ "-draft") in + Manip.SetCss.display menu_draft + (if found (fun () -> + Learnocaml_local_storage.(retrieve (exercise_state id)).Answer.solution) + then "" else "none"); + let menu_graded = find_component (id_menu ^ "-graded") in + Manip.SetCss.display menu_graded + (if found (fun () -> + Learnocaml_local_storage.(retrieve (graded_solution id))) + then "" else "none") let download id = editor_button ~icon: "download" [%i"Download"] @@ fun () -> diff --git a/src/app/learnocaml_common.mli b/src/app/learnocaml_common.mli index 55a08f86f..ff7a3e0d6 100644 --- a/src/app/learnocaml_common.mli +++ b/src/app/learnocaml_common.mli @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -91,6 +91,7 @@ val disable_with_button_group : button_group -> unit val button : + ?id: string -> container: 'a Tyxml_js.Html.elt -> theme: string -> ?group: button_group -> @@ -105,6 +106,16 @@ val dropdown : [< Html_types.div_content_fun ] Tyxml_js.Html.elt list -> [> Html_types.div ] Tyxml_js.Html.elt +val button_dropup : + container: 'a Tyxml_js.Html5.elt -> + theme: string -> + ?state: button_state -> + icon: string -> + id_menu: string -> + items: [< Html_types.div_content_fun ] Tyxml_js.Html.elt list -> + string -> (unit -> unit Lwt.t) -> + unit + val render_rich_text : ?on_runnable_clicked: (string -> unit) -> Learnocaml_data.Tutorial.text -> @@ -213,6 +224,7 @@ end module Editor_button (_ : Editor_info) : sig val cleanup : string -> unit + val reload : Learnocaml_data.Token.t option Lwt.t -> string -> string -> unit val download : string -> unit val eval : Learnocaml_toplevel.t -> (string -> unit) -> unit val sync : Token.t option Lwt.t -> Learnocaml_data.SMap.key -> (unit -> unit) -> unit diff --git a/src/app/learnocaml_exercise_main.ml b/src/app/learnocaml_exercise_main.ml index f9130eef9..0651ee828 100644 --- a/src/app/learnocaml_exercise_main.ml +++ b/src/app/learnocaml_exercise_main.ml @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019-2020 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -109,7 +109,7 @@ let () = let toplevel_toolbar = find_component "learnocaml-exo-toplevel-toolbar" in let editor_toolbar = find_component "learnocaml-exo-editor-toolbar" in let toplevel_button = - button ~container: toplevel_toolbar ~theme: "dark" ~group:toplevel_buttons_group ?state:None in + button ?id:None ~container: toplevel_toolbar ~theme: "dark" ~group:toplevel_buttons_group ?state:None in let id = match Url.Current.path with | "" :: "exercises" :: p | "exercises" :: p -> String.concat "/" (List.map Url.urldecode (List.filter ((<>) "") p)) @@ -181,7 +181,9 @@ let () = (* ---- editor pane --------------------------------------------------- *) let editor, ace = setup_editor solution in let module EB = Editor_button (struct let ace = ace let buttons_container = editor_toolbar end) in - EB.cleanup (Learnocaml_exercise.(access File.template exo)); + if has_server then + EB.reload token id (Learnocaml_exercise.(access File.template exo)) + else EB.cleanup (Learnocaml_exercise.(access File.template exo)); EB.sync token id (fun () -> Ace.focus ace; Ace.set_synchronized ace) ; EB.download id; EB.eval top select_tab; @@ -215,7 +217,7 @@ let () = typecheck true end; begin toolbar_button - ~icon: "reload" [%i"Grade!"] @@ fun () -> + ~icon: "reload" [%i"Grade!"] @@ fun () -> check_if_need_refresh has_server >>= fun () -> let aborted, abort_message = let t, u = Lwt.task () in diff --git a/src/app/learnocaml_index_main.ml b/src/app/learnocaml_index_main.ml index 1019e8bf1..8bf0cbad0 100644 --- a/src/app/learnocaml_index_main.ml +++ b/src/app/learnocaml_index_main.ml @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -485,7 +485,7 @@ let tutorial_tab select (arg, set_arg, _delete_arg) () = load_tutorial !current_tutorial_name !current_step_id () >>= fun () -> toplevel_launch >>= fun top -> let toplevel_button = - button ~container: buttons_div ~theme: "dark" ~group:toplevel_buttons_group ?state:None in + button ?id:None ~container: buttons_div ~theme: "dark" ~group:toplevel_buttons_group ?state:None in init_toplevel_pane toplevel_launch top toplevel_buttons_group toplevel_button ; Lwt.return tutorial_div @@ -505,7 +505,7 @@ let toplevel_tab select _ () = (fun _ -> Lwt.async select) toplevel_buttons_group "toplevel" >>= fun top -> Manip.appendChild El.content div ; - let button = button ~container: buttons_div ~theme: "dark" ?group:None ?state:None in + let button = button ?id:None ~container: buttons_div ~theme: "dark" ?group:None ?state:None in init_toplevel_pane (Lwt.return top) top toplevel_buttons_group button ; Lwt.return div diff --git a/src/app/learnocaml_local_storage.ml b/src/app/learnocaml_local_storage.ml index d2e5e0d66..38ea32409 100644 --- a/src/app/learnocaml_local_storage.ml +++ b/src/app/learnocaml_local_storage.ml @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -236,6 +236,14 @@ let exercise_list, [ "exercise-state" ] Answer.enc +let graded_list, + graded_solution, + all_graded_solutions = + listed + [ "exercise-graded-list" ] + [ "exercise-graded" ] + Json_encoding.string + let toplevel_history_list, toplevel_history, all_toplevel_histories = diff --git a/src/app/learnocaml_local_storage.mli b/src/app/learnocaml_local_storage.mli index 1755e6342..0469e1019 100644 --- a/src/app/learnocaml_local_storage.mli +++ b/src/app/learnocaml_local_storage.mli @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -30,6 +30,17 @@ val exercise_state : string -> Answer.t storage_key val all_exercise_states : Answer.t SMap.t storage_key +(* The following three accessors are needed because of Answer.solution: + -- on server: last graded solution; + -- on localStorage: last edited solution, + see learnocaml_common.set_state_from_save_file. *) + +val graded_list : string list storage_key + +val graded_solution : string -> string storage_key + +val all_graded_solutions : string SMap.t storage_key + val exercise_toplevel_history : string -> Learnocaml_toplevel_history.snapshot storage_key val exercise_toplevel_history_list : string list storage_key diff --git a/src/state/learnocaml_data.mli b/src/state/learnocaml_data.mli index 23e24790b..bbc6c46f4 100644 --- a/src/state/learnocaml_data.mli +++ b/src/state/learnocaml_data.mli @@ -1,6 +1,6 @@ (* This file is part of Learn-OCaml. * - * Copyright (C) 2019 OCaml Software Foundation. + * Copyright (C) 2019-2022 OCaml Software Foundation. * Copyright (C) 2016-2018 OCamlPro. * * Learn-OCaml is distributed under the terms of the MIT license. See the @@ -30,6 +30,9 @@ module Report = Learnocaml_report module Answer: sig type t = { + (* -- on server: last graded solution; + -- on localStorage: last edited solution, + see learnocaml_common.set_state_from_save_file. *) solution: string ; grade: int (* \in [0, 100] *) option ; report: Report.t option ; diff --git a/static/css/learnocaml_exercise.css b/static/css/learnocaml_exercise.css index 1517c8bf7..618c7bcb3 100644 --- a/static/css/learnocaml_exercise.css +++ b/static/css/learnocaml_exercise.css @@ -69,6 +69,33 @@ body { top: 61px; } } +/* ---------------- drop up menu ---------------- */ + +.dropup_content { + display: none; + position: absolute; + bottom: 40px; + z-index: 100; + background-color: #666; + box-shadow: 0 0 10px 2px rgba(0,0,0,0.4); + width: max-content; + transition: all .3s ease .15s; /* optional */ +} + +.dropup_content ul { + list-style-type: none; + padding: 0px; + margin: 0px; +} +.dropup_content li { + padding: 5px 10px; + font-size: 16px; +} +.dropup_content li:hover { + background-color: rgba(170,204,255,0.5); + cursor: pointer; +} + /* -------------------- tabs and tab buttons ---------------------- */ #learnocaml-exo-tab-buttons { position: absolute; diff --git a/translations/fr.po b/translations/fr.po index 7aae463af..aa72fe2c4 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -6,9 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: learn-ocaml ~dev\n" -"PO-Revision-Date: 2022-01-07 03:03+0100\n" -"Last-Translator: Louis Gesbert \n" -"Language-Team: OCamlPro\n" +"PO-Revision-Date: 2022-12-29 00:17+0100\n" +"Last-Translator: Erik Martin-Dorel \n" +"Language-Team: OCSF\n" "Language: french\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -85,232 +85,247 @@ msgstr "OK" msgid "ERROR" msgstr "ERREUR" -#: File "src/app/learnocaml_common.ml", line 140, characters 58-66 418, 12-20 -#: "src/app/learnocaml_index_main.ml", 583, 17-25 +#: File "src/app/learnocaml_common.ml", line 140, characters 58-66 446, 12-20 +#: 942, 19-27 "src/app/learnocaml_index_main.ml", 583, 17-25 msgid "Cancel" msgstr "Annuler" -#: File "src/app/learnocaml_common.ml", line 410, characters 26-41 -#: "src/app/learnocaml_index_main.ml", 578, 32-47 +#: File "src/app/learnocaml_common.ml", line 438, characters 26-41 937, 32-47 +#: "src/app/learnocaml_index_main.ml", 578, msgid "REQUEST ERROR" msgstr "ERREUR DE REQUÊTE" -#: File "src/app/learnocaml_common.ml", line 411, characters 22-59 +#: File "src/app/learnocaml_common.ml", line 439, characters 22-59 938, 30-67 #: "src/app/learnocaml_index_main.ml", 579, 28-65 msgid "Could not retrieve data from server" msgstr "Échec lors du téléchargement des données du serveur" -#: File "src/app/learnocaml_common.ml", line 414, characters 12-19 458, 13-20 -#: "src/app/learnocaml_index_main.ml", 582, 17-24 +#: File "src/app/learnocaml_common.ml", line 442, characters 12-19 486, 13-20 +#: 941, 19-26 "src/app/learnocaml_index_main.ml", 582, 17-24 msgid "Retry" msgstr "Réessayer" -#: File "src/app/learnocaml_common.ml", line 417, characters 25-33 459, 13-21 +#: File "src/app/learnocaml_common.ml", line 445, characters 25-33 487, 13-21 msgid "Ignore" msgstr "Ignorer" -#: File "src/app/learnocaml_common.ml", line 454, characters 26-39 +#: File "src/app/learnocaml_common.ml", line 482, characters 26-39 msgid "SYNC FAILED" msgstr "ECHEC DE LA SYNCHRONISATION" -#: File "src/app/learnocaml_common.ml", line 455, characters 22-66 +#: File "src/app/learnocaml_common.ml", line 483, characters 22-66 msgid "Could not synchronise save with the server" msgstr "Les données n'ont pas pu être synchronisées avec le serveur" -#: File "src/app/learnocaml_common.ml", line 514, characters 39-50 +#: File "src/app/learnocaml_common.ml", line 542, characters 39-50 msgid "%dd %02dh" msgstr "%dj %02dh" -#: File "src/app/learnocaml_common.ml", line 515, characters 40-51 +#: File "src/app/learnocaml_common.ml", line 543, characters 40-51 msgid "%02d:%02d" msgstr "%02d:%02d" -#: File "src/app/learnocaml_common.ml", line 516, characters 23-36 +#: File "src/app/learnocaml_common.ml", line 544, characters 23-36 msgid "0:%02d:%02d" msgstr "0:%02d:%02d" -#: File "src/app/learnocaml_common.ml", line 547, characters 34-55 1147, 38-59 +#: File "src/app/learnocaml_common.ml", line 575, characters 34-55 1182, 38-59 msgid "difficulty: %d / 40" msgstr "difficulté: %d / 40" -#: File "src/app/learnocaml_common.ml", line 582, characters 30-75 +#: File "src/app/learnocaml_common.ml", line 610, characters 30-75 msgid "No description available for this exercise." msgstr "Aucune description pour cet exercice." -#: File "src/app/learnocaml_common.ml", line 605, characters 32-41 +#: File "src/app/learnocaml_common.ml", line 633, characters 32-41 #: "src/app/learnocaml_index_main.ml", 132, 54-63 msgid "project" msgstr "projet" -#: File "src/app/learnocaml_common.ml", line 606, characters 32-41 +#: File "src/app/learnocaml_common.ml", line 634, characters 32-41 #: "src/app/learnocaml_index_main.ml", 133, 54-63 msgid "problem" msgstr "problème" -#: File "src/app/learnocaml_common.ml", line 607, characters 33-43 +#: File "src/app/learnocaml_common.ml", line 635, characters 33-43 #: "src/app/learnocaml_index_main.ml", 134, 55-65 msgid "exercise" msgstr "exercice" -#: File "src/app/learnocaml_common.ml", line 743, characters 20-39 -msgid "Fetch from server" -msgstr "Télécharger du serveur" - -#: File "src/app/learnocaml_common.ml", line 746, characters 20-43 751, -msgid "Ignore & keep editing" -msgstr "Ignorer & continuer d'éditer" - -#: File "src/app/learnocaml_common.ml", line 753, characters 20-51 -msgid "Fetch from server & overwrite" -msgstr "Télécharger du serveur & écraser" - -#: File "src/app/learnocaml_common.ml", lines 760-761, characters 28-67 -msgid "" -"A more recent answer exists on the server. Do you want to fetch the new " -"version?" -msgstr "" -"Une version plus récente de cette réponse existe sur le serveur. Voulez-vous " -"télécharger la nouvelle version ?" - -#: File "src/app/learnocaml_common.ml", line 820, characters 26-33 +#: File "src/app/learnocaml_common.ml", line 787, characters 26-33 msgid "Clear" msgstr "Effacer" -#: File "src/app/learnocaml_common.ml", line 825, characters 25-32 947, 24-31 +#: File "src/app/learnocaml_common.ml", line 792, characters 25-32 917, 24-31 msgid "Reset" msgstr "Réinitialiser" -#: File "src/app/learnocaml_common.ml", line 830, characters 22-35 +#: File "src/app/learnocaml_common.ml", line 797, characters 22-35 msgid "Eval phrase" msgstr "Évaluer la phrase" -#: File "src/app/learnocaml_common.ml", line 845, characters 24-51 +#: File "src/app/learnocaml_common.ml", line 812, characters 24-51 msgid "Preparing the environment" msgstr "Préparation de l'environnement" -#: File "src/app/learnocaml_common.ml", line 846, characters 39-47 851, 37-45 +#: File "src/app/learnocaml_common.ml", line 813, characters 39-47 818, 37-45 msgid "Editor" msgstr "Éditeur" -#: File "src/app/learnocaml_common.ml", line 847, characters 41-51 +#: File "src/app/learnocaml_common.ml", line 814, characters 41-51 #: "src/app/learnocaml_index_main.ml", 691, 30-40 msgid "Toplevel" msgstr "Toplevel" -#: File "src/app/learnocaml_common.ml", line 848, characters 39-47 860, +#: File "src/app/learnocaml_common.ml", line 815, characters 39-47 827, #: "src/app/learnocaml_exercise_main.ml", 58, 30-38 62, 67, #: "src/app/learnocaml_student_view.ml", 383, 28-36 396, 400, 405, msgid "Report" msgstr "Rapport" -#: File "src/app/learnocaml_common.ml", line 849, characters 37-47 +#: File "src/app/learnocaml_common.ml", line 816, characters 37-47 msgid "Exercise" msgstr "Exercice" -#: File "src/app/learnocaml_common.ml", line 850, characters 37-46 +#: File "src/app/learnocaml_common.ml", line 817, characters 37-46 msgid "Details" msgstr "Détails" -#: File "src/app/learnocaml_common.ml", line 852, characters 27-70 +#: File "src/app/learnocaml_common.ml", line 819, characters 27-70 msgid "Click the Grade button to get your report" msgstr "Cliquez sur le bouton Noter pour obtenir votre rapport" -#: File "src/app/learnocaml_common.ml", line 857, characters 22-44 +#: File "src/app/learnocaml_common.ml", line 824, characters 22-44 msgid "Loading student data" msgstr "Chargement des informations sur les étudiants" -#: File "src/app/learnocaml_common.ml", line 858, characters 38-45 +#: File "src/app/learnocaml_common.ml", line 825, characters 38-45 msgid "Stats" msgstr "Statistiques" -#: File "src/app/learnocaml_common.ml", line 859, characters 37-48 +#: File "src/app/learnocaml_common.ml", line 826, characters 37-48 #: "src/app/learnocaml_index_main.ml", 688, 29-40 #: "src/app/learnocaml_teacher_tab.ml", 329, 18-29 -#: "src/app/learnocaml_exercise_main.ml", 203, 23-34 +#: "src/app/learnocaml_exercise_main.ml", 204, 23-34 msgid "Exercises" msgstr "Exercices" -#: File "src/app/learnocaml_common.ml", line 861, characters 37-46 +#: File "src/app/learnocaml_common.ml", line 828, characters 37-46 msgid "Subject" msgstr "Énoncé" -#: File "src/app/learnocaml_common.ml", line 862, characters 39-47 +#: File "src/app/learnocaml_common.ml", line 829, characters 39-47 msgid "Answer" msgstr "Réponse" -#: File "src/app/learnocaml_common.ml", line 948, characters 22-42 +#: File "src/app/learnocaml_common.ml", line 918, characters 22-42 968, 34-54 msgid "START FROM SCRATCH" msgstr "TOUT RECOMMENCER" -#: File "src/app/learnocaml_common.ml", line 949, characters 16-65 +#: File "src/app/learnocaml_common.ml", line 919, characters 16-65 969, 28-77 msgid "This will discard all your edits. Are you sure?" -msgstr "Toutes vos modifications seront perdues. Vous êtes sûr·e ?" +msgstr "Toutes vos modifications seront perdues. Êtes-vous sûr·e ?" + +#: File "src/app/learnocaml_common.ml", line 933, characters 28-45 1152, +#: "src/app/learnocaml_index_main.ml", 574, +msgid "TOKEN NOT FOUND" +msgstr "TOKEN NON TROUVÉ" + +#: File "src/app/learnocaml_common.ml", line 934, characters 17-60 1153, +#: "src/app/learnocaml_index_main.ml", 575, +msgid "The entered token couldn't be recognised." +msgstr "Le token entré n'a pas été reconnu." + +#: File "src/app/learnocaml_common.ml", line 952, characters 34-61 957, 25-52 +msgid "Reload latest graded code" +msgstr "Reprendre le dernier code noté" + +#: File "src/app/learnocaml_common.ml", line 953, characters 28-99 +msgid "This will replace your code with your last graded code. Are you sure?" +msgstr "Cela va remplacer votre code par la dernière version évaluée. Êtes-vous sûr·e ?" + +#: File "src/app/learnocaml_common.ml", line 960, characters 34-61 965, 25-52 +msgid "Reload latest saved draft" +msgstr "Reprendre le dernier code synchronisé" -#: File "src/app/learnocaml_common.ml", line 956, characters 27-37 +#: File "src/app/learnocaml_common.ml", line 961, characters 28-99 +msgid "This will replace your code with your last saved draft. Are you sure?" +msgstr "Cela va remplacer votre code par la dernière version synchronisée. Êtes-vous sûr·e ?" + +#: File "src/app/learnocaml_common.ml", line 972, characters 25-52 +msgid "Reset to initial template" +msgstr "Réinitialiser le code" + +#: File "src/app/learnocaml_common.ml", line 975, characters 9-17 +msgid "Reload" +msgstr "Recharger" + +#: File "src/app/learnocaml_common.ml", line 982, characters 27-37 msgid "Download" msgstr "Télécharger" -#: File "src/app/learnocaml_common.ml", line 964, characters 22-33 +#: File "src/app/learnocaml_common.ml", line 990, characters 22-33 msgid "Eval code" msgstr "Exécuter le code" -#: File "src/app/learnocaml_common.ml", line 974, characters 23-29 +#: File "src/app/learnocaml_common.ml", line 1000, characters 23-29 msgid "Sync" msgstr "Sync" -#: File "src/app/learnocaml_common.ml", line 1040, characters 34-49 1075, +#: File "src/app/learnocaml_common.ml", line 1069, characters 34-49 1104, msgid "OCaml prelude" msgstr "Prélude OCaml" -#: File "src/app/learnocaml_common.ml", line 1047, characters 59-65 1082, +#: File "src/app/learnocaml_common.ml", line 1076, characters 59-65 1111, msgid "Hide" msgstr "Cacher" -#: File "src/app/learnocaml_common.ml", line 1052, characters 59-65 1089, +#: File "src/app/learnocaml_common.ml", line 1081, characters 59-65 1118, msgid "Show" msgstr "Montrer" -#: File "src/app/learnocaml_common.ml", line 1113, characters 18-36 -msgid "Enter the secret" -msgstr "Entrez le secret" +#: File "src/app/learnocaml_common.ml", line 1143, characters 18-36 +#: "src/app/learnocaml_index_main.ml", 629, 31-49 +msgid "Enter your token" +msgstr "Entrez votre token" -#: File "src/app/learnocaml_common.ml", line 1153, characters 22-35 +#: File "src/app/learnocaml_common.ml", line 1188, characters 22-35 msgid "Difficulty:" msgstr "Difficulté :" -#: File "src/app/learnocaml_common.ml", line 1167, characters 39-49 +#: File "src/app/learnocaml_common.ml", line 1202, characters 39-49 msgid "Kind: %s" msgstr "Type : %s" -#: File "src/app/learnocaml_common.ml", line 1308, characters 46-59 +#: File "src/app/learnocaml_common.ml", line 1343, characters 46-59 msgid "Identifier:" msgstr "Identifiant de l'exercice :" -#: File "src/app/learnocaml_common.ml", line 1312, characters 48-57 +#: File "src/app/learnocaml_common.ml", line 1347, characters 48-57 msgid "Author:" msgstr "Auteur :" -#: File "src/app/learnocaml_common.ml", line 1313, characters 47-57 +#: File "src/app/learnocaml_common.ml", line 1348, characters 47-57 msgid "Authors:" msgstr "Auteurs :" -#: File "src/app/learnocaml_common.ml", line 1318, characters 31-48 +#: File "src/app/learnocaml_common.ml", line 1353, characters 31-48 msgid "Skills trained:" msgstr "Compétences pratiquées :" -#: File "src/app/learnocaml_common.ml", line 1322, characters 31-49 +#: File "src/app/learnocaml_common.ml", line 1357, characters 31-49 msgid "Skills required:" msgstr "Compétences requises :" -#: File "src/app/learnocaml_common.ml", line 1327, characters 36-57 +#: File "src/app/learnocaml_common.ml", line 1362, characters 36-57 msgid "Previous exercises:" msgstr "Exercices précédents :" -#: File "src/app/learnocaml_common.ml", line 1330, characters 35-52 +#: File "src/app/learnocaml_common.ml", line 1365, characters 35-52 msgid "Next exercises:" msgstr "Exercices suivants :" -#: File "src/app/learnocaml_common.ml", line 1335, characters 26-36 +#: File "src/app/learnocaml_common.ml", line 1370, characters 26-36 msgid "Metadata" msgstr "Métadonnées" @@ -464,14 +479,6 @@ msgstr "" msgid "Please write it down." msgstr "Notez-le !" -#: File "src/app/learnocaml_index_main.ml", line 574, characters 28-45 -msgid "TOKEN NOT FOUND" -msgstr "TOKEN NON TROUVÉ" - -#: File "src/app/learnocaml_index_main.ml", line 575, characters 17-60 -msgid "The entered token couldn't be recognised." -msgstr "Le token entré n'a pas été reconnu." - #: File "src/app/learnocaml_index_main.ml", line 617, characters 7-21 msgid "Connected as" msgstr "Connecté en tant que" @@ -512,10 +519,6 @@ msgstr "Utilisateur existant" msgid "Already have a token? Click here!" msgstr "Vous avez déjà un token ? Par ici !" -#: File "src/app/learnocaml_index_main.ml", line 629, characters 31-49 -msgid "Enter your token" -msgstr "Entrez votre token" - #: File "src/app/learnocaml_index_main.ml", line 630, characters 31-40 msgid "Connect" msgstr "Se connecter" @@ -710,6 +713,72 @@ msgstr "Exporter les données étudiants en CSV" msgid "Unsaved changes" msgstr "Modifications non sauvegardées" +#: File "src/grader/grading.ml", line 16, characters 28-67 +msgid "" +"Exercise definition error %s:\n" +"%a\n" +"%!" +msgstr "" +"Erreur dans la définition de l'exercice %s:\n" +"%a\n" +"%!" + +#: File "src/grader/grading.ml", line 22, characters 28-59 +msgid "" +"Error in user code:\n" +"\n" +"%a\n" +"%!" +msgstr "" +"Erreur dans le code:\n" +"\n" +"%a\n" +"%!" + +#: File "src/grader/grading.ml", line 96, characters 38-65 106, 131, 139, 143, +msgid "while preparing the tests" +msgstr "lors de la préparation des tests" + +#: File "src/grader/grading.ml", line 100, characters 22-44 +msgid "Loading the prelude." +msgstr "Chargement du prélude." + +#: File "src/grader/grading.ml", line 101, characters 38-65 +msgid "while loading the prelude" +msgstr "lors du chargement du prélude" + +#: File "src/grader/grading.ml", line 105, characters 22-55 +msgid "Preparing the test environment." +msgstr "Préparation de l'environnement de test." + +#: File "src/grader/grading.ml", line 110, characters 22-42 +msgid "Loading your code." +msgstr "Chargement du code utilisateur." + +#: File "src/grader/grading.ml", line 115, characters 22-45 +msgid "Loading the solution." +msgstr "Chargement de la solution." + +#: File "src/grader/grading.ml", line 116, characters 38-66 +msgid "while loading the solution" +msgstr "lors du chargement de la solution" + +#: File "src/grader/grading.ml", line 120, characters 22-54 +msgid "Preparing to launch the tests." +msgstr "Préparation du lancement des tests." + +#: File "src/grader/grading.ml", line 146, characters 22-49 +msgid "Launching the test bench." +msgstr "Lancement du banc de test." + +#: File "src/grader/grading.ml", line 175, characters 45-78 +msgid "while loading user dependencies" +msgstr "lors du chargement des dépendances" + +#: File "src/grader/grading.ml", line 191, characters 38-67 +msgid "while testing your solution" +msgstr "lors du test de la solution utilisateur" + #: File "src/app/learnocaml_exercise_main.ml", line 29, characters 22-81 msgid "WARNING: You have an older grader version than the server" msgstr "" @@ -755,37 +824,37 @@ msgstr "Chargement du prélude..." msgid "error in prelude" msgstr "erreur dans le prélude" -#: File "src/app/learnocaml_exercise_main.ml", line 215, characters 28-37 +#: File "src/app/learnocaml_exercise_main.ml", line 216, characters 28-37 #: "src/app/learnocaml_playground_main.ml", 84, msgid "Compile" msgstr "Compiler" -#: File "src/app/learnocaml_exercise_main.ml", line 219, characters 29-37 +#: File "src/app/learnocaml_exercise_main.ml", line 220, characters 25-33 msgid "Grade!" msgstr "Noter!" -#: File "src/app/learnocaml_exercise_main.ml", line 223, characters 48-55 +#: File "src/app/learnocaml_exercise_main.ml", line 224, characters 48-55 msgid "abort" msgstr "abandonner" -#: File "src/app/learnocaml_exercise_main.ml", lines 227-228, characters 35-65 +#: File "src/app/learnocaml_exercise_main.ml", lines 228-229, characters 35-65 msgid "Grading is taking a lot of time, maybe your code is looping? " msgstr "" "La notation prend du temps, peut-être une boucle infinie dans votre code ? " -#: File "src/app/learnocaml_exercise_main.ml", line 234, characters 35-57 +#: File "src/app/learnocaml_exercise_main.ml", line 235, characters 35-57 msgid "Launching the grader" msgstr "Lancement de la notation" -#: File "src/app/learnocaml_exercise_main.ml", line 257, characters 60-86 +#: File "src/app/learnocaml_exercise_main.ml", line 258, characters 60-86 msgid "Grading aborted by user." msgstr "Notation annulée par l'utilisateur." -#: File "src/app/learnocaml_exercise_main.ml", line 279, characters 38-59 +#: File "src/app/learnocaml_exercise_main.ml", line 280, characters 38-59 msgid "Error in your code." msgstr "Erreur dans le code." -#: File "src/app/learnocaml_exercise_main.ml", line 280, characters 27-85 +#: File "src/app/learnocaml_exercise_main.ml", line 281, characters 27-85 msgid "Cannot start the grader if your code does not typecheck." msgstr "La notation ne peut être lancée si le code ne compile pas." @@ -893,71 +962,24 @@ msgstr "" msgid "Unexpected error:\n" msgstr "Erreur inattendue:\n" -#: File "src/grader/grading.ml", line 16, characters 28-67 -msgid "" -"Exercise definition error %s:\n" -"%a\n" -"%!" -msgstr "" -"Erreur dans la définition de l'exercice %s:\n" -"%a\n" -"%!" +#~ msgid "Fetch from server" +#~ msgstr "Télécharger du serveur" -#: File "src/grader/grading.ml", line 22, characters 28-59 -msgid "" -"Error in user code:\n" -"\n" -"%a\n" -"%!" -msgstr "" -"Erreur dans le code:\n" -"\n" -"%a\n" -"%!" +#~ msgid "Ignore & keep editing" +#~ msgstr "Ignorer & continuer d'éditer" -#: File "src/grader/grading.ml", line 96, characters 38-65 106, 131, 139, 143, -msgid "while preparing the tests" -msgstr "lors de la préparation des tests" +#~ msgid "Fetch from server & overwrite" +#~ msgstr "Télécharger du serveur & écraser" -#: File "src/grader/grading.ml", line 100, characters 22-44 -msgid "Loading the prelude." -msgstr "Chargement du prélude." - -#: File "src/grader/grading.ml", line 101, characters 38-65 -msgid "while loading the prelude" -msgstr "lors du chargement du prélude" - -#: File "src/grader/grading.ml", line 105, characters 22-55 -msgid "Preparing the test environment." -msgstr "Préparation de l'environnement de test." - -#: File "src/grader/grading.ml", line 110, characters 22-42 -msgid "Loading your code." -msgstr "Chargement du code utilisateur." - -#: File "src/grader/grading.ml", line 115, characters 22-45 -msgid "Loading the solution." -msgstr "Chargement de la solution." - -#: File "src/grader/grading.ml", line 116, characters 38-66 -msgid "while loading the solution" -msgstr "lors du chargement de la solution" - -#: File "src/grader/grading.ml", line 120, characters 22-54 -msgid "Preparing to launch the tests." -msgstr "Préparation du lancement des tests." - -#: File "src/grader/grading.ml", line 146, characters 22-49 -msgid "Launching the test bench." -msgstr "Lancement du banc de test." - -#: File "src/grader/grading.ml", line 175, characters 45-78 -msgid "while loading user dependencies" -msgstr "lors du chargement des dépendances" +#~ msgid "" +#~ "A more recent answer exists on the server. Do you want to fetch the new " +#~ "version?" +#~ msgstr "" +#~ "Une version plus récente de cette réponse existe sur le serveur. Voulez-" +#~ "vous télécharger la nouvelle version ?" -#: File "src/grader/grading.ml", line 191, characters 38-67 -msgid "while testing your solution" -msgstr "lors du test de la solution utilisateur" +#~ msgid "Enter the secret" +#~ msgstr "Entrez le secret" #~ msgid "Try OCaml" #~ msgstr "Try OCaml"