Skip to content

Commit e1bc4be

Browse files
committed
feat: Improve description entrypoint (ocaml-sf#423)
* 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).
1 parent d3fc9c5 commit e1bc4be

File tree

3 files changed

+88
-11
lines changed

3 files changed

+88
-11
lines changed

src/app/learnocaml_description_main.ml

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,47 @@ open Learnocaml_data.Exercise.Meta
77
let init_tabs, select_tab =
88
mk_tab_handlers "text" ["text"; "meta"]
99

10+
type encoded_token =
11+
{
12+
arg_name: string;
13+
raw_arg: string;
14+
token: Learnocaml_data.Token.t
15+
}
16+
17+
(** [get_arg_token ()] read (and decode if need be) the user token.
18+
19+
@return [Some encoded_token] if a token was successfully read.
20+
It returns [None] if no token was specified in the URL.
21+
An exception is raised if an incorrect token was specified. *)
22+
let get_encoded_token () =
23+
match arg "token" with (* arg in plain text, deprecated in learn-ocaml 0.13 *)
24+
| raw_arg ->
25+
let token = Learnocaml_data.Token.parse raw_arg in
26+
Some { arg_name = "token"; raw_arg; token }
27+
| exception Not_found ->
28+
match arg "token1" with (* encoding algo 1: space-padded token |> base64 *)
29+
| raw_arg ->
30+
begin match Base64.decode ~pad:true raw_arg with
31+
(* ~pad:false would work also, but ~pad:true is stricter *)
32+
| Ok pad_token ->
33+
Some { arg_name = "token1"; raw_arg;
34+
token = Learnocaml_data.Token.parse (String.trim pad_token) }
35+
| Error (`Msg msg) -> failwith msg
36+
end
37+
| exception Not_found -> None
38+
1039
module Exercise_link =
1140
struct
1241
let exercise_link ?(cl = []) id content =
13-
let token = Learnocaml_data.Token.(to_string (parse (arg "token"))) in
14-
Tyxml_js.Html5.(a ~a:[ a_href ("/description/"^id^"#token="^token);
15-
a_class cl ]
16-
content)
42+
match get_encoded_token () with
43+
| Some { arg_name; raw_arg; _ } ->
44+
Tyxml_js.Html5.(a ~a:[ a_href
45+
(Printf.sprintf "/description/%s#%s=%s"
46+
id arg_name raw_arg);
47+
a_class cl ]
48+
content)
49+
| None ->
50+
Tyxml_js.Html5.(a ~a:[ a_href "#" ; a_class cl ] content)
1751
end
1852

1953
module Display = Display_exercise(Exercise_link)
@@ -28,8 +62,8 @@ let () =
2862
Learnocaml_local_storage.init () ;
2963
let title_container = find_component "learnocaml-exo-tab-text-title" in
3064
let text_container = find_component "learnocaml-exo-tab-text-descr" in
31-
try begin
32-
let token = Learnocaml_data.Token.parse (arg "token") in
65+
match get_encoded_token () with
66+
| Some { arg_name = _; raw_arg = _; token } -> begin
3367
let exercise_fetch =
3468
retrieve (Learnocaml_api.Exercise (Some token, id))
3569
in
@@ -50,9 +84,21 @@ let () =
5084
d##write (Js.string (exercise_text ex_meta exo));
5185
d##close) ;
5286
(* display meta *)
53-
display_meta (Some token) ex_meta id
87+
display_meta (Some token) ex_meta id >>= fun () ->
88+
(* hide the initial/loading phase curtain *)
89+
Lwt.return @@ hide_loading ~id:"learnocaml-exo-loading" ()
5490
end
55-
with Not_found ->
56-
Lwt.return @@
57-
Manip.replaceChildren text_container
58-
Tyxml_js.Html5.[ h1 [ txt "Error: Missing token" ] ]
91+
| None ->
92+
let elt = find_div_or_append_to_body "learnocaml-exo-loading" in
93+
Manip.(addClass elt "loading-layer") ;
94+
Manip.(removeClass elt "loaded") ;
95+
Manip.(addClass elt "loading") ;
96+
Manip.replaceChildren elt
97+
Tyxml_js.Html5.[
98+
h1 [ txt "Error: missing token. \
99+
Use a link from ";
100+
(* Note: could be put in a global constant *)
101+
a ~a:[ a_href "https://github.com/pfitaxel/learn-ocaml.el" ]
102+
[ txt "learn-ocaml-mode" ];
103+
txt "?" ] ];
104+
Lwt.return_unit

static/css/learnocaml_description.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ body {
141141

142142
/* BEGIN excerpt from learnocaml_exercise.css */
143143

144+
/* -------------------- loading splash screen --------------------- */
145+
#learnocaml-exo-loading {
146+
position: absolute;
147+
top: 0; left: 0; right: 0; bottom: 0;
148+
}
149+
#learnocaml-exo-loading.loading,
150+
#learnocaml-exo-loading.loaded {
151+
background: rgba(200,200,200,0.9);
152+
}
153+
144154
.learnocaml-exo-meta-category ~ .exercise + .exercise {
145155
border-top: 1px #333 solid;
146156
}

static/description.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@
1919
</head>
2020

2121
<body>
22+
<!-- Should be kept untouched. -->
23+
<div style="display:none">
24+
<!-- (Weakly) preload images. -->
25+
<img src="/icons/tryocaml_loading_1.gif"><img src="/icons/tryocaml_loading_2.gif">
26+
<img src="/icons/tryocaml_loading_3.gif"><img src="/icons/tryocaml_loading_4.gif">
27+
<img src="/icons/tryocaml_loading_5.gif"><img src="/icons/tryocaml_loading_6.gif">
28+
<img src="/icons/tryocaml_loading_7.gif"><img src="/icons/tryocaml_loading_8.gif">
29+
<img src="/icons/tryocaml_loading_9.gif">
30+
</div>
31+
<!-- Three states: .initial, .loading and .loaded.
32+
Set to .loaded when initial loading finished.
33+
Set to .loading while loading, then to .loaded. -->
34+
<div id="learnocaml-exo-loading" class="loading-layer initial">
35+
<div id="chamo"><img id="chamo-img" src="/icons/tryocaml_loading_5.gif"></div>
36+
<div class="messages"><ul><li id="txt_preparing">Preparing the environment</li></ul></div>
37+
</div>
38+
<script language="JavaScript">
39+
var n = Math.floor (Math.random () * 8.99) + 1;
40+
document.getElementById('chamo-img').src = learnocaml_config.baseUrl + '/icons/tryocaml_loading_' + n + '.gif';
41+
</script>
42+
<!-- Anything below could be recreated dynamically, but IDs must be kept. -->
2243
<div id="learnocaml-exo-toolbar">
2344
<div class="logo">
2445
<img src="/icons/logo_ocaml.svg">

0 commit comments

Comments
 (0)