@@ -10,11 +10,13 @@ open Js_of_ocaml
1010open Js_of_ocaml_tyxml
1111open Js_of_ocaml_lwt
1212open Js_utils
13+ open Re.Pcre
1314open Lwt
1415open Learnocaml_data
1516open Learnocaml_common
1617open Learnocaml_config
1718
19+
1820module H = Tyxml_js. Html5
1921
2022module El = struct
@@ -85,8 +87,76 @@ let (exercise_filter_signal: string option React.signal), set_exercise_filter =
8587let (exercise_sort_signal: exercise_ordering React.signal ), set_exercise_sort =
8688 React.S. create By_category
8789
90+ let (expand_state: string list React.signal ), set_expand_state =
91+ React.S. create []
92+
93+ let encode str =
94+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " ," ) ~subst: (fun _ -> " -c" ) (
95+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " &" ) ~subst: (fun _ -> " -a" ) (
96+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " =" ) ~subst: (fun _ -> " -e" ) (
97+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " -" ) ~subst: (fun _ -> " --" ) str)))
98+ let decode str =
99+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " --" ) ~subst: (fun _ -> " -" ) (
100+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " -e" ) ~subst: (fun _ -> " =" ) (
101+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " -a" ) ~subst: (fun _ -> " &" ) (
102+ Re.Pcre. substitute ~rex: (Re.Pcre. regexp " -c" ) ~subst: (fun _ -> " ," ) str)))
103+
104+ let rec update_expand ?value fragment =
105+ match value with
106+ | Some v ->
107+ if (List. mem_assoc " expand" fragment) then
108+ match fragment with
109+ | [] ->
110+ update_expand ~value: v []
111+ | ("expand" , x )::t ->
112+ let expand_list = Re.Pcre. split ~rex: (Re.Pcre. regexp " ," ) x in
113+ let new_expand_list =
114+ if (List. mem v expand_list) then
115+ begin
116+ let filtered_list = List. filter (fun x -> x <> v) expand_list in
117+ if (filtered_list = [] ) then [" " ] else filtered_list
118+ end
119+ else
120+ begin
121+ if (expand_list = [" " ]) then [v] else v::expand_list
122+ end
123+ in
124+ let join l =
125+ match l with
126+ | [] -> " "
127+ | h ::t -> List. fold_left (fun acc x -> acc ^ " ," ^ x) h t
128+ in
129+ let joined_expand = join new_expand_list in
130+ set_expand_state new_expand_list;
131+ (* if (joined_expand <> "") then*) (" expand" , joined_expand)::t (* else t*)
132+ | h ::t ->
133+ h::(update_expand ~value: v t)
134+ else
135+ begin
136+ set_expand_state [v];
137+ fragment@ [(" expand" ,v)]
138+ end
139+ | None ->
140+ set_expand_state [] ;
141+ List. filter (fun (k , _ ) -> k <> " expand" ) fragment
142+
143+ let update_sort value fragment =
144+ let filtered_fragment = List. remove_assoc " sort" (update_expand fragment) in
145+ filtered_fragment@ [(" sort" ,value)]
146+
147+ let update_fragment key value =
148+ let fragment = Js_utils. parse_fragment () in
149+ let filtered_fragment =
150+ if (key = " expand" ) then
151+ let v = (Uri. pct_encode (encode value)) in
152+ update_expand ~value: v fragment
153+ else
154+ update_sort value fragment
155+ in
156+ Js_utils. set_fragment (filtered_fragment)
157+
88158let make_exercises_to_display_signal index =
89- let get_index exo_sort exo_filter =
159+ let get_index exo_sort exo_filter _expand =
90160 let index =
91161 match exo_sort with
92162 | By_category -> index
@@ -113,7 +183,7 @@ let make_exercises_to_display_signal index =
113183 StrMap. fold (fun skill exercises acc ->
114184 (skill,
115185 {Exercise.Index. title = skill;
116- contents = Exercise.Index. Exercises (List. rev exercises)})
186+ contents = Exercise.Index. Exercises (List. rev exercises)})
117187 :: acc)
118188 by_skill []
119189 in
@@ -165,14 +235,31 @@ let make_exercises_to_display_signal index =
165235 Exercise.Index. contents = Exercise.Index. Exercises [] ; }]
166236 else index
167237 in
168- React.S. l2 get_index exercise_sort_signal exercise_filter_signal
238+ React.S. l3 get_index exercise_sort_signal exercise_filter_signal expand_state
169239
170240let retain_signals = ref (React.S. const () )
171241(* Used to register signals as GC roots *)
172242
173243let exercises_tab token : tab_handler =
174244 fun _ _ () ->
175245 let open Tyxml_js.Html5 in
246+ let () =
247+ Dom_html. window##.onhashchange := Dom_html. handler (fun _ ->
248+ let fragment = Js_utils. parse_fragment () in
249+ (match List. assoc_opt " sort" fragment with
250+ | Some "category" -> set_exercise_sort By_category
251+ | Some "skill" -> set_exercise_sort By_skill
252+ | Some "difficulty" -> set_exercise_sort By_difficulty
253+ | _ -> () );
254+ let expands = List. assoc_opt " expand" fragment in
255+ let ids expands =
256+ match expands with
257+ | None -> []
258+ | Some e -> Re.Pcre. split ~rex: (Re.Pcre. regexp " ," ) e
259+ in
260+ set_expand_state (ids expands);
261+ Js. _true)
262+ in
176263 show_loading [% i" Loading exercises" ] @@ fun () ->
177264 Lwt_js. sleep 0.5 >> = fun () ->
178265 retrieve (Learnocaml_api. Exercise_index token)
@@ -236,6 +323,12 @@ let exercises_tab token : tab_handler =
236323 ]
237324 ] ]
238325 in
326+ let update_expand_class id ids =
327+ if (List. mem id ids) then
328+ []
329+ else
330+ [" collapse" ]
331+ in
239332 let rec format_exercise_list index =
240333 match index with
241334 | Exercise.Index. Exercises el ->
@@ -248,7 +341,24 @@ let exercises_tab token : tab_handler =
248341 List. map (fun (id , grp ) ->
249342 let clas =
250343 " group-title" ::
251- match gl with [] | [_] -> [] | _ -> [" collapsed" ]
344+ match gl with
345+ | [] -> []
346+ | [_] ->
347+ let expand_ids = React.S. value expand_state in
348+ if (expand_ids = [] ) then
349+ begin
350+ set_expand_state [id];
351+ update_fragment " expand" id;
352+ []
353+ end
354+ else
355+ update_expand_class id expand_ids
356+ | _ ->
357+ let expand_ids = React.S. value expand_state in
358+ if (expand_ids = [] ) then
359+ [" collapsed" ]
360+ else
361+ update_expand_class id expand_ids
252362 in
253363 let title =
254364 H. div ~a: [a_id id; a_class clas]
@@ -257,8 +367,9 @@ let exercises_tab token : tab_handler =
257367 let exos = format_exercise_list grp.Exercise.Index. contents in
258368 Manip.Ev. onclick title
259369 (fun _ ->
260- ignore (Manip. toggleClass title " collapsed" );
261- false );
370+ update_fragment " expand" id;
371+ ignore (Manip. toggleClass title " collapsed" );
372+ false );
262373 H. li [title; exos])
263374 gl
264375 in
@@ -269,7 +380,14 @@ let exercises_tab token : tab_handler =
269380 List. map (fun (id , sort , name ) ->
270381 let btn = button ~a: [a_id id] [ txt name ] in
271382 Manip.Ev. onclick btn
272- (fun _ -> set_exercise_sort sort; true );
383+ (fun _ ->
384+ let sort_value =
385+ match sort with
386+ | By_category -> " category"
387+ | By_skill -> " skill"
388+ | By_difficulty -> " difficulty"
389+ in
390+ update_fragment " sort" sort_value;set_exercise_sort sort;true );
273391 let signal =
274392 React.S. map (fun s ->
275393 (if sort = s then Manip. addClass else Manip. removeClass)
@@ -311,6 +429,17 @@ let exercises_tab token : tab_handler =
311429 in
312430 retain_signals :=
313431 React.S. merge (fun () () -> () ) () (list_update_signal :: btns_sigs);
432+ let () =
433+ ignore (React.S. map (fun expanded_ids ->
434+ List. iter (fun id ->
435+ match Dom_html. getElementById_opt id with
436+ | Some elt ->
437+ let class_list = elt##.classList in
438+ if Js. to_bool (class_list##contains (Js. string " group-title" )) &&
439+ Js. to_bool (class_list##contains (Js. string " collapsed" ))
440+ then ignore (class_list##remove (Js. string " collapsed" ))
441+ | None -> () ) expanded_ids) expand_state)
442+ in
314443 Lwt. return pane_div
315444
316445let playground_tab token : tab_handler =
0 commit comments