Skip to content

Commit

Permalink
feat: Improve description entrypoint (#423)
Browse files Browse the repository at this point in the history
* feat(description): Add learnocaml-exo-loading initial phase

* in order to have as good an UX as that of exercises view

* feat(description): Implement padding decoding

* Aim: interoperate with learn-ocaml.el, which can encode the token by

  (base64-encode-string (format "%192s" "AAA-BBB-CCC-DDD") t)

  before opening the URL

  http://localhost:8080/description/:id#token1=:encoded

  with a 256-wide string for ":encoded" that couldn't be easily copied
  if, say, the user takes a screenshot of the exercise description.

* For the moment, the support of legacy URLs

  http://localhost:8080/description/:id#token=AAA-BBB-CCC-DDD

  is still supported (but deprecated; it may be removed later on).
  • Loading branch information
erikmd committed Sep 29, 2021
1 parent 51ed717 commit a825d7e
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 11 deletions.
68 changes: 57 additions & 11 deletions src/app/learnocaml_description_main.ml
Expand Up @@ -8,13 +8,47 @@ open Learnocaml_data.Exercise.Meta
let init_tabs, select_tab =
mk_tab_handlers "text" ["text"; "meta"]

type encoded_token =
{
arg_name: string;
raw_arg: string;
token: Learnocaml_data.Token.t
}

(** [get_arg_token ()] read (and decode if need be) the user token.
@return [Some encoded_token] if a token was successfully read.
It returns [None] if no token was specified in the URL.
An exception is raised if an incorrect token was specified. *)
let get_encoded_token () =
match arg "token" with (* arg in plain text, deprecated in learn-ocaml 0.13 *)
| raw_arg ->
let token = Learnocaml_data.Token.parse raw_arg in
Some { arg_name = "token"; raw_arg; token }
| exception Not_found ->
match arg "token1" with (* encoding algo 1: space-padded token |> base64 *)
| raw_arg ->
begin match Base64.decode ~pad:true raw_arg with
(* ~pad:false would work also, but ~pad:true is stricter *)
| Ok pad_token ->
Some { arg_name = "token1"; raw_arg;
token = Learnocaml_data.Token.parse (String.trim pad_token) }
| Error (`Msg msg) -> failwith msg
end
| exception Not_found -> None

module Exercise_link =
struct
let exercise_link ?(cl = []) id content =
let token = Learnocaml_data.Token.(to_string (parse (arg "token"))) in
Tyxml_js.Html5.(a ~a:[ a_href ("/description/"^id^"#token="^token);
a_class cl ]
content)
match get_encoded_token () with
| Some { arg_name; raw_arg; _ } ->
Tyxml_js.Html5.(a ~a:[ a_href
(Printf.sprintf "/description/%s#%s=%s"
id arg_name raw_arg);
a_class cl ]
content)
| None ->
Tyxml_js.Html5.(a ~a:[ a_href "#" ; a_class cl ] content)
end

module Display = Display_exercise(Exercise_link)
Expand All @@ -29,8 +63,8 @@ let () =
Learnocaml_local_storage.init () ;
let title_container = find_component "learnocaml-exo-tab-text-title" in
let text_container = find_component "learnocaml-exo-tab-text-descr" in
try begin
let token = Learnocaml_data.Token.parse (arg "token") in
match get_encoded_token () with
| Some { arg_name = _; raw_arg = _; token } -> begin
let exercise_fetch =
retrieve (Learnocaml_api.Exercise (Some token, id))
in
Expand All @@ -51,9 +85,21 @@ let () =
d##write (Js.string (exercise_text ex_meta exo));
d##close) ;
(* display meta *)
display_meta (Some token) ex_meta id
display_meta (Some token) ex_meta id >>= fun () ->
(* hide the initial/loading phase curtain *)
Lwt.return @@ hide_loading ~id:"learnocaml-exo-loading" ()
end
with Not_found ->
Lwt.return @@
Manip.replaceChildren text_container
Tyxml_js.Html5.[ h1 [ txt "Error: Missing token" ] ]
| None ->
let elt = find_div_or_append_to_body "learnocaml-exo-loading" in
Manip.(addClass elt "loading-layer") ;
Manip.(removeClass elt "loaded") ;
Manip.(addClass elt "loading") ;
Manip.replaceChildren elt
Tyxml_js.Html5.[
h1 [ txt "Error: missing token. \
Use a link from ";
(* Note: could be put in a global constant *)
a ~a:[ a_href "https://github.com/pfitaxel/learn-ocaml.el" ]
[ txt "learn-ocaml-mode" ];
txt "?" ] ];
Lwt.return_unit
10 changes: 10 additions & 0 deletions static/css/learnocaml_description.css
Expand Up @@ -141,6 +141,16 @@ body {

/* BEGIN excerpt from learnocaml_exercise.css */

/* -------------------- loading splash screen --------------------- */
#learnocaml-exo-loading {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
#learnocaml-exo-loading.loading,
#learnocaml-exo-loading.loaded {
background: rgba(200,200,200,0.9);
}

.learnocaml-exo-meta-category ~ .exercise + .exercise {
border-top: 1px #333 solid;
}
Expand Down
21 changes: 21 additions & 0 deletions static/description.html
Expand Up @@ -19,6 +19,27 @@
</head>

<body>
<!-- Should be kept untouched. -->
<div style="display:none">
<!-- (Weakly) preload images. -->
<img src="/icons/tryocaml_loading_1.gif"><img src="/icons/tryocaml_loading_2.gif">
<img src="/icons/tryocaml_loading_3.gif"><img src="/icons/tryocaml_loading_4.gif">
<img src="/icons/tryocaml_loading_5.gif"><img src="/icons/tryocaml_loading_6.gif">
<img src="/icons/tryocaml_loading_7.gif"><img src="/icons/tryocaml_loading_8.gif">
<img src="/icons/tryocaml_loading_9.gif">
</div>
<!-- Three states: .initial, .loading and .loaded.
Set to .loaded when initial loading finished.
Set to .loading while loading, then to .loaded. -->
<div id="learnocaml-exo-loading" class="loading-layer initial">
<div id="chamo"><img id="chamo-img" src="/icons/tryocaml_loading_5.gif"></div>
<div class="messages"><ul><li id="txt_preparing">Preparing the environment</li></ul></div>
</div>
<script language="JavaScript">
var n = Math.floor (Math.random () * 8.99) + 1;
document.getElementById('chamo-img').src = learnocaml_config.baseUrl + '/icons/tryocaml_loading_' + n + '.gif';
</script>
<!-- Anything below could be recreated dynamically, but IDs must be kept. -->
<div id="learnocaml-exo-toolbar">
<div class="logo">
<img src="/icons/logo_ocaml.svg">
Expand Down

0 comments on commit a825d7e

Please sign in to comment.