From 72e3b16bbd258e63f047392c973ba5e8f0a823c8 Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Tue, 1 Jun 2021 13:11:58 +0200 Subject: Added export button in editor --- editor/actions/actions.ml | 133 ---------------------------------- editor/actions/delete_page.ml | 7 +- editor/actions/dune | 3 +- editor/actions/editor_actions.ml | 150 +++++++++++++++++++++++++++++++++++++++ editor/actions/export.ml | 46 ++++++++++++ editor/dune | 6 +- editor/editor.ml | 6 +- editor/forms/delete_page.ml | 18 ----- editor/forms/delete_page.mli | 5 -- editor/forms/dune | 4 -- editor/forms/validation.ml | 13 ++++ editor/forms/validation.mli | 5 ++ editor/state/storage.ml | 21 ++++++ editor/state/storage.mli | 3 + 14 files changed, 248 insertions(+), 172 deletions(-) delete mode 100755 editor/actions/actions.ml create mode 100755 editor/actions/editor_actions.ml create mode 100755 editor/actions/export.ml delete mode 100755 editor/forms/delete_page.ml delete mode 100755 editor/forms/delete_page.mli create mode 100755 editor/forms/validation.ml create mode 100755 editor/forms/validation.mli diff --git a/editor/actions/actions.ml b/editor/actions/actions.ml deleted file mode 100755 index f35beef..0000000 --- a/editor/actions/actions.ml +++ /dev/null @@ -1,133 +0,0 @@ -open StdLabels -open Js_of_ocaml -open Brr -open Brr_note - -type button_actions = - { delete : State.event Note.event - ; redirect : State.event Note.event - ; add: State.event Note.event - } - -let populate_menu - : unit -> button_actions option - = fun () -> - match Blog.Sidebar.get () with - | None -> None - | Some element -> - - let () = Blog.Sidebar.clean element in - - let delete_button = El.button - ~at:At.[ class' (Jstr.v "action-button") ] - [ El.i [] - ~at:At.[ class' (Jstr.v "fa") - ; class' (Jstr.v "fa-2x") - ; class' (Jstr.v "fa-trash") ] ] - - and home_button = El.button - ~at:At.[ class' (Jstr.v "action-button") ] - [ El.i [] - ~at:At.[ class' (Jstr.v "fa") - ; class' (Jstr.v "fa-2x") - ; class' (Jstr.v "fa-home") ] ] - - and add_button = El.button - ~at:At.[ class' (Jstr.v "action-button") ] - [ El.i [] - ~at:At.[ class' (Jstr.v "fa") - ; class' (Jstr.v "fa-2x") - ; class' (Jstr.v "fa-plus") ] ] - in - - (* We are waiting for event inside another event ( form validation inside - popup creation. - - Note.E.join is used here in order to get only te popup validation. *) - let delete_event = - Note.E.join ( - Evr.on_el - Ev.click - (fun _ -> Delete_page.create ()) - delete_button) - - (* Event on popup creation *) - and add_event = - Note.E.join ( - Evr.on_el - Ev.click - (fun _ -> Add_page.create ()) - add_button) in - - - let stored_pages = State.Storage.get_ids () in - let pages = - List.map - stored_pages - ~f:(fun id -> - - let name_opt = (State.Storage.load (Some id))##.title in - let name = Js.Opt.get - name_opt - (fun () -> id) in - - let target = Jstr.v "#" in - El.li - [ El.a - ~at:[At.href target] - [ El.txt name ] ]) in - - (* Wait for a click on an existing page in order to sent the associated - event. - - We compose the resulting event with both : - - the home button - - the list for all the pages presents in the sidebar *) - - let redirect_handler = - (module Load_page.M : State.Event with type t = Load_page.M.t ) in - - let redirect_event = Note.E.select - (( Evr.on_el - Ev.click - (fun _ -> State.E (None, redirect_handler)) - home_button - ) :: ( - List.map2 stored_pages pages - ~f:(fun name el -> - Evr.on_el - Ev.click - (fun _ -> State.E ((Some name), redirect_handler)) - el ))) in - - let childs = - [ home_button - ; add_button - ; El.button - ~at:At.[class' (Jstr.v "action-button")] - [ El.i - [] - ~at:At.[ class' (Jstr.v "fa") - ; class' (Jstr.v "fa-2x") - ; class' (Jstr.v "fa-download") ] - ] - ; delete_button - ; El.button - ~at:At.[class' (Jstr.v "action-button")] - [ El.i - [] - ~at:At.[ class' (Jstr.v "fa") - ; class' (Jstr.v "fa-2x") - ; class' (Jstr.v "fa-cog") ] - ] - ; El.hr () - ; El.ul - pages - ] in - - let () = El.append_children element childs in - Some - { delete = delete_event - ; redirect = redirect_event - ; add = add_event - } diff --git a/editor/actions/delete_page.ml b/editor/actions/delete_page.ml index 9086fc3..fb600bd 100755 --- a/editor/actions/delete_page.ml +++ b/editor/actions/delete_page.ml @@ -17,10 +17,13 @@ end let create : unit -> State.event Note.event = fun () -> - let title = Jstr.v "Confirmation" in + let title = Jstr.v "Confirmation" + and message = + Jstr.v "La page sera définitivement supprimée" + in let ev = Elements.Popup.create ~title - ~form:(Some (Forms.Delete_page.create () )) + ~form:(Some (Forms.Validation.create message )) in Note.E.map (fun v -> State.E diff --git a/editor/actions/dune b/editor/actions/dune index 5d269c4..4044b52 100755 --- a/editor/actions/dune +++ b/editor/actions/dune @@ -1,11 +1,10 @@ (library - (name actions) + (name editor_actions) (libraries brr brr.note elements blog - js_lib forms state ) diff --git a/editor/actions/editor_actions.ml b/editor/actions/editor_actions.ml new file mode 100755 index 0000000..8c47363 --- /dev/null +++ b/editor/actions/editor_actions.ml @@ -0,0 +1,150 @@ +open StdLabels +open Js_of_ocaml +open Brr +open Brr_note + +let populate_menu + : unit -> State.event Note.event option + = fun () -> + match Blog.Sidebar.get () with + | None -> None + | Some element -> + + let () = Blog.Sidebar.clean element in + + let delete_button = El.button + ~at:At.[ class' (Jstr.v "action-button") ] + [ El.i [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-trash") ] ] + + and home_button = El.button + ~at:At.[ class' (Jstr.v "action-button") ] + [ El.i [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-home") ] ] + + and add_button = El.button + ~at:At.[ class' (Jstr.v "action-button") ] + [ El.i [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-plus") ] ] + + and export_button = El.button + ~at:At.[class' (Jstr.v "action-button")] + [ El.i + [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-download") ] + ] + + and load_button = El.button + ~at:At.[class' (Jstr.v "action-button")] + [ El.i + [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-upload") ] + ] + in + + (* We are waiting for event inside another event ( form validation inside + popup creation. + + Note.E.join is used here in order to get only te popup validation. *) + let delete_event = + Note.E.join ( + Evr.on_el + Ev.click + (fun _ -> Delete_page.create ()) + delete_button) + + (* Event on popup creation *) + and add_event = + Note.E.join ( + Evr.on_el + Ev.click + (fun _ -> Add_page.create ()) + add_button) + + and export_event = + Note.E.join ( + Evr.on_el + Ev.click + (fun _ -> Export.create ()) + export_button) + in + + + let stored_pages = State.Storage.get_ids () in + let pages = + List.map + stored_pages + ~f:(fun id -> + + let name_opt = (State.Storage.load (Some id))##.title in + let name = Js.Opt.get + name_opt + (fun () -> id) in + + let target = Jstr.v "#" in + El.li + [ El.a + ~at:[At.href target] + [ El.txt name ] ]) in + + (* Wait for a click on an existing page in order to sent the associated + event. + + We compose the resulting event with both : + - the home button + - the list for all the pages presents in the sidebar *) + + let redirect_handler = + (module Load_page.M : State.Event with type t = Load_page.M.t ) in + + let redirect_event = Note.E.select + (( Evr.on_el + Ev.click + (fun _ -> State.E (None, redirect_handler)) + home_button + ) :: ( + List.map2 stored_pages pages + ~f:(fun name el -> + Evr.on_el + Ev.click + (fun _ -> State.E ((Some name), redirect_handler)) + el ))) in + + let childs = + [ home_button + ; add_button + ; export_button + ; load_button + ; delete_button + ; El.button + ~at:At.[class' (Jstr.v "action-button")] + [ El.i + [] + ~at:At.[ class' (Jstr.v "fa") + ; class' (Jstr.v "fa-2x") + ; class' (Jstr.v "fa-cog") ] + ] + ; El.hr () + ; El.ul + pages + ] in + + let () = El.append_children element childs in + + Some ( + Note.E.select + [ delete_event + ; redirect_event + ; add_event + ; export_event + ]) diff --git a/editor/actions/export.ml b/editor/actions/export.ml new file mode 100755 index 0000000..a97eeac --- /dev/null +++ b/editor/actions/export.ml @@ -0,0 +1,46 @@ +module Js = Js_of_ocaml.Js + +module M = struct + + type t = unit + + let update + : t -> State.t -> State.t + = fun _ state -> + + (* Save this as a json element. The text may contains UTF-16 characters, + which will raise an error in the btoa function. + + As an easy solution, we convert them into UTF-8 through the native + OCaml representation of string. + *) + let json = State.Storage.to_json () + |> Jstr.to_string (* Encode into UTF-8 *) + |> Obj.magic (* Then type the element again as a string. *) + in + Elements.Transfert.send + ~mime_type:(Jstr.v "application/json") + ~filename:(Jstr.v "export.json") + json; + + (* The function does not actually update the state, and return it + unchanged *) + state + +end + +(** Create a new element *) +let create + : unit -> State.event Note.event + = fun () -> + let title = Jstr.v "Confirmation" + and message = Jstr.v "Exporter les notes" in + let ev = Elements.Popup.create + ~title + ~form:(Some (Forms.Validation.create message )) + in + Note.E.map + (fun v -> State.E + ( v + , (module M : State.Event with type t = M.t ))) + (Note.E.Option.on_some ev) diff --git a/editor/dune b/editor/dune index 8f2e3d1..6b13228 100755 --- a/editor/dune +++ b/editor/dune @@ -3,15 +3,11 @@ (libraries brr brr.note - elements - js_lib prosemirror blog - application state plugins - forms - actions + editor_actions ) (modes js) (preprocess (pps js_of_ocaml-ppx)) diff --git a/editor/editor.ml b/editor/editor.ml index 79ad54a..c80b426 100755 --- a/editor/editor.ml +++ b/editor/editor.ml @@ -2,6 +2,8 @@ open Brr module PM = Prosemirror module Js = Js_of_ocaml.Js +module Actions = Editor_actions + (** Create a new editor view [build_view element state] will create the editor and attach it to [element]. @@ -131,9 +133,7 @@ let app id content = (State.E ( () , (module Store:State.Event with type t = Store.t)))) editor - ; Note.E.map (fun ev -> ev) btn_events.Actions.delete - ; Note.E.map (fun ev -> ev) btn_events.Actions.add - ; Note.E.map (fun v -> v) btn_events.Actions.redirect + ; btn_events ]) in let () = diff --git a/editor/forms/delete_page.ml b/editor/forms/delete_page.ml deleted file mode 100755 index 37b1c32..0000000 --- a/editor/forms/delete_page.ml +++ /dev/null @@ -1,18 +0,0 @@ -open Brr -open Note - -type t = unit - -let create - : unit -> t Note.signal * El.t - = fun () -> - let state = S.const () in - - let message = begin - let open Jstr in - - (v "La page sera définitivement supprimée") - end in - - ( state - , El.txt message ) diff --git a/editor/forms/delete_page.mli b/editor/forms/delete_page.mli deleted file mode 100755 index 0a3d9f9..0000000 --- a/editor/forms/delete_page.mli +++ /dev/null @@ -1,5 +0,0 @@ -type t = unit - -val create - : unit -> t Note.signal * Brr.El.t - diff --git a/editor/forms/dune b/editor/forms/dune index 124ce01..5aaf249 100755 --- a/editor/forms/dune +++ b/editor/forms/dune @@ -3,10 +3,6 @@ (libraries brr brr.note - elements - js_lib - blog - application state ) (preprocess (pps js_of_ocaml-ppx)) diff --git a/editor/forms/validation.ml b/editor/forms/validation.ml new file mode 100755 index 0000000..cda7db8 --- /dev/null +++ b/editor/forms/validation.ml @@ -0,0 +1,13 @@ +open Brr +open Note + +type t = unit + +let create + : Jstr.t -> t Note.signal * El.t + = fun message -> + let state = S.const () in + + + ( state + , El.txt message ) diff --git a/editor/forms/validation.mli b/editor/forms/validation.mli new file mode 100755 index 0000000..680b3ed --- /dev/null +++ b/editor/forms/validation.mli @@ -0,0 +1,5 @@ +type t = unit + +val create + : Jstr.t -> t Note.signal * Brr.El.t + diff --git a/editor/state/storage.ml b/editor/state/storage.ml index b0c00de..a790a9d 100755 --- a/editor/state/storage.ml +++ b/editor/state/storage.ml @@ -1,4 +1,5 @@ open Brr +open StdLabels module Js = Js_of_ocaml.Js let storage_key = (Jstr.v "editor") @@ -136,3 +137,23 @@ let get_ids in add_element [] items + +let save_for_id + : Jstr.t option -> 'a Js.t + = fun id -> + let element = load id in + object%js + val title = element##.title + val content = element##.content + val date = element##.date + val id = id + end + +let to_json + : unit -> Jstr.t + = fun () -> + let keys = get_ids () in + let pages = List.map ~f:(fun id -> save_for_id (Some id)) keys in + (* Also add the home page *) + let pages = Array.of_list @@ (save_for_id None)::pages in + Brr.Json.encode (Jv.Id.to_jv pages) diff --git a/editor/state/storage.mli b/editor/state/storage.mli index 5b7e0a0..50e164e 100755 --- a/editor/state/storage.mli +++ b/editor/state/storage.mli @@ -34,3 +34,6 @@ val delete (** Collect all the keys to the existing pages *) val get_ids : unit -> Jstr.t list + +val to_json + : unit -> Jstr.t -- cgit v1.2.3