Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic URL Encoding for scratchpad text state #1441

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ echo-html-dir:
@echo $(HTML_DIR)

serve:
cd $(HTML_DIR); python3 -m http.server 8000
cd $(HTML_DIR); python3 -m http.server 8000 --bind 127.0.0.1

serve2:
cd $(HTML_DIR); python3 -m http.server 8001
Expand Down
1 change: 1 addition & 0 deletions hazel.opam

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion hazel.opam.locked

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/haz3lcore/zipper/PersistentZipper.re
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ let persist = (zipper: Zipper.t) => {
};
};

let persist_text_only = (s: string) => {
{zipper: "", backup_text: s};
};

let unpersist = (persisted: t) =>
try(Sexplib.Sexp.of_string(persisted.zipper) |> Zipper.t_of_sexp) {
| _ =>
Expand Down
2 changes: 2 additions & 0 deletions src/haz3lcore/zipper/Zipper.re
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ let serialize = (z: t): string => {
sexp_of_t(z) |> Sexplib.Sexp.to_string;
};

let to_sexp = (z: t): Sexplib.Sexp.t => sexp_of_t(z);

let deserialize = (data: string): t => {
Sexplib.Sexp.of_string(data) |> t_of_sexp;
};
Expand Down
3 changes: 3 additions & 0 deletions src/haz3lweb/Store.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
open Util;

// A generic key-value store for saving/loading data to/from local storage

type key =
Expand Down Expand Up @@ -31,6 +32,8 @@ module F =
) => {
include STORE_KIND;

let key_string = key_to_string(key);

let serialize = (data: t) => {
data |> sexp_of_t |> Sexplib.Sexp.to_string;
};
Expand Down
53 changes: 50 additions & 3 deletions src/haz3lweb/view/ScratchMode.re
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,33 @@ module StoreDocumentation =
let default = () => Init.startup.documentation;
});

module Store =
Store.F({
module Store = {
include Store.F({
[@deriving (show({with_path: false}), sexp, yojson)]
type t = (int, list(CellEditor.Model.persistent));
let key = Store.Scratch;
let default = () => Init.startup.scratch;
});

// Override the load function
let load = (): t => {
switch (JsUtil.QueryParams.get_param(key_string)) {
| Some(data) =>
let decompressed = StringUtil.decompress(data);
let zip = Haz3lcore.Printer.zipper_of_string(decompressed);
switch (zip) {
| Some(zip) => (0, [PersistentZipper.persist(zip)])
| None => init()
};
| None =>
switch (JsUtil.get_localstore(key_string)) {
| None => init()
| Some(data) => deserialize(data, default())
}
};
};
};
Comment on lines +70 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This blows away all the scratch buffers a user currently has. Not sure how we want to handle it. We could create a new scratch buffer or maybe highjack the active one?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my ideal solution here is for the URL to encode some arbitrary partial state -- some of which could be a named scratchpad added to the editor state -- maybe the default names would be protected to prevent malicious links deleting existing work.

Alternatively (and more easily), we could have some "visitor" mode in which your state isn't persisted through traditional methods, and some option to merge the visitor state with your local state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the easiest thing is to just add a third section under scratch and documentation that only shows up if you're using the share url.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol... you're totally right, that's way easier


module Update = {
open Updated;
[@deriving (show({with_path: false}), sexp, yojson)]
Expand All @@ -76,6 +95,7 @@ module Update = {
| ResetCurrent
| InitImportScratchpad([@opaque] Js_of_ocaml.Js.t(Js_of_ocaml.File.file))
| FinishImportScratchpad(option(string))
| Encode
| Export;

let export_scratch_slide = (model: Model.t): unit => {
Expand All @@ -88,6 +108,19 @@ module Update = {
);
};

let encode_scratch_slide = (model: Model.t): unit => {
let zip =
(model.current |> List.nth(model.scratchpads) |> snd).editor.editor.
state.
zipper;
let compressed = StringUtil.compress(Printer.to_string_basic(zip));
JsUtil.QueryParams.set_param(
// would like to use Store.key_to_string(Store.Scratch) here, but it's shadowed?
"SAVE_SCRATCH",
compressed,
);
};

let update =
(
~schedule_action,
Expand Down Expand Up @@ -144,6 +177,9 @@ module Update = {
| Export =>
export_scratch_slide(model);
model |> Updated.return_quiet;
| Encode =>
encode_scratch_slide(model);
model |> Updated.return_quiet;
};
};

Expand Down Expand Up @@ -272,6 +308,13 @@ module View = {
~tooltip="Export Scratchpad",
);

let encode_button =
Widgets.button_named(
Icons.export,
_ => inject(Encode),
~tooltip="Encode Scratchpad",
);

let import_button =
Widgets.file_select_button_named(
"import-scratchpad",
Expand All @@ -286,7 +329,11 @@ module View = {
);

let file_group_scratch =
NutMenu.item_group(~inject, "File", [export_button, import_button]);
NutMenu.item_group(
~inject,
"File",
[export_button, encode_button, import_button],
);

let reset_button =
Widgets.button_named(
Expand Down
64 changes: 64 additions & 0 deletions src/util/JsUtil.re
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
open Js_of_ocaml;
open Virtual_dom.Vdom;
open Js_of_ocaml.Url;

let get_elem_by_id = id => {
let doc = Dom_html.document;
Expand Down Expand Up @@ -190,3 +191,66 @@ module Fragment = {
Url.Current.get() |> Option.map(fragment_of_url);
};
};

module QueryParams = {
let get_arguments = (url: Url.url): list((string, string)) =>
switch (url) {
| Http({hu_arguments, _}) => hu_arguments
| Https({hu_arguments, _}) => hu_arguments
| File({fu_arguments, _}) => fu_arguments
};

let set_arguments = (url: Url.url, args: list((string, string))): Url.url =>
switch (url) {
| Http(u) => Http({...u, hu_arguments: args})
| Https(u) => Https({...u, hu_arguments: args})
| File(u) => File({...u, fu_arguments: args})
};

let get_param = (name: string) => {
let q_opt =
Url.Current.get()
|> Option.map(url =>
url |> get_arguments |> List.find_opt(((k, _)) => k == name)
);
switch (q_opt) {
| Some(Some((_, v))) => Some(v)
| _ => None
};
};

let set_param = (name: string, value: string) => {
Url.Current.get()
|> Option.iter(url => {
let args =
get_arguments(url)
|> List.filter(((k, _)) => k != name)
|> List.cons((name, value));

let new_url = set_arguments(url, args);
let href = Url.string_of_url(new_url);

Dom_html.window##.history##pushState(
Js.null,
Js.string(""),
Js.some(Js.string(href)),
);
});
};

let remove_param = (name: string) =>
Url.Current.get()
|> Option.iter(url => {
let args =
get_arguments(url) |> List.filter(((k, _)) => k != name);

let new_url = set_arguments(url, args);
let href = Url.string_of_url(new_url);

Dom_html.window##.history##pushState(
Js.null,
Js.string(""),
Js.some(Js.string(href)),
);
});
};
14 changes: 14 additions & 0 deletions src/util/StringUtil.re
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,17 @@ let unescape_linebreaks: string => string =
Re.Str.global_replace(Re.Str.regexp("\\\\n"), "\n");

let trim_leading = Re.Str.global_replace(Re.Str.regexp("\n[ ]*"), "\n");

let compress = (s: string): string => {
let js_string = "encodeURIComponent(`" ++ s ++ "`)";
let result =
Js_of_ocaml.Js.Unsafe.eval_string(js_string) |> Js_of_ocaml.Js.to_string;
result;
};

let decompress = (s: string): string => {
let js_string = "decodeURIComponent(`" ++ s ++ "`)";
let result =
Js_of_ocaml.Js.Unsafe.eval_string(js_string) |> Js_of_ocaml.Js.to_string;
result;
};
Loading