From 99890979bb451530a4078427227cdecc926ced2f Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Thu, 13 Jun 2024 13:14:20 -0400 Subject: [PATCH 01/77] init --- src/haz3lschool/Exercise.re | 1 + src/haz3lweb/UpdateAction.re | 6 +++++- src/haz3lweb/view/Cell.re | 21 +++++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 86fdd4ce99..4b2e48a010 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -81,6 +81,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type pos = + // | Title | Prelude | CorrectImpl | YourTestsValidation diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 17856fff80..6ebd39e6bb 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -89,7 +89,8 @@ type t = | Assistant(agent_action) | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) - | UpdateResult(ModelResults.t); + | UpdateResult(ModelResults.t) + | UpdateTitle(string); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -150,6 +151,7 @@ let is_edit: t => bool = | Assistant(AcceptSuggestion) | Reset => true | UpdateResult(_) + | UpdateTitle(_) | SwitchEditor(_) | ExportPersistentData | Save @@ -205,6 +207,7 @@ let reevaluate_post_update: t => bool = | UpdateExplainThisModel(_) | ExportPersistentData | UpdateResult(_) + | UpdateTitle(_) | SwitchEditor(_) | DebugConsole(_) | TAB @@ -249,6 +252,7 @@ let should_scroll_to_caret = } | Assistant(Prompt(_)) | UpdateResult(_) + | UpdateTitle(_) | ToggleStepper(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 818e69837b..1bd699a00b 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -332,11 +332,28 @@ let panel = (~classes=[], content, ~footer: option(t)) => { ); }; -let title_cell = title => { +let title_cell = (~inject, ~init_title) => { + let handle_input = (evt, _) => { + let newTitle = Id.to_string(evt##.target##.value); + let event = [ + inject(UpdateTitle(newTitle)) + ]; + Virtual_dom.Vdom.Effect.Many(event); + }; + simple_cell_view([ div( ~attr=Attr.class_("title-cell"), - [div(~attr=Attr.class_("title-text"), [text(title)])], + [ + input( + ~attr=Attr.many([ + Attr.class_("title-text"), + Attr.value(init_title), + Attr.on_input(handle_input), + ]), + [], + ), + ], ), ]); }; From 1c53e11e68e75187e22e2fcca804bf036a99b58d Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 22 Jun 2024 09:52:27 -0400 Subject: [PATCH 02/77] title-box added --- src/haz3lschool/Exercise.re | 1 - src/haz3lweb/Log.re | 1 + src/haz3lweb/Update.re | 15 ++++++++++ src/haz3lweb/UpdateAction.re | 7 ++++- src/haz3lweb/view/Cell.re | 50 +++++++++++++++++++++---------- src/haz3lweb/view/ExerciseMode.re | 3 +- src/haz3lweb/view/Page.re | 3 +- 7 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 4b2e48a010..86fdd4ce99 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -81,7 +81,6 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type pos = - // | Title | Prelude | CorrectImpl | YourTestsValidation diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index 83876619ef..b49ae90a39 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -30,6 +30,7 @@ let is_action_logged: UpdateAction.t => bool = | Redo | MoveToNextHole(_) | UpdateResult(_) + | UpdateTitle(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index e9d4f46068..13600dff2f 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -245,6 +245,14 @@ let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => Ok(model); }; +/* + let update_title = + (model: Cell.title_model, start: bool): Result.t(Cell.title_model) => { + let model = start ? {...model, editing: true} : {...model, editing: false}; + Ok(model); + }; + */ + let switch_scratch_slide = (editors: Editors.t, ~instructor_mode, idx: int): option(Editors.t) => switch (editors) { @@ -508,6 +516,13 @@ let rec apply = let results = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); + | UpdateTitle(_) => + /* let new_title = + switch (action) { + | Start => "old title" + | Finish(updated_title) => updated_title + } */ + Ok(model) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 6ebd39e6bb..9ec080ce68 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -55,6 +55,11 @@ type benchmark_action = | Start | Finish; +[@deriving (show({with_path: false}), sexp, yojson)] +type titleAction = + | Start + | Finish(string); + [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -90,7 +95,7 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdateTitle(string); + | UpdateTitle(titleAction); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 1bd699a00b..4ea742953f 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -332,27 +332,45 @@ let panel = (~classes=[], content, ~footer: option(t)) => { ); }; -let title_cell = (~inject, ~init_title) => { - let handle_input = (evt, _) => { - let newTitle = Id.to_string(evt##.target##.value); - let event = [ - inject(UpdateTitle(newTitle)) - ]; - Virtual_dom.Vdom.Effect.Many(event); - }; +type title_model = { + title: string, + editing: bool, +}; +let title_cell = (~inject: UpdateAction.t => 'a, ~model: title_model) => { + let handle_double_click = _ => { + // Change display from div block to text-box + //Virtual_dom.Vdom.Effect.() + inject( + UpdateTitle(Start), + ); + }; + let handle_input = (_, new_title) => { + //Virtual_dom.Vdom.Effect.() + inject(UpdateTitle(Finish(new_title))); + }; simple_cell_view([ div( ~attr=Attr.class_("title-cell"), [ - input( - ~attr=Attr.many([ - Attr.class_("title-text"), - Attr.value(init_title), - Attr.on_input(handle_input), - ]), - [], - ), + model.editing + ? input( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.value(model.title), + Attr.on_input(handle_input), + ]), + [], + ) + : div( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.on_double_click(handle_double_click), + ]), + [text(model.title)], + ), ], ), ]); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 52302e7493..094e2d5f5d 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -74,7 +74,8 @@ let view = ); }; - let title_view = Cell.title_cell(eds.title); + let init_model: Cell.title_model = {title: eds.title, editing: true}; + let title_view = Cell.title_cell(~inject, ~model=init_model); let prompt_view = Cell.narrative_cell( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index fe4e1f43ba..49f1f92544 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -12,8 +12,7 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { Effect.( switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore - | Some(action) => - Many([Prevent_default, Stop_propagation, inject(action)]) + | Some(action) => Many([Prevent_default, inject(action)]) } ); [ From 63ccab70ed8b5bca3ab34c13c93c43df7425e43f Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 22 Jun 2024 10:56:03 -0400 Subject: [PATCH 03/77] added ability to change display between div/text-box depending on instructor mode --- src/haz3lweb/Update.re | 7 +------ src/haz3lweb/UpdateAction.re | 9 ++------- src/haz3lweb/view/Cell.re | 29 ++++++----------------------- src/haz3lweb/view/ExerciseMode.re | 8 ++++++-- src/haz3lweb/view/Page.re | 5 +++-- 5 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 13600dff2f..6e0ea7d50d 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -517,12 +517,7 @@ let rec apply = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); | UpdateTitle(_) => - /* let new_title = - switch (action) { - | Start => "old title" - | Finish(updated_title) => updated_title - } */ - Ok(model) + Ok({...model, editors: Exercises.}) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 9ec080ce68..2731e5cd43 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -54,12 +54,7 @@ type set_meta = type benchmark_action = | Start | Finish; - -[@deriving (show({with_path: false}), sexp, yojson)] -type titleAction = - | Start - | Finish(string); - + [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -95,7 +90,7 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdateTitle(titleAction); + | UpdateTitle(string); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 4ea742953f..6657036716 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -332,44 +332,27 @@ let panel = (~classes=[], content, ~footer: option(t)) => { ); }; -type title_model = { - title: string, - editing: bool, -}; - -let title_cell = (~inject: UpdateAction.t => 'a, ~model: title_model) => { - let handle_double_click = _ => { - // Change display from div block to text-box - //Virtual_dom.Vdom.Effect.() - inject( - UpdateTitle(Start), - ); - }; +let title_cell = (~inject: UpdateAction.t => 'a, ~title: string, ~flag: bool) => { let handle_input = (_, new_title) => { - //Virtual_dom.Vdom.Effect.() - inject(UpdateTitle(Finish(new_title))); + inject(UpdateTitle(new_title)); }; simple_cell_view([ div( ~attr=Attr.class_("title-cell"), [ - model.editing + flag ? input( ~attr= Attr.many([ Attr.class_("title-text"), - Attr.value(model.title), + Attr.value(title), Attr.on_input(handle_input), ]), [], ) : div( - ~attr= - Attr.many([ - Attr.class_("title-text"), - Attr.on_double_click(handle_double_click), - ]), - [text(model.title)], + ~attr=Attr.class_("title-text"), + [text(title)], ), ], ), diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 094e2d5f5d..551e460a69 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -74,8 +74,12 @@ let view = ); }; - let init_model: Cell.title_model = {title: eds.title, editing: true}; - let title_view = Cell.title_cell(~inject, ~model=init_model); + let title_view = + Cell.title_cell( + ~inject, + ~title=eds.title, + ~flag=settings.instructor_mode, + ); let prompt_view = Cell.narrative_cell( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 49f1f92544..a45fe7b76a 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -12,11 +12,12 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { Effect.( switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore - | Some(action) => Many([Prevent_default, inject(action)]) + | Some(action) => + Many([/* Prevent_default, Stop_propagation, */ inject(action)]) } ); [ - Attr.on_keypress(_ => Effect.Prevent_default), + // Attr.on_keypress(_ => Effect.Prevent_default), Attr.on_keyup(key_handler(~inject, ~dir=KeyUp)), Attr.on_keydown(key_handler(~inject, ~dir=KeyDown)), /* safety handler in case mousedown overlay doesn't catch it */ From c52269e019124f96556d767e7360c91e49f83351 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sun, 23 Jun 2024 21:22:36 -0400 Subject: [PATCH 04/77] added functionality to switch between an edit state and finalized state while in instructor mode --- src/haz3lweb/Editors.re | 16 ++++++++++ src/haz3lweb/Init.ml | 1 + src/haz3lweb/Settings.re | 2 ++ src/haz3lweb/Update.re | 19 ++++++++++-- src/haz3lweb/UpdateAction.re | 18 ++++++++--- src/haz3lweb/view/Cell.re | 27 ----------------- src/haz3lweb/view/ExerciseMode.re | 50 +++++++++++++++++++++++++++---- 7 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 95a1d81dbe..20233f7b74 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -128,6 +128,22 @@ let set_instructor_mode = (editors: t, instructor_mode: bool): t => ) }; +let update_exercise_title = + (editors: t, new_title: string, instructor_mode: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, _) => + Exercises( + n, + specs, + Exercise.state_of_spec( + {...List.nth(specs, n), title: new_title}, + ~instructor_mode, + ), + ) + }; + let reset_nth_slide = (n, slides) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Init.ml b/src/haz3lweb/Init.ml index 84116e2fe6..4b9dbf5dd9 100644 --- a/src/haz3lweb/Init.ml +++ b/src/haz3lweb/Init.ml @@ -26,6 +26,7 @@ let startup : PersistentData.t = async_evaluation = false; context_inspector = false; instructor_mode = true; + editing_title = false; benchmark = false; mode = Documentation; explainThis = diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index d64917b0ab..3f68041d24 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -22,6 +22,8 @@ type t = { async_evaluation: bool, context_inspector: bool, instructor_mode: bool, + editing_title: bool, + // editing_module: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 6e0ea7d50d..7160c545f5 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -174,6 +174,13 @@ let update_settings = instructor_mode: !settings.instructor_mode, }, }; + | EditingTitle => { + ...model, + settings: { + ...settings, + editing_title: !settings.editing_title, + }, + } | Mode(mode) => { ...model, settings: { @@ -516,8 +523,16 @@ let rec apply = let results = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); - | UpdateTitle(_) => - Ok({...model, editors: Exercises.}) + | UpdateTitle(new_title) => + Model.save_and_return({ + ...model, + editors: + Editors.update_exercise_title( + model.editors, + new_title, + model.settings.instructor_mode, + ), + }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 2731e5cd43..2748190805 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -25,6 +25,7 @@ type settings_action = | Benchmark | ContextInspector | InstructorMode + | EditingTitle | Evaluation(evaluation_settings_action) | ExplainThis(ExplainThisModel.Settings.action) | Mode(Settings.mode); @@ -54,7 +55,13 @@ type set_meta = type benchmark_action = | Start | Finish; - + +// To-do: Use this to update either title or model +[@deriving (show({with_path: false}), sexp, yojson)] +type edit_action = + | Title + | Model; + [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -127,6 +134,7 @@ let is_edit: t => bool = | Benchmark | ContextInspector | InstructorMode + | EditingTitle | Evaluation(_) => false } | SetMeta(meta_action) => @@ -149,9 +157,9 @@ let is_edit: t => bool = | FinishImportScratchpad(_) | ResetCurrentEditor | Assistant(AcceptSuggestion) + | UpdateTitle(_) | Reset => true | UpdateResult(_) - | UpdateTitle(_) | SwitchEditor(_) | ExportPersistentData | Save @@ -188,6 +196,7 @@ let reevaluate_post_update: t => bool = | Elaborate | Dynamics | InstructorMode + | EditingTitle | Mode(_) => true } | SetMeta(meta_action) => @@ -198,6 +207,7 @@ let reevaluate_post_update: t => bool = | FontMetrics(_) => false } | Assistant(AcceptSuggestion) => true + | UpdateTitle(_) | Assistant(Prompt(_)) => false | MoveToNextHole(_) | Save @@ -207,7 +217,6 @@ let reevaluate_post_update: t => bool = | UpdateExplainThisModel(_) | ExportPersistentData | UpdateResult(_) - | UpdateTitle(_) | SwitchEditor(_) | DebugConsole(_) | TAB @@ -241,6 +250,7 @@ let should_scroll_to_caret = | Benchmark | ContextInspector | InstructorMode + | EditingTitle | Evaluation(_) => false } | SetMeta(meta_action) => @@ -252,8 +262,8 @@ let should_scroll_to_caret = } | Assistant(Prompt(_)) | UpdateResult(_) - | UpdateTitle(_) | ToggleStepper(_) + | UpdateTitle(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true | FinishImportScratchpad(_) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 6657036716..5f65a4c61e 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -332,33 +332,6 @@ let panel = (~classes=[], content, ~footer: option(t)) => { ); }; -let title_cell = (~inject: UpdateAction.t => 'a, ~title: string, ~flag: bool) => { - let handle_input = (_, new_title) => { - inject(UpdateTitle(new_title)); - }; - simple_cell_view([ - div( - ~attr=Attr.class_("title-cell"), - [ - flag - ? input( - ~attr= - Attr.many([ - Attr.class_("title-text"), - Attr.value(title), - Attr.on_input(handle_input), - ]), - [], - ) - : div( - ~attr=Attr.class_("title-text"), - [text(title)], - ), - ], - ), - ]); -}; - /* An editor view that is not selectable or editable, * and does not show error holes or test results. * Used in Docs to display the header example */ diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 551e460a69..6d20d778d6 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -74,12 +74,50 @@ let view = ); }; - let title_view = - Cell.title_cell( - ~inject, - ~title=eds.title, - ~flag=settings.instructor_mode, - ); + let title_view = { + Cell.simple_cell_view([ + div( + ~attr=Attr.class_("title-cell"), + [ + settings.instructor_mode + ? settings.editing_title + ? input( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.value(eds.title), + Attr.id("title-input"), + Attr.on_keydown(evt => + if (evt##.keyCode === 13) { + let new_title = Obj.magic(evt##.target)##.value; + let update_events = [ + inject(Set(EditingTitle)), + inject(UpdateTitle(new_title)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + } else { + // This is placeholder until I figure out how to "do nothing" + inject( + FinishImportAll(None), + ); + } + ), + ]), + [], + ) + : div( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.on_double_click(_ => inject(Set(EditingTitle))), + ]), + [text(eds.title)], + ) + : div(~attr=Attr.class_("title-text"), [text(eds.title)]), + ], + ), + ]); + }; let prompt_view = Cell.narrative_cell( From 52bbf77fc062ad216b744d37220cfcc4c6ee7b3d Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 24 Jun 2024 12:51:42 -0400 Subject: [PATCH 05/77] added functionality to disable (set to read only) any editors while user is editing title, also automated swapping between focus on title-box/editors --- src/haz3lschool/Exercise.re | 18 ++++++++++++++++++ src/haz3lweb/Editors.re | 8 ++++++++ src/haz3lweb/Main.re | 2 +- src/haz3lweb/Update.re | 9 ++++++--- src/haz3lweb/view/Page.re | 10 +++++++--- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 86fdd4ce99..10a62b4294 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -465,6 +465,24 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let set_editing_title = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; + }, + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 20233f7b74..8985120924 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -128,6 +128,14 @@ let set_instructor_mode = (editors: t, instructor_mode: bool): t => ) }; +let set_editing_title = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_title(exercise, editing)) + }; + let update_exercise_title = (editors: t, new_title: string, instructor_mode: bool): t => switch (editors) { diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index e1268431af..4f8eba5908 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -122,7 +122,7 @@ module App = { print_endline("Saving..."); schedule_action(Update.Save); }; - if (scroll_to_caret.contents) { + if (scroll_to_caret.contents && !model.settings.editing_title) { scroll_to_caret := false; JsUtil.scroll_cursor_into_view_if_needed(); }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 7160c545f5..01554bf625 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -174,13 +174,16 @@ let update_settings = instructor_mode: !settings.instructor_mode, }, }; - | EditingTitle => { + | EditingTitle => + let editing = !settings.editing_title; + { ...model, + editors: Editors.set_editing_title(model.editors, editing), settings: { ...settings, - editing_title: !settings.editing_title, + editing_title: editing, }, - } + }; | Mode(mode) => { ...model, settings: { diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index a45fe7b76a..479201e112 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -6,6 +6,7 @@ open Node; let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { let get_selection = (model: Model.t): string => model.editors |> Editors.get_editor |> Printer.to_string_selection; + let get_settings = (model: Model.t): Settings.t => model.settings; let key_handler = (~inject, ~dir: Key.dir, evt: Js.t(Dom_html.keyboardEvent)) : Effect.t(unit) => @@ -13,11 +14,12 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore | Some(action) => - Many([/* Prevent_default, Stop_propagation, */ inject(action)]) + get_settings(model).editing_title + ? Many([inject(action)]) + : Many([Prevent_default, Stop_propagation, inject(action)]) } ); - [ - // Attr.on_keypress(_ => Effect.Prevent_default), + let attrs = [ Attr.on_keyup(key_handler(~inject, ~dir=KeyUp)), Attr.on_keydown(key_handler(~inject, ~dir=KeyDown)), /* safety handler in case mousedown overlay doesn't catch it */ @@ -46,6 +48,8 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { inject(UpdateAction.Paste(pasted_text)); }), ]; + model.settings.editing_title + ? attrs : attrs @ [Attr.on_keypress(_ => Effect.Prevent_default)]; }; let main_view = From bde06c2e1bc4e2cc160f9b9fde5f791974db8f56 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 24 Jun 2024 14:33:30 -0400 Subject: [PATCH 06/77] added disabling of editing_title when switching between exercise slides, this also solves issue of text-box with incorrect value persisting across exercise slides when switching while editing --- src/haz3lweb/Update.re | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 01554bf625..d29fed769b 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -385,9 +385,11 @@ let rec apply = Model.save_and_return({...model, editors}); | SwitchScratchSlide(n) => let instructor_mode = model.settings.instructor_mode; - switch (switch_scratch_slide(model.editors, ~instructor_mode, n)) { + let editors = Editors.set_editing_title(model.editors, false); + let settings = {...model.settings, editing_title: false}; + switch (switch_scratch_slide(editors, ~instructor_mode, n)) { | None => Error(FailedToSwitch) - | Some(editors) => Model.save_and_return({...model, editors}) + | Some(editors) => Model.save_and_return({...model, editors, settings}) }; | SwitchDocumentationSlide(name) => switch (Editors.switch_example_slide(model.editors, name)) { @@ -527,7 +529,7 @@ let rec apply = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); | UpdateTitle(new_title) => - Model.save_and_return({ + Ok({ ...model, editors: Editors.update_exercise_title( From 80ec61131439b9c41a74d9f8205bc1b01e870b4c Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 24 Jun 2024 14:49:55 -0400 Subject: [PATCH 07/77] updating module name as well now --- src/haz3lweb/Editors.re | 2 +- src/haz3lweb/Settings.re | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 8985120924..ecb4f55c5f 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -146,7 +146,7 @@ let update_exercise_title = n, specs, Exercise.state_of_spec( - {...List.nth(specs, n), title: new_title}, + {...List.nth(specs, n), title: new_title, module_name: new_title}, ~instructor_mode, ), ) diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index 3f68041d24..8ec57a6720 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -23,7 +23,6 @@ type t = { context_inspector: bool, instructor_mode: bool, editing_title: bool, - // editing_module: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, From 5edf1c90573735ddbfe407cbefbe1900532c1fe2 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 24 Jun 2024 19:45:54 -0400 Subject: [PATCH 08/77] added an edit symbol --- src/haz3lweb/Main.re | 2 +- src/haz3lweb/Update.re | 8 -------- src/haz3lweb/view/ExerciseMode.re | 13 +++++++------ src/haz3lweb/view/Icons.re | 8 ++++++++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 4f8eba5908..5509564032 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -122,7 +122,7 @@ module App = { print_endline("Saving..."); schedule_action(Update.Save); }; - if (scroll_to_caret.contents && !model.settings.editing_title) { + if (scroll_to_caret.contents && !model.settings.instructor_mode) { scroll_to_caret := false; JsUtil.scroll_cursor_into_view_if_needed(); }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index d29fed769b..9e55ed917c 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -255,14 +255,6 @@ let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => Ok(model); }; -/* - let update_title = - (model: Cell.title_model, start: bool): Result.t(Cell.title_model) => { - let model = start ? {...model, editing: true} : {...model, editing: false}; - Ok(model); - }; - */ - let switch_scratch_slide = (editors: Editors.t, ~instructor_mode, idx: int): option(Editors.t) => switch (editors) { diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 6d20d778d6..9f3481d6e0 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -106,12 +106,13 @@ let view = [], ) : div( - ~attr= - Attr.many([ - Attr.class_("title-text"), - Attr.on_double_click(_ => inject(Set(EditingTitle))), - ]), - [text(eds.title)], + ~attr=Attr.many([Attr.class_("title-text")]), + [ + text(eds.title), + Widgets.button(Icons.pencil, _ => + inject(Set(EditingTitle)) + ), + ], ) : div(~attr=Attr.class_("title-text"), [text(eds.title)]), ], diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 1067286891..36f9cdf7d7 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -211,3 +211,11 @@ let backpack = "m438.25 148.18 41.09-6.3125v-34.773l7.9062-28.441s-37.945 17.387-48.996 34.766c-11.062 17.387-15.816 26.867-15.816 34.766 0 7.9062 15.816-0.003907 15.816-0.003907z", ], ); + +let pencil = + simple_icon( + ~view="0 0 512 512", + [ + "M403.914,0L54.044,349.871L0,512l162.128-54.044L512,108.086L403.914,0z M295.829,151.319l21.617,21.617L110.638,379.745 l-21.617-21.617L295.829,151.319z M71.532,455.932l-15.463-15.463l18.015-54.043l51.491,51.491L71.532,455.932z M153.871,422.979 l-21.617-21.617l206.809-206.809l21.617,21.617L153.871,422.979z M382.297,194.555l-64.852-64.852l21.617-21.617l64.852,64.852 L382.297,194.555z M360.679,86.468l43.234-43.235l64.853,64.853l-43.235,43.234L360.679,86.468z", + ], + ); From 294f669cfb64eca465defe2b0f31e59e5dff0759 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 25 Jun 2024 14:38:02 -0400 Subject: [PATCH 09/77] updated UI with pencil icon wobble and positioning --- src/haz3lweb/view/ExerciseMode.re | 12 ++++++++---- src/haz3lweb/www/style.css | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 9f3481d6e0..f0f943566a 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -77,7 +77,7 @@ let view = let title_view = { Cell.simple_cell_view([ div( - ~attr=Attr.class_("title-cell"), + ~attr=Attr.many([Attr.class_("title-cell")]), [ settings.instructor_mode ? settings.editing_title @@ -86,7 +86,6 @@ let view = Attr.many([ Attr.class_("title-text"), Attr.value(eds.title), - Attr.id("title-input"), Attr.on_keydown(evt => if (evt##.keyCode === 13) { let new_title = Obj.magic(evt##.target)##.value; @@ -109,8 +108,13 @@ let view = ~attr=Attr.many([Attr.class_("title-text")]), [ text(eds.title), - Widgets.button(Icons.pencil, _ => - inject(Set(EditingTitle)) + div( + ~attr=Attr.class_("pencil-icon"), + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingTitle)) + ), + ], ), ], ) diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index b4d299f19b..fa0ac1aa24 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -714,12 +714,27 @@ select { .title-cell { padding-left: 1em; + display: flex; + align-items: center; } .title-cell .title-text { font-size: 1.5rem; font-weight: bold; color: var(--light-text-color); + flex-grow: 1; + display: flex; + align-items: center; +} + +.title-cell .pencil-icon { + margin-left: 0.5em; + cursor: pointer; + color: #7a6219; +} + +.title-cell .pencil-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; } .cell-prompt { From 968fe1cd1679fb3a28d23ef6e406c20b1cd8a2ad Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Wed, 26 Jun 2024 18:48:49 -0400 Subject: [PATCH 10/77] fixed bug with resetting code in editors; fixed bug with not saving when switching between exercise slides; added confirm and cancel edit buttons for finalizing title updates (or cancelling them), this also fixes a bug with pressing enter to confirm where it would also press enter in the respective code editor where the cursor was located --- src/haz3lschool/Exercise.re | 8 ++++ src/haz3lweb/Editors.re | 12 ++---- src/haz3lweb/Update.re | 14 +++---- src/haz3lweb/view/ExerciseMode.re | 63 +++++++++++++++++++------------ src/haz3lweb/view/Icons.re | 16 ++++++++ src/haz3lweb/www/style.css | 12 ++++-- 6 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 10a62b4294..d972d524f2 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -483,6 +483,14 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let update_exercise_title = ({eds, _} as state: state, new_title: string) => { + ...state, + eds: { + ...eds, + title: new_title, + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index ecb4f55c5f..97b0abff00 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -136,19 +136,15 @@ let set_editing_title = (editors: t, editing: bool): t => Exercises(n, specs, Exercise.set_editing_title(exercise, editing)) }; -let update_exercise_title = - (editors: t, new_title: string, instructor_mode: bool): t => +let update_exercise_title = (editors: t, new_title: string): t => switch (editors) { | Scratch(_) | Documentation(_) => editors - | Exercises(n, specs, _) => + | Exercises(n, specs, exercise) => Exercises( n, - specs, - Exercise.state_of_spec( - {...List.nth(specs, n), title: new_title, module_name: new_title}, - ~instructor_mode, - ), + ListUtil.update_nth(n, specs, spec => {{...spec, title: new_title}}), + Exercise.update_exercise_title(exercise, new_title), ) }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 9e55ed917c..2a402be46e 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -166,12 +166,15 @@ let update_settings = } | InstructorMode => let new_mode = !settings.instructor_mode; + let editors = Editors.set_editing_title(model.editors, false); + let editors = Editors.set_instructor_mode(editors, new_mode); { ...model, - editors: Editors.set_instructor_mode(model.editors, new_mode), + editors, settings: { ...settings, instructor_mode: !settings.instructor_mode, + editing_title: false, }, }; | EditingTitle => @@ -521,14 +524,9 @@ let rec apply = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); | UpdateTitle(new_title) => - Ok({ + Model.save_and_return({ ...model, - editors: - Editors.update_exercise_title( - model.editors, - new_title, - model.settings.instructor_mode, - ), + editors: Editors.update_exercise_title(model.editors, new_title), }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index f0f943566a..d2e7d67c7b 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -74,6 +74,18 @@ let view = ); }; + let update_title = _ => { + let new_title = + Obj.magic( + Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("title-input-box")), + )##.value; + let update_events = [ + inject(Set(EditingTitle)), + inject(UpdateTitle(new_title)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }; + let title_view = { Cell.simple_cell_view([ div( @@ -81,35 +93,38 @@ let view = [ settings.instructor_mode ? settings.editing_title - ? input( - ~attr= - Attr.many([ - Attr.class_("title-text"), - Attr.value(eds.title), - Attr.on_keydown(evt => - if (evt##.keyCode === 13) { - let new_title = Obj.magic(evt##.target)##.value; - let update_events = [ - inject(Set(EditingTitle)), - inject(UpdateTitle(new_title)), - ]; - Virtual_dom.Vdom.Effect.Many(update_events); - } else { - // This is placeholder until I figure out how to "do nothing" - inject( - FinishImportAll(None), - ); - } - ), - ]), - [], + ? div( + ~attr=Attr.many([Attr.class_("title-edit")]), + [ + input( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.id("title-input-box"), + Attr.value(eds.title), + ]), + [], + ), + div( + ~attr=Attr.class_("edit-icon"), + [Widgets.button(Icons.confirm, update_title)], + ), + div( + ~attr=Attr.class_("edit-icon"), + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingTitle)) + ), + ], + ), + ], ) : div( - ~attr=Attr.many([Attr.class_("title-text")]), + ~attr=Attr.many([Attr.class_("title-edit")]), [ text(eds.title), div( - ~attr=Attr.class_("pencil-icon"), + ~attr=Attr.class_("edit-icon"), [ Widgets.button(Icons.pencil, _ => inject(Set(EditingTitle)) diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 36f9cdf7d7..668a8a9dd6 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -219,3 +219,19 @@ let pencil = "M403.914,0L54.044,349.871L0,512l162.128-54.044L512,108.086L403.914,0z M295.829,151.319l21.617,21.617L110.638,379.745 l-21.617-21.617L295.829,151.319z M71.532,455.932l-15.463-15.463l18.015-54.043l51.491,51.491L71.532,455.932z M153.871,422.979 l-21.617-21.617l206.809-206.809l21.617,21.617L153.871,422.979z M382.297,194.555l-64.852-64.852l21.617-21.617l64.852,64.852 L382.297,194.555z M360.679,86.468l43.234-43.235l64.853,64.853l-43.235,43.234L360.679,86.468z", ], ); + +let confirm = + simple_icon( + ~view="0 0 32 32", + [ + "m16 0c8.836556 0 16 7.163444 16 16s-7.163444 16-16 16-16-7.163444-16-16 7.163444-16 16-16zm0 2c-7.7319865 0-14 6.2680135-14 14s6.2680135 14 14 14 14-6.2680135 14-14-6.2680135-14-14-14zm6.6208153 9.8786797c.3905243.3905242.3905243 1.0236892 0 1.4142135l-7.0710678 7.0710678c-.3626297.3626297-.9344751.3885319-1.3269928.0777064l-.0872208-.0777064-4.24264068-4.2426407c-.39052429-.3905242-.39052429-1.0236892 0-1.4142135.39052428-.3905243 1.02368928-.3905243 1.41421358 0l3.5348268 3.5348268 6.3646681-6.3632539c.3905243-.3905243 1.0236893-.3905243 1.4142136 0z", + ], + ); + +let cancel = + simple_icon( + ~view="0 0 32 32", + [ + "m16 0c8.836556 0 16 7.163444 16 16s-7.163444 16-16 16-16-7.163444-16-16 7.163444-16 16-16zm0 2c-7.7319865 0-14 6.2680135-14 14s6.2680135 14 14 14 14-6.2680135 14-14-6.2680135-14-14-14zm4.2426407 9.7573593c.3905243.3905243.3905243 1.0236893 0 1.4142136l-2.8284271 2.8284271 2.8284271 2.8284271c.3905243.3905243.3905243 1.0236893 0 1.4142136s-1.0236893.3905243-1.4142136 0l-2.8284271-2.8284271-2.8284271 2.8284271c-.3905243.3905243-1.0236893.3905243-1.4142136 0s-.3905243-1.0236893 0-1.4142136l2.8284271-2.8284271-2.8284271-2.8284271c-.3905243-.3905243-.3905243-1.0236893 0-1.4142136s1.0236893-.3905243 1.4142136 0l2.8284271 2.8284271 2.8284271-2.8284271c.3905243-.3905243 1.0236893-.3905243 1.4142136 0z", + ], + ); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index fa0ac1aa24..4b87fa7d2a 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -722,18 +722,24 @@ select { font-size: 1.5rem; font-weight: bold; color: var(--light-text-color); +} + +.title-cell .title-edit { + font-size: 1.5rem; + font-weight: bold; + color: var(--light-text-color); flex-grow: 1; display: flex; align-items: center; } -.title-cell .pencil-icon { +.title-edit .edit-icon { margin-left: 0.5em; cursor: pointer; - color: #7a6219; + fill: #7a6219; } -.title-cell .pencil-icon:hover { +.title-edit .edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; } From 6ffc5cfffd8b5f74b487f9c6926acd51316fab90 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Thu, 27 Jun 2024 13:42:02 -0400 Subject: [PATCH 11/77] added UI for creating and deleting buggy implementations (the buttons are non-functional still) --- src/haz3lweb/view/Cell.re | 91 ++++++++++++++++++++++- src/haz3lweb/view/ExerciseMode.re | 118 +++++++++++++----------------- src/haz3lweb/view/Icons.re | 20 +++++ src/haz3lweb/www/style.css | 31 +++++--- 4 files changed, 183 insertions(+), 77 deletions(-) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 5f65a4c61e..63da5fd795 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -307,7 +307,7 @@ let editor_view = div( ~attr= Attr.many([ - Attr.classes(["cell-item"]), + Attr.class_("cell-item"), Attr.on_mousedown(on_mousedown), ]), Option.to_list(caption) @ mousedown_overlay @ [code_view], @@ -332,6 +332,95 @@ let panel = (~classes=[], content, ~footer: option(t)) => { ); }; +let student_title_cell = (~title) => { + div(~attr=Attr.class_("title-text"), [text(title)]); +}; + +let update_title = (_, inject) => { + let new_title = + Obj.magic(Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("title-input-box")))##.value; + let update_events = [ + inject(UpdateAction.Set(EditingTitle)), + inject(UpdateAction.UpdateTitle(new_title)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); +}; + +let editing_title_cell = (~inject, ~title) => { + div( + ~attr=Attr.class_("title-edit"), + [ + input( + ~attr= + Attr.many([ + Attr.class_("title-text"), + Attr.id("title-input-box"), + Attr.value(title), + ]), + [], + ), + div( + ~attr=Attr.class_("instructor-edit-icon"), + [ + Widgets.button( + Icons.confirm, + update_title(_, inject), + ~tooltip="Confirm", + ), + ], + ), + div( + ~attr=Attr.class_("instructor-edit-icon"), + [ + Widgets.button( + Icons.cancel, + _ => inject(UpdateAction.Set(EditingTitle)), + ~tooltip="Cancel", + ), + ], + ), + ], + ); +}; + +let instructor_title_cell = (~inject, ~title) => { + div( + ~attr=Attr.class_("title-edit"), + [ + text(title), + div( + ~attr=Attr.class_("instructor-edit-icon"), + [ + Widgets.button( + Icons.pencil, + _ => inject(UpdateAction.Set(EditingTitle)), + ~tooltip="Edit Title", + ), + ], + ), + ], + ); +}; + +let wrong_impl_caption = (~inject, sub: string) => { + div( + ~attr=Attr.class_("wrong-impl-cell-caption"), + [ + caption("", ~rest=sub), + div( + ~attr=Attr.class_("instructor-edit-icon"), + [ + Widgets.button( + Icons.delete, + _ => inject(UpdateAction.Set(EditingTitle)), + ~tooltip="Delete Buggy Implementation", + ), + ], + ), + ], + ); +}; + /* An editor view that is not selectable or editable, * and does not show error holes or test results. * Used in Docs to display the header example */ diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index d2e7d67c7b..62a19714fc 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -66,7 +66,11 @@ let view = ~mousedown_updates=[SwitchEditor(this_pos)], ~settings, ~highlights, - ~caption=Cell.caption(caption, ~rest=?subcaption), + ~caption= + switch (this_pos) { + | HiddenBugs(_) => Cell.wrong_impl_caption(~inject, caption) + | _ => Cell.caption(caption, ~rest=?subcaption) + }, ~target_id=Exercise.show_pos(this_pos), ~test_results=ModelResult.test_results(di.result), ~footer?, @@ -74,66 +78,16 @@ let view = ); }; - let update_title = _ => { - let new_title = - Obj.magic( - Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("title-input-box")), - )##.value; - let update_events = [ - inject(Set(EditingTitle)), - inject(UpdateTitle(new_title)), - ]; - Virtual_dom.Vdom.Effect.Many(update_events); - }; - let title_view = { Cell.simple_cell_view([ div( - ~attr=Attr.many([Attr.class_("title-cell")]), + ~attr=Attr.class_("title-cell"), [ settings.instructor_mode ? settings.editing_title - ? div( - ~attr=Attr.many([Attr.class_("title-edit")]), - [ - input( - ~attr= - Attr.many([ - Attr.class_("title-text"), - Attr.id("title-input-box"), - Attr.value(eds.title), - ]), - [], - ), - div( - ~attr=Attr.class_("edit-icon"), - [Widgets.button(Icons.confirm, update_title)], - ), - div( - ~attr=Attr.class_("edit-icon"), - [ - Widgets.button(Icons.cancel, _ => - inject(Set(EditingTitle)) - ), - ], - ), - ], - ) - : div( - ~attr=Attr.many([Attr.class_("title-edit")]), - [ - text(eds.title), - div( - ~attr=Attr.class_("edit-icon"), - [ - Widgets.button(Icons.pencil, _ => - inject(Set(EditingTitle)) - ), - ], - ), - ], - ) - : div(~attr=Attr.class_("title-text"), [text(eds.title)]), + ? Cell.editing_title_cell(~inject, ~title=eds.title) + : Cell.instructor_title_cell(~inject, ~title=eds.title) + : Cell.student_title_cell(~title=eds.title), ], ), ]); @@ -231,19 +185,41 @@ let view = let wrong_impl_views = List.mapi( (i, (Exercise.{impl, _}, di)) => { - InstructorOnly( - () => - editor_view( - HiddenBugs(i), - ~caption="Wrong Implementation " ++ string_of_int(i + 1), - ~editor=impl, - ~di, - ), + editor_view( + HiddenBugs(i), + ~caption="Wrong Implementation " ++ string_of_int(i + 1), + ~editor=impl, + ~di, ) }, List.combine(eds.hidden_bugs, hidden_bugs), ); + let add_wrong_impl_view = + Cell.simple_cell_view([ + Cell.simple_cell_item([ + div( + ~attr=Attr.many([Attr.class_("wrong-impl-cell-caption")]), + [ + div( + ~attr= + Attr.many([ + Attr.class_("instructor-edit-icon"), + Attr.id("add-icon"), + ]), + [ + Widgets.button( + Icons.add, + _ => inject(UpdateAction.Set(EditingTitle)), + ~tooltip="Add Buggy Implementation", + ), + ], + ), + ], + ), + ]), + ]); + let mutation_testing_view = Always( Grading.MutationTestingReport.view( @@ -315,6 +291,18 @@ let view = ), ); + let wrong_impl_views = + InstructorOnly( + () => + Cell.simple_cell_view([ + Cell.simple_cell_item( + [Cell.caption("Wrong Implementations")] + @ wrong_impl_views + @ [add_wrong_impl_view], + ), + ]), + ); + [score_view, title_view, prompt_view] @ render_cells( settings, @@ -323,9 +311,7 @@ let view = correct_impl_view, correct_impl_ctx_view, your_tests_view, - ] - @ wrong_impl_views - @ [ + wrong_impl_views, mutation_testing_view, your_impl_view, syntax_grading_view, diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 668a8a9dd6..5455990312 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -235,3 +235,23 @@ let cancel = "m16 0c8.836556 0 16 7.163444 16 16s-7.163444 16-16 16-16-7.163444-16-16 7.163444-16 16-16zm0 2c-7.7319865 0-14 6.2680135-14 14s6.2680135 14 14 14 14-6.2680135 14-14-6.2680135-14-14-14zm4.2426407 9.7573593c.3905243.3905243.3905243 1.0236893 0 1.4142136l-2.8284271 2.8284271 2.8284271 2.8284271c.3905243.3905243.3905243 1.0236893 0 1.4142136s-1.0236893.3905243-1.4142136 0l-2.8284271-2.8284271-2.8284271 2.8284271c-.3905243.3905243-1.0236893.3905243-1.4142136 0s-.3905243-1.0236893 0-1.4142136l2.8284271-2.8284271-2.8284271-2.8284271c-.3905243-.3905243-.3905243-1.0236893 0-1.4142136s1.0236893-.3905243 1.4142136 0l2.8284271 2.8284271 2.8284271-2.8284271c.3905243-.3905243 1.0236893-.3905243 1.4142136 0z", ], ); + +let add = + simple_icon( + ~view="0 0 24 24", + [ + "M12.75 9C12.75 8.58579 12.4142 8.25 12 8.25C11.5858 8.25 11.25 8.58579 11.25 9L11.25 11.25H9C8.58579 11.25 8.25 11.5858 8.25 12C8.25 12.4142 8.58579 12.75 9 12.75H11.25V15C11.25 15.4142 11.5858 15.75 12 15.75C12.4142 15.75 12.75 15.4142 12.75 15L12.75 12.75H15C15.4142 12.75 15.75 12.4142 15.75 12C15.75 11.5858 15.4142 11.25 15 11.25H12.75V9Z", + "M12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 6.06294 17.9371 1.25 12 1.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12Z", + ], + ); + +let delete = + simple_icon( + ~view="0 0 24 24", + [ + "M10.3094 2.25002H13.6908C13.9072 2.24988 14.0957 2.24976 14.2737 2.27819C14.977 2.39049 15.5856 2.82915 15.9146 3.46084C15.9978 3.62073 16.0573 3.79961 16.1256 4.00494L16.2373 4.33984C16.2562 4.39653 16.2616 4.41258 16.2661 4.42522C16.4413 4.90933 16.8953 5.23659 17.4099 5.24964C17.4235 5.24998 17.44 5.25004 17.5001 5.25004H20.5001C20.9143 5.25004 21.2501 5.58582 21.2501 6.00004C21.2501 6.41425 20.9143 6.75004 20.5001 6.75004H3.5C3.08579 6.75004 2.75 6.41425 2.75 6.00004C2.75 5.58582 3.08579 5.25004 3.5 5.25004H6.50008C6.56013 5.25004 6.5767 5.24998 6.59023 5.24964C7.10488 5.23659 7.55891 4.90936 7.73402 4.42524C7.73863 4.41251 7.74392 4.39681 7.76291 4.33984L7.87452 4.00496C7.94281 3.79964 8.00233 3.62073 8.08559 3.46084C8.41453 2.82915 9.02313 2.39049 9.72643 2.27819C9.90445 2.24976 10.093 2.24988 10.3094 2.25002ZM9.00815 5.25004C9.05966 5.14902 9.10531 5.04404 9.14458 4.93548C9.1565 4.90251 9.1682 4.86742 9.18322 4.82234L9.28302 4.52292C9.37419 4.24941 9.39519 4.19363 9.41601 4.15364C9.52566 3.94307 9.72853 3.79686 9.96296 3.75942C10.0075 3.75231 10.067 3.75004 10.3553 3.75004H13.6448C13.9331 3.75004 13.9927 3.75231 14.0372 3.75942C14.2716 3.79686 14.4745 3.94307 14.5842 4.15364C14.605 4.19363 14.626 4.2494 14.7171 4.52292L14.8169 4.82216L14.8556 4.9355C14.8949 5.04405 14.9405 5.14902 14.992 5.25004H9.00815Z", + "M5.91509 8.45015C5.88754 8.03685 5.53016 7.72415 5.11686 7.7517C4.70357 7.77925 4.39086 8.13663 4.41841 8.54993L4.88186 15.5017C4.96736 16.7844 5.03642 17.8205 5.19839 18.6336C5.36679 19.4789 5.65321 20.185 6.2448 20.7385C6.8364 21.2919 7.55995 21.5308 8.4146 21.6425C9.23662 21.7501 10.275 21.7501 11.5606 21.75H12.4395C13.7251 21.7501 14.7635 21.7501 15.5856 21.6425C16.4402 21.5308 17.1638 21.2919 17.7554 20.7385C18.347 20.185 18.6334 19.4789 18.8018 18.6336C18.9638 17.8206 19.0328 16.7844 19.1183 15.5017L19.5818 8.54993C19.6093 8.13663 19.2966 7.77925 18.8833 7.7517C18.47 7.72415 18.1126 8.03685 18.0851 8.45015L17.6251 15.3493C17.5353 16.6971 17.4713 17.6349 17.3307 18.3406C17.1943 19.025 17.004 19.3873 16.7306 19.6431C16.4572 19.8989 16.083 20.0647 15.391 20.1552C14.6776 20.2485 13.7376 20.25 12.3868 20.25H11.6134C10.2626 20.25 9.32255 20.2485 8.60915 20.1552C7.91715 20.0647 7.54299 19.8989 7.26958 19.6431C6.99617 19.3873 6.80583 19.025 6.66948 18.3406C6.52892 17.6349 6.46489 16.6971 6.37503 15.3493L5.91509 8.45015Z", + "M9.42546 10.2538C9.83762 10.2125 10.2052 10.5133 10.2464 10.9254L10.7464 15.9254C10.7876 16.3376 10.4869 16.7051 10.0747 16.7463C9.66256 16.7875 9.29503 16.4868 9.25381 16.0747L8.75381 11.0747C8.7126 10.6625 9.01331 10.295 9.42546 10.2538Z", + "M14.5747 10.2538C14.9869 10.295 15.2876 10.6625 15.2464 11.0747L14.7464 16.0747C14.7052 16.4868 14.3376 16.7875 13.9255 16.7463C13.5133 16.7051 13.2126 16.3376 13.2538 15.9254L13.7538 10.9254C13.795 10.5133 14.1626 10.2125 14.5747 10.2538Z", + ], + ); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 4b87fa7d2a..4bb5ca9309 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -712,6 +712,27 @@ select { border-bottom-right-radius: 0.4em; } +.wrong-impl-cell-caption { + flex-grow: 1; + display: flex; + align-items: center; +} + +.instructor-edit-icon { + margin-top: 0.175em; + margin-left: 1em; + cursor: pointer; + fill: #7a6219; +} + +#add-icon { + margin-left: 0em; +} + +.instructor-edit-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; +} + .title-cell { padding-left: 1em; display: flex; @@ -733,16 +754,6 @@ select { align-items: center; } -.title-edit .edit-icon { - margin-left: 0.5em; - cursor: pointer; - fill: #7a6219; -} - -.title-edit .edit-icon:hover { - animation: wobble 0.6s ease 0s 1 normal forwards; -} - .cell-prompt { padding: 1em; font-size: 1rem; From a1e82bf946a8dbd681e2a04ce4ee6ddeb3a2aad0 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Thu, 27 Jun 2024 19:30:47 -0400 Subject: [PATCH 12/77] added functionality for creating and deleting buggy implementations; to-do: allow editing of implementation hints as they default to "no hint available" --- src/haz3lschool/Exercise.re | 41 +++++++++++++++++++++++++++++++ src/haz3lweb/Editors.re | 37 ++++++++++++++++++++++++++++ src/haz3lweb/Log.re | 2 ++ src/haz3lweb/Update.re | 10 ++++++++ src/haz3lweb/UpdateAction.re | 10 +++++++- src/haz3lweb/view/Cell.re | 7 +++--- src/haz3lweb/view/ExerciseMode.re | 4 +-- src/haz3lweb/view/Icons.re | 9 ++++--- 8 files changed, 110 insertions(+), 10 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index d972d524f2..f36fb9a97b 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -491,6 +491,47 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let add_buggy_impl = (state: state) => { + let new_buggy_impl = { + impl: Editor.init(Zipper.init()), + hint: "no hint available", + }; + let new_state = { + pos: HiddenBugs(List.length(state.eds.hidden_bugs)), + eds: { + ...state.eds, + hidden_bugs: state.eds.hidden_bugs @ [new_buggy_impl], + }, + }; + put_editor(new_state, new_buggy_impl.impl); + }; + + let delete_buggy_impl = (state: state, index: int) => { + let length = List.length(state.eds.hidden_bugs); + let flag = length > 1; + let editor_on = + flag + ? List.nth( + state.eds.hidden_bugs, + index < length - 1 ? index + 1 : index - 1, + ). + impl + : state.eds.your_tests.tests; + let position = + flag + ? HiddenBugs(index < length - 1 ? index : index - 1) + : YourTestsValidation; + let new_state = { + pos: position, + eds: { + ...state.eds, + hidden_bugs: + List.filteri((i, _) => i != index, state.eds.hidden_bugs), + }, + }; + put_editor(new_state, editor_on); + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 97b0abff00..87958300c8 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -148,6 +148,43 @@ let update_exercise_title = (editors: t, new_title: string): t => ) }; +let add_buggy_impl = (editors: t) => { + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + let new_buggy_impl = { + Exercise.impl: Zipper.init(), + hint: "no hint available", + }; + Exercises( + n, + ListUtil.update_nth(n, specs, spec => { + {...spec, hidden_bugs: spec.hidden_bugs @ [new_buggy_impl]} + }), + Exercise.add_buggy_impl(exercise), + ); + }; +}; + +let delete_buggy_impl = (editors: t, index: int) => { + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises( + n, + ListUtil.update_nth(n, specs, spec => { + { + ...spec, + hidden_bugs: List.filteri((i, _) => i != index, spec.hidden_bugs), + } + }), + Exercise.delete_buggy_impl(exercise, index), + ) + }; +}; + let reset_nth_slide = (n, slides) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index b49ae90a39..a9baafc600 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -31,6 +31,8 @@ let is_action_logged: UpdateAction.t => bool = | MoveToNextHole(_) | UpdateResult(_) | UpdateTitle(_) + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 2a402be46e..372cd62cce 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -528,6 +528,16 @@ let rec apply = ...model, editors: Editors.update_exercise_title(model.editors, new_title), }) + | AddBuggyImplementation => + Model.save_and_return({ + ...model, + editors: Editors.add_buggy_impl(model.editors), + }) + | DeleteBuggyImplementation(index) => + Model.save_and_return({ + ...model, + editors: Editors.delete_buggy_impl(model.editors, index), + }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 2748190805..2dfd3689be 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -97,7 +97,9 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdateTitle(string); + | UpdateTitle(string) + | AddBuggyImplementation + | DeleteBuggyImplementation(int); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -158,6 +160,8 @@ let is_edit: t => bool = | ResetCurrentEditor | Assistant(AcceptSuggestion) | UpdateTitle(_) + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | Reset => true | UpdateResult(_) | SwitchEditor(_) @@ -208,6 +212,8 @@ let reevaluate_post_update: t => bool = } | Assistant(AcceptSuggestion) => true | UpdateTitle(_) + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | Assistant(Prompt(_)) => false | MoveToNextHole(_) | Save @@ -264,6 +270,8 @@ let should_scroll_to_caret = | UpdateResult(_) | ToggleStepper(_) | UpdateTitle(_) + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true | FinishImportScratchpad(_) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 63da5fd795..0010dc5f4d 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -304,13 +304,14 @@ let editor_view = locked ? "locked" : "unlocked", ]), [ + div(~attr=Attr.class_("cell-item"), Option.to_list(caption)), div( ~attr= Attr.many([ Attr.class_("cell-item"), Attr.on_mousedown(on_mousedown), ]), - Option.to_list(caption) @ mousedown_overlay @ [code_view], + mousedown_overlay @ [code_view], ), ] @ (footer |> Option.to_list |> List.concat), @@ -402,7 +403,7 @@ let instructor_title_cell = (~inject, ~title) => { ); }; -let wrong_impl_caption = (~inject, sub: string) => { +let wrong_impl_caption = (~inject, sub: string, n: int) => { div( ~attr=Attr.class_("wrong-impl-cell-caption"), [ @@ -412,7 +413,7 @@ let wrong_impl_caption = (~inject, sub: string) => { [ Widgets.button( Icons.delete, - _ => inject(UpdateAction.Set(EditingTitle)), + _ => inject(UpdateAction.DeleteBuggyImplementation(n)), ~tooltip="Delete Buggy Implementation", ), ], diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 62a19714fc..7b34be2bf3 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -68,7 +68,7 @@ let view = ~highlights, ~caption= switch (this_pos) { - | HiddenBugs(_) => Cell.wrong_impl_caption(~inject, caption) + | HiddenBugs(n) => Cell.wrong_impl_caption(~inject, caption, n) | _ => Cell.caption(caption, ~rest=?subcaption) }, ~target_id=Exercise.show_pos(this_pos), @@ -210,7 +210,7 @@ let view = [ Widgets.button( Icons.add, - _ => inject(UpdateAction.Set(EditingTitle)), + _ => inject(UpdateAction.AddBuggyImplementation), ~tooltip="Add Buggy Implementation", ), ], diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 5455990312..f1a05fe2b2 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -249,9 +249,10 @@ let delete = simple_icon( ~view="0 0 24 24", [ - "M10.3094 2.25002H13.6908C13.9072 2.24988 14.0957 2.24976 14.2737 2.27819C14.977 2.39049 15.5856 2.82915 15.9146 3.46084C15.9978 3.62073 16.0573 3.79961 16.1256 4.00494L16.2373 4.33984C16.2562 4.39653 16.2616 4.41258 16.2661 4.42522C16.4413 4.90933 16.8953 5.23659 17.4099 5.24964C17.4235 5.24998 17.44 5.25004 17.5001 5.25004H20.5001C20.9143 5.25004 21.2501 5.58582 21.2501 6.00004C21.2501 6.41425 20.9143 6.75004 20.5001 6.75004H3.5C3.08579 6.75004 2.75 6.41425 2.75 6.00004C2.75 5.58582 3.08579 5.25004 3.5 5.25004H6.50008C6.56013 5.25004 6.5767 5.24998 6.59023 5.24964C7.10488 5.23659 7.55891 4.90936 7.73402 4.42524C7.73863 4.41251 7.74392 4.39681 7.76291 4.33984L7.87452 4.00496C7.94281 3.79964 8.00233 3.62073 8.08559 3.46084C8.41453 2.82915 9.02313 2.39049 9.72643 2.27819C9.90445 2.24976 10.093 2.24988 10.3094 2.25002ZM9.00815 5.25004C9.05966 5.14902 9.10531 5.04404 9.14458 4.93548C9.1565 4.90251 9.1682 4.86742 9.18322 4.82234L9.28302 4.52292C9.37419 4.24941 9.39519 4.19363 9.41601 4.15364C9.52566 3.94307 9.72853 3.79686 9.96296 3.75942C10.0075 3.75231 10.067 3.75004 10.3553 3.75004H13.6448C13.9331 3.75004 13.9927 3.75231 14.0372 3.75942C14.2716 3.79686 14.4745 3.94307 14.5842 4.15364C14.605 4.19363 14.626 4.2494 14.7171 4.52292L14.8169 4.82216L14.8556 4.9355C14.8949 5.04405 14.9405 5.14902 14.992 5.25004H9.00815Z", - "M5.91509 8.45015C5.88754 8.03685 5.53016 7.72415 5.11686 7.7517C4.70357 7.77925 4.39086 8.13663 4.41841 8.54993L4.88186 15.5017C4.96736 16.7844 5.03642 17.8205 5.19839 18.6336C5.36679 19.4789 5.65321 20.185 6.2448 20.7385C6.8364 21.2919 7.55995 21.5308 8.4146 21.6425C9.23662 21.7501 10.275 21.7501 11.5606 21.75H12.4395C13.7251 21.7501 14.7635 21.7501 15.5856 21.6425C16.4402 21.5308 17.1638 21.2919 17.7554 20.7385C18.347 20.185 18.6334 19.4789 18.8018 18.6336C18.9638 17.8206 19.0328 16.7844 19.1183 15.5017L19.5818 8.54993C19.6093 8.13663 19.2966 7.77925 18.8833 7.7517C18.47 7.72415 18.1126 8.03685 18.0851 8.45015L17.6251 15.3493C17.5353 16.6971 17.4713 17.6349 17.3307 18.3406C17.1943 19.025 17.004 19.3873 16.7306 19.6431C16.4572 19.8989 16.083 20.0647 15.391 20.1552C14.6776 20.2485 13.7376 20.25 12.3868 20.25H11.6134C10.2626 20.25 9.32255 20.2485 8.60915 20.1552C7.91715 20.0647 7.54299 19.8989 7.26958 19.6431C6.99617 19.3873 6.80583 19.025 6.66948 18.3406C6.52892 17.6349 6.46489 16.6971 6.37503 15.3493L5.91509 8.45015Z", - "M9.42546 10.2538C9.83762 10.2125 10.2052 10.5133 10.2464 10.9254L10.7464 15.9254C10.7876 16.3376 10.4869 16.7051 10.0747 16.7463C9.66256 16.7875 9.29503 16.4868 9.25381 16.0747L8.75381 11.0747C8.7126 10.6625 9.01331 10.295 9.42546 10.2538Z", - "M14.5747 10.2538C14.9869 10.295 15.2876 10.6625 15.2464 11.0747L14.7464 16.0747C14.7052 16.4868 14.3376 16.7875 13.9255 16.7463C13.5133 16.7051 13.2126 16.3376 13.2538 15.9254L13.7538 10.9254C13.795 10.5133 14.1626 10.2125 14.5747 10.2538Z", + "M12 2.75C11.0215 2.75 10.1871 3.37503 9.87787 4.24993C9.73983 4.64047 9.31134 4.84517 8.9208 4.70713C8.53026 4.56909 8.32557 4.1406 8.46361 3.75007C8.97804 2.29459 10.3661 1.25 12 1.25C13.634 1.25 15.022 2.29459 15.5365 3.75007C15.6745 4.1406 15.4698 4.56909 15.0793 4.70713C14.6887 4.84517 14.2602 4.64047 14.1222 4.24993C13.813 3.37503 12.9785 2.75 12 2.75Z", + "M2.75 6C2.75 5.58579 3.08579 5.25 3.5 5.25H20.5001C20.9143 5.25 21.2501 5.58579 21.2501 6C21.2501 6.41421 20.9143 6.75 20.5001 6.75H3.5C3.08579 6.75 2.75 6.41421 2.75 6Z", + "M5.91508 8.45011C5.88753 8.03681 5.53015 7.72411 5.11686 7.75166C4.70356 7.77921 4.39085 8.13659 4.41841 8.54989L4.88186 15.5016C4.96735 16.7844 5.03641 17.8205 5.19838 18.6336C5.36678 19.4789 5.6532 20.185 6.2448 20.7384C6.83639 21.2919 7.55994 21.5307 8.41459 21.6425C9.23663 21.75 10.2751 21.75 11.5607 21.75H12.4395C13.7251 21.75 14.7635 21.75 15.5856 21.6425C16.4402 21.5307 17.1638 21.2919 17.7554 20.7384C18.347 20.185 18.6334 19.4789 18.8018 18.6336C18.9637 17.8205 19.0328 16.7844 19.1183 15.5016L19.5818 8.54989C19.6093 8.13659 19.2966 7.77921 18.8833 7.75166C18.47 7.72411 18.1126 8.03681 18.0851 8.45011L17.6251 15.3492C17.5353 16.6971 17.4712 17.6349 17.3307 18.3405C17.1943 19.025 17.004 19.3873 16.7306 19.6431C16.4572 19.8988 16.083 20.0647 15.391 20.1552C14.6776 20.2485 13.7376 20.25 12.3868 20.25H11.6134C10.2626 20.25 9.32255 20.2485 8.60915 20.1552C7.91715 20.0647 7.54299 19.8988 7.26957 19.6431C6.99616 19.3873 6.80583 19.025 6.66948 18.3405C6.52891 17.6349 6.46488 16.6971 6.37503 15.3492L5.91508 8.45011Z", + "M9.42546 10.2537C9.83762 10.2125 10.2051 10.5132 10.2464 10.9254L10.7464 15.9254C10.7876 16.3375 10.4869 16.7051 10.0747 16.7463C9.66256 16.7875 9.29502 16.4868 9.25381 16.0746L8.75381 11.0746C8.71259 10.6625 9.0133 10.2949 9.42546 10.2537Z", + "M15.2464 11.0746C15.2876 10.6625 14.9869 10.2949 14.5747 10.2537C14.1626 10.2125 13.795 10.5132 13.7538 10.9254L13.2538 15.9254C13.2126 16.3375 13.5133 16.7051 13.9255 16.7463C14.3376 16.7875 14.7051 16.4868 14.7464 16.0746L15.2464 11.0746Z", ], ); From 7ea356a06cca5b85096b0ddda51ae7ad5d8469f4 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Wed, 10 Jul 2024 10:15:06 -0400 Subject: [PATCH 13/77] added functionality for creating and deleting buggy implementations; to-do: allow editing of implementation hints as they default to "no hint available --- Bool | 0 false | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Bool create mode 100644 false diff --git a/Bool b/Bool new file mode 100644 index 0000000000..e69de29bb2 diff --git a/false b/false new file mode 100644 index 0000000000..e69de29bb2 From 69fea7f5a3ee64783038329f1adfea81ced3c82b Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 23 Jul 2024 10:43:30 -0400 Subject: [PATCH 14/77] fixed bug with refreshing page while editing title causes code editors to be set off read-only --- src/haz3lschool/Exercise.re | 57 ++++++++++++++++++----------------- src/haz3lschool/Gradescope.re | 1 + src/haz3lweb/Export.re | 8 ++++- src/haz3lweb/Model.re | 9 +++++- src/haz3lweb/Store.re | 43 ++++++++++++++++++++------ src/haz3lweb/Update.re | 20 ++++++++++-- 6 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index d972d524f2..4259323d7c 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -524,6 +524,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { (pos, positioned_zippers): persistent_state, ~spec: spec, ~instructor_mode: bool, + ~editing_title: bool, ) : state => { let lookup = (pos, default) => @@ -549,33 +550,35 @@ module F = (ExerciseEnv: ExerciseEnv) => { ); let hidden_tests_tests = lookup(HiddenTests, spec.hidden_tests.tests); - set_instructor_mode( - { - pos, - eds: { - title: spec.title, - version: spec.version, - module_name: spec.module_name, - prompt: spec.prompt, - point_distribution: spec.point_distribution, - prelude, - correct_impl, - your_tests: { - tests: your_tests_tests, - required: spec.your_tests.required, - provided: spec.your_tests.provided, - }, - your_impl, - hidden_bugs, - hidden_tests: { - tests: hidden_tests_tests, - hints: spec.hidden_tests.hints, + let state = + set_instructor_mode( + { + pos, + eds: { + title: spec.title, + version: spec.version, + module_name: spec.module_name, + prompt: spec.prompt, + point_distribution: spec.point_distribution, + prelude, + correct_impl, + your_tests: { + tests: your_tests_tests, + required: spec.your_tests.required, + provided: spec.your_tests.provided, + }, + your_impl, + hidden_bugs, + hidden_tests: { + tests: hidden_tests_tests, + hints: spec.hidden_tests.hints, + }, + syntax_tests: spec.syntax_tests, }, - syntax_tests: spec.syntax_tests, }, - }, - instructor_mode, - ); + instructor_mode, + ); + set_editing_title(state, editing_title); }; // # Stitching @@ -1022,11 +1025,11 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> Sexplib.Sexp.to_string; }; - let deserialize_exercise = (data, ~spec, ~instructor_mode) => { + let deserialize_exercise = (data, ~spec, ~instructor_mode, ~editing_title) => { data |> Sexplib.Sexp.of_string |> persistent_state_of_sexp - |> unpersist_state(~spec, ~instructor_mode); + |> unpersist_state(~spec, ~instructor_mode, ~editing_title); }; let deserialize_exercise_export = data => { diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index c3bb2a0001..27c95c0efb 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -119,6 +119,7 @@ module Main = { persistent_state, ~spec, ~instructor_mode=true, + ~editing_title=false, ); let report = exercise |> gen_grading_report; {name, report}; diff --git a/src/haz3lweb/Export.re b/src/haz3lweb/Export.re index 0aa22384e2..e5f6961ea6 100644 --- a/src/haz3lweb/Export.re +++ b/src/haz3lweb/Export.re @@ -55,7 +55,13 @@ let import_all = (data, ~specs) => { let settings = Store.Settings.import(all.settings); Store.ExplainThisModel.import(all.explainThisModel); let instructor_mode = settings.instructor_mode; + let editing_title = settings.editing_title; Store.Scratch.import(~settings=settings.core.evaluation, all.scratch); - Store.Exercise.import(all.exercise, ~specs, ~instructor_mode); + Store.Exercise.import( + all.exercise, + ~specs, + ~instructor_mode, + ~editing_title, + ); Log.import(all.log); }; diff --git a/src/haz3lweb/Model.re b/src/haz3lweb/Model.re index 65c5d519c0..acfdc7ccd4 100644 --- a/src/haz3lweb/Model.re +++ b/src/haz3lweb/Model.re @@ -56,7 +56,12 @@ let blank = mk(Editors.Scratch(0, []), ModelResults.empty, CachedStatics.empty); let load_editors = - (~settings, ~mode: Settings.mode, ~instructor_mode: bool) + ( + ~settings, + ~mode: Settings.mode, + ~instructor_mode: bool, + ~editing_title: bool, + ) : (Editors.t, ModelResults.t) => switch (mode) { | Scratch => @@ -70,6 +75,7 @@ let load_editors = Store.Exercise.load( ~specs=ExerciseSettings.exercises, ~instructor_mode, + ~editing_title, ); (Exercises(n, specs, exercise), ModelResults.empty); }; @@ -93,6 +99,7 @@ let load = (init_model: t): t => { ~settings=settings.core.evaluation, ~mode=settings.mode, ~instructor_mode=settings.instructor_mode, + ~editing_title=settings.editing_title, ); let ui_state = init_model.ui_state; let statics = Editors.mk_statics(~settings, editors); diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 0c4ac3fe23..c57dad75dc 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -265,12 +265,20 @@ module Exercise = { exercise; }; - let load_exercise = (key, spec, ~instructor_mode): Exercise.state => { + let load_exercise = + (key, spec, ~instructor_mode, ~editing_title): Exercise.state => { let keystring = keystring_of_key(key); switch (JsUtil.get_localstore(keystring)) { | Some(data) => let exercise = - try(Exercise.deserialize_exercise(data, ~spec, ~instructor_mode)) { + try( + Exercise.deserialize_exercise( + data, + ~spec, + ~instructor_mode, + ~editing_title, + ) + ) { | _ => init_exercise(spec, ~instructor_mode) }; JsUtil.set_localstore(cur_exercise_key, keystring); @@ -299,7 +307,7 @@ module Exercise = { exercises; }; - let load = (~specs, ~instructor_mode) => { + let load = (~specs, ~instructor_mode, ~editing_title) => { switch (JsUtil.get_localstore(cur_exercise_key)) { | Some(keystring) => let key = key_of_keystring(keystring); @@ -308,7 +316,14 @@ module Exercise = { switch (JsUtil.get_localstore(keystring)) { | Some(data) => let exercise = - try(deserialize_exercise(data, ~spec, ~instructor_mode)) { + try( + deserialize_exercise( + data, + ~spec, + ~instructor_mode, + ~editing_title, + ) + ) { | _ => init_exercise(spec, ~instructor_mode) }; (n, specs, exercise); @@ -322,13 +337,22 @@ module Exercise = { // invalid current exercise key saved, load the first exercise let first_spec = List.nth(specs, 0); let first_key = Exercise.key_of(first_spec); - (0, specs, load_exercise(first_key, first_spec, ~instructor_mode)); + ( + 0, + specs, + load_exercise( + first_key, + first_spec, + ~instructor_mode, + ~editing_title, + ), + ); }; | None => init(~instructor_mode) }; }; - let prep_exercise_export = (~specs, ~instructor_mode) => { + let prep_exercise_export = (~specs, ~instructor_mode, ~editing_title) => { { cur_exercise: key_of_keystring( @@ -339,7 +363,7 @@ module Exercise = { |> List.map(spec => { let key = Exercise.key_of(spec); let exercise = - load_exercise(key, spec, ~instructor_mode) + load_exercise(key, spec, ~instructor_mode, ~editing_title) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); }), @@ -347,7 +371,7 @@ module Exercise = { }; let serialize_exercise_export = (~specs, ~instructor_mode) => { - prep_exercise_export(~specs, ~instructor_mode) + prep_exercise_export(~specs, ~instructor_mode, ~editing_title=false) |> sexp_of_exercise_export |> Sexplib.Sexp.to_string; }; @@ -356,7 +380,7 @@ module Exercise = { serialize_exercise_export(~specs, ~instructor_mode); }; - let import = (data, ~specs, ~instructor_mode) => { + let import = (data, ~specs, ~instructor_mode, ~editing_title) => { let exercise_export = data |> deserialize_exercise_export; save_exercise_key(exercise_export.cur_exercise); exercise_export.exercise_data @@ -371,6 +395,7 @@ module Exercise = { persistent_state, ~spec, ~instructor_mode, + ~editing_title, ), ~instructor_mode, ) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 2a402be46e..2ce3b32b26 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -259,7 +259,8 @@ let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => }; let switch_scratch_slide = - (editors: Editors.t, ~instructor_mode, idx: int): option(Editors.t) => + (editors: Editors.t, ~instructor_mode, ~editing_title, idx: int) + : option(Editors.t) => switch (editors) { | Documentation(_) => None | Scratch(n, _) when n == idx => None @@ -269,7 +270,13 @@ let switch_scratch_slide = | Exercises(_, specs, _) => let spec = List.nth(specs, idx); let key = Exercise.key_of(spec); - let exercise = Store.Exercise.load_exercise(key, spec, ~instructor_mode); + let exercise = + Store.Exercise.load_exercise( + key, + spec, + ~instructor_mode, + ~editing_title, + ); Some(Exercises(idx, specs, exercise)); }; @@ -382,7 +389,14 @@ let rec apply = let instructor_mode = model.settings.instructor_mode; let editors = Editors.set_editing_title(model.editors, false); let settings = {...model.settings, editing_title: false}; - switch (switch_scratch_slide(editors, ~instructor_mode, n)) { + switch ( + switch_scratch_slide( + editors, + ~instructor_mode, + ~editing_title=false, + n, + ) + ) { | None => Error(FailedToSwitch) | Some(editors) => Model.save_and_return({...model, editors, settings}) }; From 45c783ad4cc92cbf3e0f7e68eeee05bfa7afdf8f Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Fri, 2 Aug 2024 21:16:10 -0400 Subject: [PATCH 15/77] replaced local storage key system with Uuidm.t types rather than (key : string, version : int) pairs; edited title now saves on refresh --- src/haz3lschool/Exercise.re | 43 +++--- src/haz3lschool/Gradescope.re | 8 +- src/haz3lweb/Editors.re | 6 +- src/haz3lweb/Store.re | 127 ++++++++---------- src/haz3lweb/Update.re | 8 +- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 1 + .../exercises/Ex_RecursiveFibonacci.ml | 1 + 7 files changed, 88 insertions(+), 106 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 4259323d7c..182b1212f1 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -53,6 +53,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type p('code) = { + id: Id.t, title: string, version: int, module_name: string, @@ -68,15 +69,12 @@ module F = (ExerciseEnv: ExerciseEnv) => { syntax_tests, }; - [@deriving (show({with_path: false}), sexp, yojson)] - type key = (string, int); - - let key_of = p => { - (p.title, p.version); + let id_of = p => { + p.id; }; - let find_key_opt = (key, specs: list(p('code))) => { - specs |> Util.ListUtil.findi_opt(spec => key_of(spec) == key); + let find_id_opt = (id, specs: list(p('code))) => { + specs |> Util.ListUtil.findi_opt(spec => id_of(spec) == id); }; [@deriving (show({with_path: false}), sexp, yojson)] @@ -97,6 +95,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let map = (p: p('a), f: 'a => 'b): p('b) => { { + id: p.id, title: p.title, version: p.version, module_name: p.module_name, @@ -135,10 +134,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { eds, }; - let key_of_state = ({eds, _}) => key_of(eds); - [@deriving (show({with_path: false}), sexp, yojson)] - type persistent_state = (pos, list((pos, PersistentZipper.t))); + type persistent_state = (pos, list((pos, PersistentZipper.t)), string); let editor_of_state: state => Editor.t = ({pos, eds, _}) => @@ -285,6 +282,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let transition: transitionary_spec => spec = ( { + id, title, version, module_name, @@ -321,6 +319,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { {tests, hints}; }; { + id, title, version, module_name, @@ -340,6 +339,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let eds_of_spec: spec => eds = ( { + id, title, version, module_name, @@ -373,6 +373,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { {tests, hints}; }; { + id, title, version, module_name, @@ -509,24 +510,28 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let persistent_state_of_state = - ({pos, _} as state: state, ~instructor_mode: bool) => { + ({pos, eds} as state: state, ~instructor_mode: bool) => { let zippers = positioned_editors(state) |> List.filter(((pos, _)) => visible_in(pos, ~instructor_mode)) |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - (pos, zippers); + print_string("Saved Title: "); + print_endline(eds.title); + (pos, zippers, eds.title); }; let unpersist_state = ( - (pos, positioned_zippers): persistent_state, + (pos, positioned_zippers, title): persistent_state, ~spec: spec, ~instructor_mode: bool, ~editing_title: bool, ) : state => { + print_string("Loaded Title: "); + print_endline(title); let lookup = (pos, default) => if (visible_in(pos, ~instructor_mode)) { let persisted_zipper = List.assoc(pos, positioned_zippers); @@ -549,13 +554,16 @@ module F = (ExerciseEnv: ExerciseEnv) => { spec.hidden_bugs, ); let hidden_tests_tests = lookup(HiddenTests, spec.hidden_tests.tests); - + print_string("Loaded Title: "); + print_endline(title); let state = set_instructor_mode( { pos, eds: { - title: spec.title, + id: spec.id, // to-do: implement generation of id and update respective spec + // *Note: this is the only time we change the spec (upon page load) + title, version: spec.version, module_name: spec.module_name, prompt: spec.prompt, @@ -989,6 +997,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { ); let hidden_tests_tests = Zipper.next_blank(); { + id: Id.mk(), title, version: 1, module_name, @@ -1015,8 +1024,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type exercise_export = { - cur_exercise: key, - exercise_data: list((key, persistent_state)), + cur_exercise: Id.t, + exercise_data: list((Id.t, persistent_state)), }; let serialize_exercise = (exercise, ~instructor_mode) => { diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index 27c95c0efb..ada84872e6 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -36,7 +36,7 @@ type report = { }; [@deriving (sexp, yojson)] type section = { - name: string, + id: Id.t, report, }; @@ -111,8 +111,8 @@ module Main = { let hw = name_to_exercise_export(hw_path); let export_chapter = hw.exercise_data - |> List.map(~f=(((name, _) as key, persistent_state)) => { - switch (find_key_opt(key, specs)) { + |> List.map(~f=((id, persistent_state)) => { + switch (find_id_opt(id, specs)) { | Some((_n, spec)) => let exercise = unpersist_state( @@ -122,7 +122,7 @@ module Main = { ~editing_title=false, ); let report = exercise |> gen_grading_report; - {name, report}; + {id, report}; | None => failwith("Invalid spec") // | None => (key |> yojson_of_key |> Yojson.Safe.to_string, "?") } diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 97b0abff00..c96d4ff0e2 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -141,11 +141,7 @@ let update_exercise_title = (editors: t, new_title: string): t => | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises( - n, - ListUtil.update_nth(n, specs, spec => {{...spec, title: new_title}}), - Exercise.update_exercise_title(exercise, new_title), - ) + Exercises(n, specs, Exercise.update_exercise_title(exercise, new_title)) }; let reset_nth_slide = (n, slides) => { diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index c57dad75dc..942c3e03bf 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -233,51 +233,34 @@ module Exercise = { let cur_exercise_key = "CUR_EXERCISE"; - let keystring_of_key = key => { - key |> sexp_of_key |> Sexplib.Sexp.to_string; - }; - - let keystring_of = p => { - key_of(p) |> keystring_of_key; - }; - - let key_of_keystring = keystring => { - keystring |> Sexplib.Sexp.of_string |> key_of_sexp; - }; - - let save_exercise_key = key => { - JsUtil.set_localstore(cur_exercise_key, keystring_of_key(key)); + let save_exercise_id = id => { + JsUtil.set_localstore(cur_exercise_key, Id.to_string(id)); }; let save_exercise = (exercise, ~instructor_mode) => { - let key = Exercise.key_of_state(exercise); - let keystring = keystring_of_key(key); - let value = Exercise.serialize_exercise(exercise, ~instructor_mode); - JsUtil.set_localstore(keystring, value); + let keystring = Id.to_string(exercise.eds.id); + let data = Exercise.serialize_exercise(exercise, ~instructor_mode); + JsUtil.set_localstore(keystring, data); }; let init_exercise = (spec, ~instructor_mode) => { - let key = Exercise.key_of(spec); - let keystring = keystring_of_key(key); + print_endline("Initing new exercise"); + let keystring = Id.to_string(spec.id); let exercise = Exercise.state_of_spec(spec, ~instructor_mode); save_exercise(exercise, ~instructor_mode); JsUtil.set_localstore(cur_exercise_key, keystring); exercise; }; - let load_exercise = - (key, spec, ~instructor_mode, ~editing_title): Exercise.state => { - let keystring = keystring_of_key(key); + let load_exercise = (spec, ~instructor_mode, ~editing_title): Exercise.state => { + print_string("ID at load: "); + print_endline(Id.to_string(spec.id)); + let keystring = Id.to_string(spec.id); switch (JsUtil.get_localstore(keystring)) { | Some(data) => let exercise = try( - Exercise.deserialize_exercise( - data, - ~spec, - ~instructor_mode, - ~editing_title, - ) + deserialize_exercise(data, ~spec, ~instructor_mode, ~editing_title) ) { | _ => init_exercise(spec, ~instructor_mode) }; @@ -288,8 +271,7 @@ module Exercise = { }; let save = ((n, specs, exercise), ~instructor_mode) => { - let key = key_of(List.nth(specs, n)); - let keystring = keystring_of_key(key); + let keystring = Id.to_string(List.nth(specs, n).id); save_exercise(exercise, ~instructor_mode); JsUtil.set_localstore(cur_exercise_key, keystring); }; @@ -310,44 +292,41 @@ module Exercise = { let load = (~specs, ~instructor_mode, ~editing_title) => { switch (JsUtil.get_localstore(cur_exercise_key)) { | Some(keystring) => - let key = key_of_keystring(keystring); - switch (Exercise.find_key_opt(key, specs)) { - | Some((n, spec)) => - switch (JsUtil.get_localstore(keystring)) { - | Some(data) => - let exercise = - try( - deserialize_exercise( - data, - ~spec, - ~instructor_mode, - ~editing_title, - ) - ) { - | _ => init_exercise(spec, ~instructor_mode) - }; - (n, specs, exercise); + switch (Id.of_string(keystring)) { + | Some(id) => + switch (Exercise.find_id_opt(id, specs)) { + | Some((n, spec)) => + switch (JsUtil.get_localstore(keystring)) { + | Some(data) => + let exercise = + try( + Exercise.deserialize_exercise( + data, + ~spec, + ~instructor_mode, + ~editing_title, + ) + ) { + | _ => init_exercise(spec, ~instructor_mode) + }; + (n, specs, exercise); + | None => + // initialize exercise from spec + let exercise = Exercise.state_of_spec(spec, ~instructor_mode); + save_exercise(exercise, ~instructor_mode); + (n, specs, exercise); + } | None => - // initialize exercise from spec - let exercise = Exercise.state_of_spec(spec, ~instructor_mode); - save_exercise(exercise, ~instructor_mode); - (n, specs, exercise); + // invalid current exercise key saved, load the first exercise + let first_spec = List.nth(specs, 0); + ( + 0, + specs, + load_exercise(first_spec, ~instructor_mode, ~editing_title), + ); } - | None => - // invalid current exercise key saved, load the first exercise - let first_spec = List.nth(specs, 0); - let first_key = Exercise.key_of(first_spec); - ( - 0, - specs, - load_exercise( - first_key, - first_spec, - ~instructor_mode, - ~editing_title, - ), - ); - }; + | None => failwith("parse error") + } | None => init(~instructor_mode) }; }; @@ -355,15 +334,17 @@ module Exercise = { let prep_exercise_export = (~specs, ~instructor_mode, ~editing_title) => { { cur_exercise: - key_of_keystring( - Option.get(JsUtil.get_localstore(cur_exercise_key)), + Id.t_of_sexp( + Sexplib.Sexp.of_string( + Option.get(JsUtil.get_localstore(cur_exercise_key)), + ), ), exercise_data: specs |> List.map(spec => { - let key = Exercise.key_of(spec); + let key = spec.id; let exercise = - load_exercise(key, spec, ~instructor_mode, ~editing_title) + load_exercise(spec, ~instructor_mode, ~editing_title) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); }), @@ -382,10 +363,10 @@ module Exercise = { let import = (data, ~specs, ~instructor_mode, ~editing_title) => { let exercise_export = data |> deserialize_exercise_export; - save_exercise_key(exercise_export.cur_exercise); + save_exercise_id(exercise_export.cur_exercise); exercise_export.exercise_data |> List.iter(((key, persistent_state)) => { - let spec = Exercise.find_key_opt(key, specs); + let spec = Exercise.find_id_opt(key, specs); switch (spec) { | None => print_endline("Warning: saved key does not correspond to exercise") diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 2ce3b32b26..23c43cb07a 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -269,14 +269,8 @@ let switch_scratch_slide = | Exercises(_, specs, _) when idx >= List.length(specs) => None | Exercises(_, specs, _) => let spec = List.nth(specs, idx); - let key = Exercise.key_of(spec); let exercise = - Store.Exercise.load_exercise( - key, - spec, - ~instructor_mode, - ~editing_title, - ); + Store.Exercise.load_exercise(spec, ~instructor_mode, ~editing_title); Some(Exercises(idx, specs, exercise)); }; diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index ab0a0b5ee7..393c98bb51 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -4,6 +4,7 @@ let prompt = Ex_OddlyRecursive_prompt.prompt let exercise : Exercise.spec = { + id = Option.get (Id.of_string "3335e34d-d211-4332-91e2-815e9e183885"); title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index 1e95c4719d..9dfc47beda 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -4,6 +4,7 @@ let prompt = Ex_RecursiveFibonacci_prompt.prompt let exercise : Exercise.spec = { + id = Option.get (Id.of_string "12f5e34d-d211-4332-91e2-815e9e183885"); title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; From 837486687cbf0d8ad343edb36ab9fcdd1e19c9a4 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 3 Aug 2024 10:16:05 -0400 Subject: [PATCH 16/77] changed some string displays for mutation testing; still need to save and load added/deleted mutants --- Bool | 0 false | 0 src/haz3lschool/Exercise.re | 17 ++++++++--------- src/haz3lweb/Editors.re | 23 ++--------------------- src/haz3lweb/view/ExerciseMode.re | 4 ++-- 5 files changed, 12 insertions(+), 32 deletions(-) delete mode 100644 Bool delete mode 100644 false diff --git a/Bool b/Bool deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/false b/false deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 4d6b90a10d..8bacf9e04c 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -69,6 +69,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { syntax_tests, }; + type record = p(Zipper.t); + let id_of = p => { p.id; }; @@ -550,17 +552,14 @@ module F = (ExerciseEnv: ExerciseEnv) => { set_instructor_mode({pos: YourImpl, eds}, instructor_mode); }; - let persistent_state_of_state = - ({pos, eds} as state: state, ~instructor_mode: bool) => { + let persistent_state_of_state = (state: state, ~instructor_mode: bool) => { let zippers = positioned_editors(state) |> List.filter(((pos, _)) => visible_in(pos, ~instructor_mode)) |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - print_string("Saved Title: "); - print_endline(eds.title); - (pos, zippers, eds.title); + (state.pos, zippers, state.eds.title); }; let unpersist_state = @@ -571,8 +570,6 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~editing_title: bool, ) : state => { - print_string("Loaded Title: "); - print_endline(title); let lookup = (pos, default) => if (visible_in(pos, ~instructor_mode)) { let persisted_zipper = List.assoc(pos, positioned_zippers); @@ -581,6 +578,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { } else { editor_of_serialization(default); }; + print_endline("Unpersisting State Now"); let prelude = lookup(Prelude, spec.prelude); let correct_impl = lookup(CorrectImpl, spec.correct_impl); let your_tests_tests = lookup(YourTestsValidation, spec.your_tests.tests); @@ -602,8 +600,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { { pos, eds: { - id: spec.id, // to-do: implement generation of id and update respective spec - // *Note: this is the only time we change the spec (upon page load) + id: spec.id, title, version: spec.version, module_name: spec.module_name, @@ -1070,12 +1067,14 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let serialize_exercise = (exercise, ~instructor_mode) => { + print_endline("Serializing Exercise Now"); persistent_state_of_state(exercise, ~instructor_mode) |> sexp_of_persistent_state |> Sexplib.Sexp.to_string; }; let deserialize_exercise = (data, ~spec, ~instructor_mode, ~editing_title) => { + print_endline("Deserializing Exercise Now"); data |> Sexplib.Sexp.of_string |> persistent_state_of_sexp diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 8245331050..7b806dcdd3 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -149,17 +149,7 @@ let add_buggy_impl = (editors: t) => { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - let new_buggy_impl = { - Exercise.impl: Zipper.init(), - hint: "no hint available", - }; - Exercises( - n, - ListUtil.update_nth(n, specs, spec => { - {...spec, hidden_bugs: spec.hidden_bugs @ [new_buggy_impl]} - }), - Exercise.add_buggy_impl(exercise), - ); + Exercises(n, specs, Exercise.add_buggy_impl(exercise)) }; }; @@ -168,16 +158,7 @@ let delete_buggy_impl = (editors: t, index: int) => { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises( - n, - ListUtil.update_nth(n, specs, spec => { - { - ...spec, - hidden_bugs: List.filteri((i, _) => i != index, spec.hidden_bugs), - } - }), - Exercise.delete_buggy_impl(exercise, index), - ) + Exercises(n, specs, Exercise.delete_buggy_impl(exercise, index)) }; }; diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 7b34be2bf3..6248836cda 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -187,7 +187,7 @@ let view = (i, (Exercise.{impl, _}, di)) => { editor_view( HiddenBugs(i), - ~caption="Wrong Implementation " ++ string_of_int(i + 1), + ~caption="Mutant " ++ string_of_int(i + 1), ~editor=impl, ~di, ) @@ -296,7 +296,7 @@ let view = () => Cell.simple_cell_view([ Cell.simple_cell_item( - [Cell.caption("Wrong Implementations")] + [Cell.caption("Mutation Testing Suite")] @ wrong_impl_views @ [add_wrong_impl_view], ), From a2494c9e0530cecb5fd6610f9a8d1b47fdbf704a Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 3 Aug 2024 10:21:02 -0400 Subject: [PATCH 17/77] changed some string displays for mutation testing; still need to save and load added/deleted mutants --- src/haz3lweb/view/ExerciseMode.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 6248836cda..f782e4c615 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -296,7 +296,7 @@ let view = () => Cell.simple_cell_view([ Cell.simple_cell_item( - [Cell.caption("Mutation Testing Suite")] + [Cell.caption("Mutation Testing")] @ wrong_impl_views @ [add_wrong_impl_view], ), From 0ab124080e2c32669f304753ef94d5a92a54dc9e Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 3 Aug 2024 10:21:11 -0400 Subject: [PATCH 18/77] changed some string displays for mutation testing; still need to save and load added/deleted mutants --- src/haz3lweb/view/ExerciseMode.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index f782e4c615..6248836cda 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -296,7 +296,7 @@ let view = () => Cell.simple_cell_view([ Cell.simple_cell_item( - [Cell.caption("Mutation Testing")] + [Cell.caption("Mutation Testing Suite")] @ wrong_impl_views @ [add_wrong_impl_view], ), From 4c56ad50e4d881402cca0312dc573edbd35a7bf6 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 5 Aug 2024 11:39:29 -0400 Subject: [PATCH 19/77] Init --- src/haz3lweb/Log.re | 1 + src/haz3lweb/UpdateAction.re | 6 +++++- src/haz3lweb/view/Cell.re | 19 +++++++++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index cc955819c8..d44d2556b9 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -30,6 +30,7 @@ let is_action_logged: UpdateAction.t => bool = | Redo | MoveToNextHole(_) | UpdateResult(_) + | UpdatePrompt(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 922aea8d96..912313b151 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -88,7 +88,8 @@ type t = | Assistant(agent_action) | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) - | UpdateResult(ModelResults.t); + | UpdateResult(ModelResults.t) + | UpdatePrompt(string); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -149,6 +150,7 @@ let is_edit: t => bool = | Assistant(AcceptSuggestion) | Reset => true | UpdateResult(_) + | UpdatePrompt(_) | SwitchEditor(_) | ExportPersistentData | Save @@ -204,6 +206,7 @@ let reevaluate_post_update: t => bool = | UpdateExplainThisModel(_) | ExportPersistentData | UpdateResult(_) + | UpdatePrompt(_) | SwitchEditor(_) | DebugConsole(_) | TAB @@ -248,6 +251,7 @@ let should_scroll_to_caret = } | Assistant(Prompt(_)) | UpdateResult(_) + | UpdatePrompt(_) | ToggleStepper(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 59fb27eb3e..2f04d51584 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -64,11 +64,26 @@ let mousedown_handler = | (false, 3 | _) => inject(PerformAction(Select(Smart))) }; -let narrative_cell = (content: Node.t) => +let narrative_cell = (~inject, content: Node.t) => { + let handle_input = (evt, _) => { + let event = [inject(UpdatePrompt(content))]; + Virtual_dom.Vdom.Effect.Many(event); + }; div( ~attrs=[Attr.class_("cell")], - [div(~attrs=[Attr.class_("cell-chapter")], [content])], + [ + input( + ~attr= + Attr.many([ + Attr.class_("prompt-content"), + Attr.value(content), + Attr.on_input(handle_input), + ]), + [], + ), + ], ); +}; let simple_cell_item = (content: list(Node.t)) => div(~attrs=[Attr.classes(["cell-item"])], content); From 30c10357656761c8af6d0b491330f78a98282f80 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 5 Aug 2024 11:58:38 -0400 Subject: [PATCH 20/77] fixed bugs with some editors not being disabled when editing title --- src/haz3lschool/Exercise.re | 10 ++++++++++ src/haz3lweb/Store.re | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 8bacf9e04c..276ce8d3d3 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -482,6 +482,16 @@ module F = (ExerciseEnv: ExerciseEnv) => { provided: eds.your_tests.provided, }; }, + hidden_bugs: + eds.hidden_bugs + |> List.map(({impl, hint}) => { + let impl = Editor.set_read_only(impl, editing); + {impl, hint}; + }), + hidden_tests: { + let tests = Editor.set_read_only(eds.hidden_tests.tests, editing); + {tests, hints: eds.hidden_tests.hints}; + }, your_impl: Editor.set_read_only(eds.your_impl, editing), }, }; diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 942c3e03bf..8d20b6294f 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -253,8 +253,6 @@ module Exercise = { }; let load_exercise = (spec, ~instructor_mode, ~editing_title): Exercise.state => { - print_string("ID at load: "); - print_endline(Id.to_string(spec.id)); let keystring = Id.to_string(spec.id); switch (JsUtil.get_localstore(keystring)) { | Some(data) => From 0488b01bf16b30fe37d149b997241ea3474b56bb Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 5 Aug 2024 15:17:43 -0400 Subject: [PATCH 21/77] Added a prompt_action type and added a placeholder for UpdatePrompt in Update.re --- src/haz3lweb/Update.re | 1 + src/haz3lweb/UpdateAction.re | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index e9d4f46068..b91018d886 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -508,6 +508,7 @@ let rec apply = let results = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); + | UpdatePrompt(_) => Ok(model) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 912313b151..e5157a9a6d 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -1,5 +1,7 @@ open Util; open Haz3lcore; +// open Virtual_dom.Vdom; +// open Node; [@deriving (show({with_path: false}), sexp, yojson)] type evaluation_settings_action = @@ -54,6 +56,11 @@ type benchmark_action = | Start | Finish; +[@deriving (show({with_path: false}), sexp, yojson)] +type prompt_action = + | Start + | Finish(string); + [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -89,7 +96,7 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdatePrompt(string); + | UpdatePrompt(prompt_action); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] From e892a5525726b4c77468f37b44bcadb478dc2e00 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 5 Aug 2024 15:45:53 -0400 Subject: [PATCH 22/77] Changed implementation of narrative_cell --- src/haz3lweb/view/Cell.re | 42 +++++++++++++++++++++---------- src/haz3lweb/view/ExerciseMode.re | 7 +++--- src/haz3lweb/view/Page.re | 3 +-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 2f04d51584..88f8ac2be3 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -64,23 +64,39 @@ let mousedown_handler = | (false, 3 | _) => inject(PerformAction(Select(Smart))) }; -let narrative_cell = (~inject, content: Node.t) => { - let handle_input = (evt, _) => { - let event = [inject(UpdatePrompt(content))]; - Virtual_dom.Vdom.Effect.Many(event); +type prompt_model = { + content: Node.t, + editing: bool, +}; + +let narrative_cell = (~inject, ~model: prompt_model) => { + let handle_double_click = _ => { + inject(UpdatePrompt(Start)); + }; + let handle_input = (_, new_prompt) => { + inject(UpdatePrompt(Finish(new_prompt))); }; div( ~attrs=[Attr.class_("cell")], [ - input( - ~attr= - Attr.many([ - Attr.class_("prompt-content"), - Attr.value(content), - Attr.on_input(handle_input), - ]), - [], - ), + model.editing + ? input( + ~attr= + Attr.many([ + Attr.class_("prompt-content"), + Attr.value(model.content), + Attr.on_input(handle_input), + ]), + [], + ) + : div( + ~attr= + Attr.many([ + Attr.class_("prompt-content"), + Attr.on_double_click(handle_double_click), + ]), + [text(model.content)], + ), ], ); }; diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 5aa9af7176..c62ae48105 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -76,10 +76,9 @@ let view = let title_view = Cell.title_cell(eds.title); - let prompt_view = - Cell.narrative_cell( - div(~attrs=[Attr.class_("cell-prompt")], [eds.prompt]), - ); + let init_model: Cell.prompt_model = {contet: eds.prompt, editing: true}; + + let prompt_view = Cell.narrative_cell(~inject, ~model=init_model); let prelude_view = Always( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index bd856685e5..b148f46ece 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -12,8 +12,7 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { Effect.( switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore - | Some(action) => - Many([Prevent_default, Stop_propagation, inject(action)]) + | Some(action) => Many([Prevent_default, inject(action)]) } ); [ From ebdaefb335043ead08bbd35b0faafbe146c86a76 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 6 Aug 2024 11:44:30 -0400 Subject: [PATCH 23/77] reverted changes to exclusively title-editor w/ bugs resolved --- src/haz3lschool/Exercise.re | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 182b1212f1..d8c63a785d 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -480,6 +480,16 @@ module F = (ExerciseEnv: ExerciseEnv) => { provided: eds.your_tests.provided, }; }, + hidden_bugs: + eds.hidden_bugs + |> List.map(({impl, hint}) => { + let impl = Editor.set_read_only(impl, editing); + {impl, hint}; + }), + hidden_tests: { + let tests = Editor.set_read_only(eds.hidden_tests.tests, editing); + {tests, hints: eds.hidden_tests.hints}; + }, your_impl: Editor.set_read_only(eds.your_impl, editing), }, }; From 612009a9841106b99ae94d2be01c1e35cc75c439 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 6 Aug 2024 20:51:10 -0400 Subject: [PATCH 24/77] merging to use UUIDs and title-editing properties --- src/haz3lschool/Exercise.re | 46 ++++++++++++++++++++++++++++++------- src/haz3lweb/Editors.re | 4 ++-- src/haz3lweb/Update.re | 6 ++++- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 276ce8d3d3..21920855b4 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -136,8 +136,22 @@ module F = (ExerciseEnv: ExerciseEnv) => { eds, }; - [@deriving (show({with_path: false}), sexp, yojson)] - type persistent_state = (pos, list((pos, PersistentZipper.t)), string); + // [@deriving (show({with_path: false}), sexp, yojson)] + /* type persistent_state = ( + pos, + list((pos, PersistentZipper.t)), + string, + list(wrong_impl(Editor.t)), + ); */ + + type PersistentEditor = ; + + type persistent_eds = p(PersistentEditor.t); + + type persistent_state = { + pos, + persistent_eds, + }; let editor_of_state: state => Editor.t = ({pos, eds, _}) => @@ -504,7 +518,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; - let add_buggy_impl = (state: state) => { + let add_buggy_impl = (state: state, ~editing_title) => { let new_buggy_impl = { impl: Editor.init(Zipper.init()), hint: "no hint available", @@ -516,6 +530,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { hidden_bugs: state.eds.hidden_bugs @ [new_buggy_impl], }, }; + let new_state = set_editing_title(new_state, editing_title); put_editor(new_state, new_buggy_impl.impl); }; @@ -569,12 +584,24 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - (state.pos, zippers, state.eds.title); + let hints = + List.fold_right( + ( + (i, hidden_bugs: list(wrong_impl(Editor.t))), + hints: list(string), + ) => { + let hint = List.nth(hidden_bugs, i).hint; + hints @ [hint]; + }, + [(0, state.eds.hidden_bugs)], + [], + ); + (state.pos, zippers, state.eds.title, hints); }; let unpersist_state = ( - (pos, positioned_zippers, title): persistent_state, + (pos, positioned_zippers, title, hints): persistent_state, ~spec: spec, ~instructor_mode: bool, ~editing_title: bool, @@ -595,12 +622,15 @@ module F = (ExerciseEnv: ExerciseEnv) => { let your_impl = lookup(YourImpl, spec.your_impl); let (_, hidden_bugs) = List.fold_left( - ((i, hidden_bugs: list(wrong_impl(Editor.t))), {impl, hint}) => { - let impl = lookup(HiddenBugs(i), impl); + ((i, hidden_bugs: list(wrong_impl(Editor.t))), hint) => { + let persisted_zipper = + List.assoc(HiddenBugs(i), positioned_zippers); + let zipper = PersistentZipper.unpersist(persisted_zipper); + let impl = Editor.init(zipper); (i + 1, hidden_bugs @ [{impl, hint}]); }, (0, []), - spec.hidden_bugs, + hints, ); let hidden_tests_tests = lookup(HiddenTests, spec.hidden_tests.tests); print_string("Loaded Title: "); diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 7b806dcdd3..666ec64ba3 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -144,12 +144,12 @@ let update_exercise_title = (editors: t, new_title: string): t => Exercises(n, specs, Exercise.update_exercise_title(exercise, new_title)) }; -let add_buggy_impl = (editors: t) => { +let add_buggy_impl = (editors: t, ~editing_title) => { switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.add_buggy_impl(exercise)) + Exercises(n, specs, Exercise.add_buggy_impl(exercise, ~editing_title)) }; }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index b9301666a3..1fc40e49cd 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -539,7 +539,11 @@ let rec apply = | AddBuggyImplementation => Model.save_and_return({ ...model, - editors: Editors.add_buggy_impl(model.editors), + editors: + Editors.add_buggy_impl( + model.editors, + ~editing_title=model.settings.editing_title, + ), }) | DeleteBuggyImplementation(index) => Model.save_and_return({ From 73d7a5e96e3716356af3e29eaaa5da867ef8188f Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 11 Aug 2024 10:23:18 -0400 Subject: [PATCH 25/77] CHanged implementation of how prompt cell is viewed --- src/haz3lweb/Update.re | 2 +- src/haz3lweb/UpdateAction.re | 7 +------ src/haz3lweb/view/Cell.re | 18 ++++++------------ src/haz3lweb/view/ExerciseMode.re | 9 ++++++--- src/haz3lweb/view/Page.re | 4 ++-- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index b91018d886..7b17ea6a23 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -508,7 +508,7 @@ let rec apply = let results = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); - | UpdatePrompt(_) => Ok(model) + | UpdatePrompt(_) => Ok({...model, editors: Exercises.}) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index e5157a9a6d..905b4a2b6c 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -56,11 +56,6 @@ type benchmark_action = | Start | Finish; -[@deriving (show({with_path: false}), sexp, yojson)] -type prompt_action = - | Start - | Finish(string); - [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -96,7 +91,7 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdatePrompt(prompt_action); + | UpdatePrompt(string); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 88f8ac2be3..fd0456c18b 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -69,33 +69,27 @@ type prompt_model = { editing: bool, }; -let narrative_cell = (~inject, ~model: prompt_model) => { - let handle_double_click = _ => { - inject(UpdatePrompt(Start)); - }; +let narrative_cell = (~inject: UpdateAction.t => 'a, ~content: Node.t, ~flag: bool) => { let handle_input = (_, new_prompt) => { - inject(UpdatePrompt(Finish(new_prompt))); + inject(UpdatePrompt(content)); }; div( ~attrs=[Attr.class_("cell")], [ - model.editing + flag ? input( ~attr= Attr.many([ Attr.class_("prompt-content"), - Attr.value(model.content), + Attr.value(content), Attr.on_input(handle_input), ]), [], ) : div( ~attr= - Attr.many([ - Attr.class_("prompt-content"), - Attr.on_double_click(handle_double_click), - ]), - [text(model.content)], + ~attr=Attr.class_("prompt-content"), + [content], ), ], ); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index c62ae48105..1477f99070 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -76,9 +76,12 @@ let view = let title_view = Cell.title_cell(eds.title); - let init_model: Cell.prompt_model = {contet: eds.prompt, editing: true}; - - let prompt_view = Cell.narrative_cell(~inject, ~model=init_model); + let prompt_view = + Cell.narrative_cell( + ~inject, + ~title=eds.title, + ~flag=settings.instructor_mode, + ); let prelude_view = Always( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index b148f46ece..82fea6a71d 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -12,11 +12,11 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { Effect.( switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore - | Some(action) => Many([Prevent_default, inject(action)]) + | Some(action) => Many([inject(action)]) } ); [ - Attr.on_keypress(_ => Effect.Prevent_default), + // Attr.on_keypress(_ => Effect.Prevent_default), Attr.on_keyup(key_handler(~inject, ~dir=KeyUp)), Attr.on_keydown(key_handler(~inject, ~dir=KeyDown)), /* safety handler in case mousedown overlay doesn't catch it */ From a576e7aff2043798c35cf1308201844bb4ab45bd Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 11 Aug 2024 11:20:45 -0400 Subject: [PATCH 26/77] Added functionality to toggle between edit and finalized state --- src/haz3lweb/Editors.re | 18 ++++++++++++ src/haz3lweb/Init.ml | 1 + src/haz3lweb/Settings.re | 1 + src/haz3lweb/Update.re | 18 +++++++++++- src/haz3lweb/UpdateAction.re | 12 ++++++-- src/haz3lweb/view/Cell.re | 31 ++------------------ src/haz3lweb/view/ExerciseMode.re | 48 +++++++++++++++++++++++++++---- 7 files changed, 93 insertions(+), 36 deletions(-) diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 0af1a46ca3..9505eed7c6 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -1,5 +1,7 @@ open Util; open Haz3lcore; +open Virtual_dom.Vdom; +open Node; [@deriving (show({with_path: false}), sexp, yojson)] type scratch = (int, list(ScratchSlide.state)); @@ -127,6 +129,22 @@ let set_instructor_mode = (editors: t, instructor_mode: bool): t => ) }; +let update_exercise_prompt = + (editors: t, new_title: Node.t, instructor_mode: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, _) => + Exercises( + n, + specs, + Exercise.state_of_spec( + {...List.nth(specs, n), prompt: new_prompt}, + ~instructor_mode, + ), + ) + }; + let reset_nth_slide = (n, slides) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Init.ml b/src/haz3lweb/Init.ml index d3f39b9aa2..1e2d184cb0 100644 --- a/src/haz3lweb/Init.ml +++ b/src/haz3lweb/Init.ml @@ -26,6 +26,7 @@ let startup : PersistentData.t = async_evaluation = false; context_inspector = false; instructor_mode = true; + editing_prompt = false; benchmark = false; explainThis = { show = true; show_feedback = false; highlight = NoHighlight }; diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index 1481b54621..0e9af6bbc5 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -22,6 +22,7 @@ type t = { async_evaluation: bool, context_inspector: bool, instructor_mode: bool, + editing_prompt: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 7b17ea6a23..c8a31acdea 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -174,6 +174,13 @@ let update_settings = instructor_mode: !settings.instructor_mode, }, }; + | EditingPrompt => { + ...model, + settings: { + ...settings, + editing_prompt: !settings.editing_prompt, + }, + } | Mode(mode) => { ...model, settings: { @@ -508,7 +515,16 @@ let rec apply = let results = ModelResults.union((_, _a, b) => Some(b), model.results, results); Ok({...model, results}); - | UpdatePrompt(_) => Ok({...model, editors: Exercises.}) + | UpdatePrompt(new_prompt) => + Model.save_and_return({ + ...model, + editors: + Editors.update_exercise_prompt( + model.editors, + new_prompt, + model.settings.instructor_mode, + ), + }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 905b4a2b6c..364b50f94d 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -26,6 +26,7 @@ type settings_action = | Benchmark | ContextInspector | InstructorMode + | EditingPrompt | Evaluation(evaluation_settings_action) | ExplainThis(ExplainThisModel.Settings.action) | Mode(Settings.mode); @@ -56,6 +57,11 @@ type benchmark_action = | Start | Finish; +[@deriving (show({with_path: false}), sexp, yojson)] +type edit_prompt = + | Prompt + | Model; + [@deriving (show({with_path: false}), sexp, yojson)] type t = /* meta */ @@ -128,6 +134,7 @@ let is_edit: t => bool = | Benchmark | ContextInspector | InstructorMode + | EditingPrompt | Evaluation(_) => false } | SetMeta(meta_action) => @@ -150,9 +157,9 @@ let is_edit: t => bool = | FinishImportScratchpad(_) | ResetCurrentEditor | Assistant(AcceptSuggestion) + | UpdatePrompt(_) | Reset => true | UpdateResult(_) - | UpdatePrompt(_) | SwitchEditor(_) | ExportPersistentData | Save @@ -242,6 +249,7 @@ let should_scroll_to_caret = | Benchmark | ContextInspector | InstructorMode + | EditingPrompt | Evaluation(_) => false } | SetMeta(meta_action) => @@ -253,8 +261,8 @@ let should_scroll_to_caret = } | Assistant(Prompt(_)) | UpdateResult(_) - | UpdatePrompt(_) | ToggleStepper(_) + UpdatePrompt(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true | FinishImportScratchpad(_) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index fd0456c18b..dd02cac8c7 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -64,36 +64,11 @@ let mousedown_handler = | (false, 3 | _) => inject(PerformAction(Select(Smart))) }; -type prompt_model = { - content: Node.t, - editing: bool, -}; - -let narrative_cell = (~inject: UpdateAction.t => 'a, ~content: Node.t, ~flag: bool) => { - let handle_input = (_, new_prompt) => { - inject(UpdatePrompt(content)); - }; +let narrative_cell = (content: Node.t) => div( - ~attrs=[Attr.class_("cell")], - [ - flag - ? input( - ~attr= - Attr.many([ - Attr.class_("prompt-content"), - Attr.value(content), - Attr.on_input(handle_input), - ]), - [], - ) - : div( - ~attr= - ~attr=Attr.class_("prompt-content"), - [content], - ), - ], + ~attr=Attr.class_("cell"), + [div(~attr=Attr.class_("cell-chapter"), [content])], ); -}; let simple_cell_item = (content: list(Node.t)) => div(~attrs=[Attr.classes(["cell-item"])], content); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 1477f99070..07a42e8766 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -77,11 +77,49 @@ let view = let title_view = Cell.title_cell(eds.title); let prompt_view = - Cell.narrative_cell( - ~inject, - ~title=eds.title, - ~flag=settings.instructor_mode, - ); + Cell.narrative_cell([ + div( + ~attr=Attr.class_("prompt-cell"), + [ + settings.instructor_mode + ? settings.editing_prompt + ? input( + ~attr= + Attr.many([ + Attr.class_("prompt-content"), + Attr.value(eds.prompt), + Attr.id("prompt-input"), + Attr.on_keydown(evt => + if (evt##.keyCode === 13) { + let new_prompt = Obj.magic(evt##.target)##.value; + let update_events = [ + inject(Set(EditingPrompt)), + inject(UpdatePrompt(new_prompt)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + } else { + // This is placeholder until I figure out how to "do nothing" + inject( + FinishImportAll(None), + ); + } + ), + ]), + [], + ) + : div( + ~attr= + Attr.many([ + Attr.class_("prompt-content"), + Attr.on_double_click(_ => inject(Set(EditingPrompt))), + ]), + [eds.content], + ) + : div(~attr=Attr.class_("prompt-content"), [eds.content]), + ], + ), + ]); + }; let prelude_view = Always( From b0a6a1cbd9f2ea884c54acde555c4447e2621bba Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 11 Aug 2024 11:22:43 -0400 Subject: [PATCH 27/77] Style fix --- src/haz3lweb/view/Cell.re | 4 ++-- src/haz3lweb/view/ExerciseMode.re | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index dd02cac8c7..59fb27eb3e 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -66,8 +66,8 @@ let mousedown_handler = let narrative_cell = (content: Node.t) => div( - ~attr=Attr.class_("cell"), - [div(~attr=Attr.class_("cell-chapter"), [content])], + ~attrs=[Attr.class_("cell")], + [div(~attrs=[Attr.class_("cell-chapter")], [content])], ); let simple_cell_item = (content: list(Node.t)) => diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 07a42e8766..60ea5bc0a1 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -79,7 +79,7 @@ let view = let prompt_view = Cell.narrative_cell([ div( - ~attr=Attr.class_("prompt-cell"), + ~attrs=[Attr.class_("cell-prompt")], [ settings.instructor_mode ? settings.editing_prompt From 1a5e7a4939d5acfa97ac079f691bf351d9facfe6 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 17 Aug 2024 12:05:13 -0400 Subject: [PATCH 28/77] Changed type of prompt to string --- src/haz3lschool/Exercise.re | 23 ++++++++++++++++--- src/haz3lweb/Editors.re | 13 ++++++++--- src/haz3lweb/Main.re | 2 +- src/haz3lweb/Update.re | 9 +++++--- src/haz3lweb/UpdateAction.re | 5 ++-- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 2 +- .../exercises/Ex_RecursiveFibonacci.ml | 2 +- src/haz3lweb/view/ExerciseMode.re | 7 +++--- src/haz3lweb/view/Page.re | 11 ++++++--- 9 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 91b4abf1d6..5ca293ea6a 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -56,8 +56,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { title: string, version: int, module_name: string, - prompt: - [@printer (fmt, _) => Format.pp_print_string(fmt, "prompt")] [@opaque] ExerciseEnv.node, + prompt: string, point_distribution, prelude: 'code, correct_impl: 'code, @@ -465,6 +464,24 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let set_editing_prompt = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; + }, + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode @@ -971,7 +988,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { title, version: 1, module_name, - prompt: ExerciseEnv.default, + prompt: "", point_distribution, prelude, correct_impl, diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 9505eed7c6..905ff46caf 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -1,7 +1,6 @@ open Util; +// open Virtual_dom.Vdom; open Haz3lcore; -open Virtual_dom.Vdom; -open Node; [@deriving (show({with_path: false}), sexp, yojson)] type scratch = (int, list(ScratchSlide.state)); @@ -129,8 +128,16 @@ let set_instructor_mode = (editors: t, instructor_mode: bool): t => ) }; +let set_editing_prompt = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_prompt(exercise, editing)) + }; + let update_exercise_prompt = - (editors: t, new_title: Node.t, instructor_mode: bool): t => + (editors: t, new_prompt: string, instructor_mode: bool): t => switch (editors) { | Scratch(_) | Documentation(_) => editors diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index e1268431af..66ab086e81 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -122,7 +122,7 @@ module App = { print_endline("Saving..."); schedule_action(Update.Save); }; - if (scroll_to_caret.contents) { + if (scroll_to_caret.contents && !model.settings.editing_prompt) { scroll_to_caret := false; JsUtil.scroll_cursor_into_view_if_needed(); }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index c8a31acdea..54c5749abe 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -174,13 +174,16 @@ let update_settings = instructor_mode: !settings.instructor_mode, }, }; - | EditingPrompt => { + | EditingPrompt => + let editing = !settings.editing_prompt; + { ...model, + editors: Editors.set_editing_prompt(model.editors, editing), settings: { ...settings, - editing_prompt: !settings.editing_prompt, + editing_prompt: editing, }, - } + }; | Mode(mode) => { ...model, settings: { diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 364b50f94d..c5ded1e73a 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -1,7 +1,5 @@ open Util; open Haz3lcore; -// open Virtual_dom.Vdom; -// open Node; [@deriving (show({with_path: false}), sexp, yojson)] type evaluation_settings_action = @@ -196,6 +194,7 @@ let reevaluate_post_update: t => bool = | Elaborate | Dynamics | InstructorMode + | EditingPrompt | Mode(_) => true } | SetMeta(meta_action) => @@ -262,7 +261,7 @@ let should_scroll_to_caret = | Assistant(Prompt(_)) | UpdateResult(_) | ToggleStepper(_) - UpdatePrompt(_) + | UpdatePrompt(_) | StepperAction(_, StepBackward | StepForward(_)) => false | Assistant(AcceptSuggestion) => true | FinishImportScratchpad(_) diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index ab0a0b5ee7..4465e04adb 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -7,7 +7,7 @@ let exercise : Exercise.spec = title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; - prompt; + prompt = ""; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index 1e95c4719d..c5b12139db 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -7,7 +7,7 @@ let exercise : Exercise.spec = title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; - prompt; + prompt = ""; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 60ea5bc0a1..2c90781e52 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -77,7 +77,7 @@ let view = let title_view = Cell.title_cell(eds.title); let prompt_view = - Cell.narrative_cell([ + Cell.simple_cell_item([ div( ~attrs=[Attr.class_("cell-prompt")], [ @@ -111,7 +111,9 @@ let view = ~attr= Attr.many([ Attr.class_("prompt-content"), - Attr.on_double_click(_ => inject(Set(EditingPrompt))), + Attr.on_double_click(_ => + inject(Set(EditingPrompt)) + ), ]), [eds.content], ) @@ -119,7 +121,6 @@ let view = ], ), ]); - }; let prelude_view = Always( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 82fea6a71d..1e7b9c5d31 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -6,17 +6,20 @@ open Node; let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { let get_selection = (model: Model.t): string => model.editors |> Editors.get_editor |> Printer.to_string_selection; + let get_settings = (model: Model.t): Settings.t => model.settings; let key_handler = (~inject, ~dir: Key.dir, evt: Js.t(Dom_html.keyboardEvent)) : Effect.t(unit) => Effect.( switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore - | Some(action) => Many([inject(action)]) + | Some(action) => + get_settings(model).editing_title + ? Many([inject(action)]) + : Many([Prevent_default, Stop_propagation, inject(action)]) } ); - [ - // Attr.on_keypress(_ => Effect.Prevent_default), + let attrs = [ Attr.on_keyup(key_handler(~inject, ~dir=KeyUp)), Attr.on_keydown(key_handler(~inject, ~dir=KeyDown)), /* safety handler in case mousedown overlay doesn't catch it */ @@ -45,6 +48,8 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { inject(UpdateAction.Paste(pasted_text)); }), ]; + model.settings.editing_prompt + ? attrs : attrs @ [Attr.on_keypress(_ => Effect.Prevent_default)]; }; let main_view = From b76d628921d3d461af8c6b0b2fb564b73675a8e9 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 17 Aug 2024 13:49:01 -0400 Subject: [PATCH 29/77] Changed implementation of prompt_view so that it changed string to markdown --- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 2 +- .../exercises/Ex_OddlyRecursive_prompt.re | 23 ++----- .../exercises/Ex_RecursiveFibonacci.ml | 2 +- .../exercises/Ex_RecursiveFibonacci_prompt.re | 25 ++----- src/haz3lweb/view/Cell.re | 4 +- src/haz3lweb/view/ExerciseMode.re | 66 +++++++++---------- src/haz3lweb/view/Page.re | 2 +- 7 files changed, 46 insertions(+), 78 deletions(-) diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index 4465e04adb..ab0a0b5ee7 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -7,7 +7,7 @@ let exercise : Exercise.spec = title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; - prompt = ""; + prompt; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive_prompt.re b/src/haz3lweb/exercises/Ex_OddlyRecursive_prompt.re index eed88befa9..aaaafecd21 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive_prompt.re +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive_prompt.re @@ -1,20 +1,5 @@ -open Virtual_dom.Vdom; -open Node; -open ExerciseUtil; +// open Virtual_dom.Vdom; +// open Node; +// open ExerciseUtil; -let prompt = - div([ - p([ - text( - "Write a recursive function that determines whether the given integer is odd. ", - ), - ]), - p([ - code("odd(n)"), - equiv, - code("true"), - text(" iff "), - code("n"), - text(" is odd."), - ]), - ]); +let prompt = "Write a recursive function that determines whether the given integer is odd. \n `odd(n)` is equivalent to `true` iff `n` is odd."; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index c5b12139db..1e95c4719d 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -7,7 +7,7 @@ let exercise : Exercise.spec = title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; - prompt = ""; + prompt; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci_prompt.re b/src/haz3lweb/exercises/Ex_RecursiveFibonacci_prompt.re index 278dfe941a..4bf27fe4c7 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci_prompt.re +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci_prompt.re @@ -1,22 +1,5 @@ -open Virtual_dom.Vdom; -open Node; -open ExerciseUtil; +// open Virtual_dom.Vdom; +// open Node; +// open ExerciseUtil; -let prompt = - div([ - p([ - div([ - text( - "Write tests cases for, and then implement, a function, that recursively determines the nth fibonacci number.", - ), - ]), - ]), - p([ - code("fib(n)"), - equiv, - text("the "), - code("n"), - text("th fibonacci number, assuming "), - code("n >= 0."), - ]), - ]); +let prompt = "Write test cases for, and then implement, a function that recursively determines the nth Fibonacci number. \n`fib(n)` is equivalent to the `n`th Fibonacci number, assuming `n >= 0`."; diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 59fb27eb3e..a9dfa6fd07 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -64,10 +64,10 @@ let mousedown_handler = | (false, 3 | _) => inject(PerformAction(Select(Smart))) }; -let narrative_cell = (content: Node.t) => +let narrative_cell = (content: list(Node.t)) => div( ~attrs=[Attr.class_("cell")], - [div(~attrs=[Attr.class_("cell-chapter")], [content])], + [div(~attrs=[Attr.class_("cell-chapter")], content)], ); let simple_cell_item = (content: list(Node.t)) => diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 2c90781e52..dbf4fe25f7 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -76,51 +76,51 @@ let view = let title_view = Cell.title_cell(eds.title); - let prompt_view = - Cell.simple_cell_item([ + let prompt_view = { + let (msg, _) = + ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); + print_endline(eds.prompt); + Cell.narrative_cell([ div( ~attrs=[Attr.class_("cell-prompt")], [ settings.instructor_mode ? settings.editing_prompt ? input( - ~attr= - Attr.many([ - Attr.class_("prompt-content"), - Attr.value(eds.prompt), - Attr.id("prompt-input"), - Attr.on_keydown(evt => - if (evt##.keyCode === 13) { - let new_prompt = Obj.magic(evt##.target)##.value; - let update_events = [ - inject(Set(EditingPrompt)), - inject(UpdatePrompt(new_prompt)), - ]; - Virtual_dom.Vdom.Effect.Many(update_events); - } else { - // This is placeholder until I figure out how to "do nothing" - inject( - FinishImportAll(None), - ); - } - ), - ]), - [], + ~attrs=[ + Attr.class_("prompt-content"), + Attr.value(eds.prompt), + Attr.id("prompt-input"), + Attr.on_keydown(evt => + if (evt##.keyCode === 13) { + let new_prompt = Obj.magic(evt##.target)##.value; + let update_events = [ + inject(Set(EditingPrompt)), + inject(UpdatePrompt(new_prompt)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + } else { + // This is placeholder until I figure out how to "do nothing" + inject( + FinishImportAll(None), + ); + } + ), + ], + (), ) : div( - ~attr= - Attr.many([ - Attr.class_("prompt-content"), - Attr.on_double_click(_ => - inject(Set(EditingPrompt)) - ), - ]), - [eds.content], + ~attrs=[ + Attr.class_("prompt-content"), + Attr.on_double_click(_ => inject(Set(EditingPrompt))), + ], + msg, ) - : div(~attr=Attr.class_("prompt-content"), [eds.content]), + : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), ]); + }; let prelude_view = Always( diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 1e7b9c5d31..907a3d66ac 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -14,7 +14,7 @@ let handlers = (~inject: UpdateAction.t => Ui_effect.t(unit), model) => { switch (Keyboard.handle_key_event(Key.mk(dir, evt))) { | None => Ignore | Some(action) => - get_settings(model).editing_title + get_settings(model).editing_prompt ? Many([inject(action)]) : Many([Prevent_default, Stop_propagation, inject(action)]) } From 229b96923a6d2b11091627d81c8b3fcd31995ba4 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 17 Aug 2024 15:38:18 -0400 Subject: [PATCH 30/77] fixed build failure --- src/haz3lweb/view/Page.re | 9 ++++----- src/haz3lweb/www/style.css | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index afecc6a813..47e4754b8e 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -175,11 +175,10 @@ let get_selection = (model: Model.t): string => let view = (~inject: UpdateAction.t => Ui_effect.t(unit), model: Model.t) => div( - ~attrs= - Attr.[ - id("page"), - ...handlers(~inject, Editors.get_editor(model.editors), model), - ], + ~attrs=[ + Attr.id("page"), + ...handlers(~inject, Editors.get_editor(model.editors), model), + ], [ FontSpecimen.view("font-specimen"), DecUtil.filters, diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 43975d791c..02ca20c813 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -2610,7 +2610,7 @@ svg.expandable path { .em { font-style: italic; } - +ç ninja-keys { --ninja-text-color: var(--light-text-color); --ninja-accent-color: var(--selection-shadow-color); From d31a33819cd3bd6844f3dea79a011de2cc298e22 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 17 Aug 2024 19:22:56 -0400 Subject: [PATCH 31/77] build and runs locally --- src/haz3lweb/view/dec/Diag.re | 56 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/haz3lweb/view/dec/Diag.re b/src/haz3lweb/view/dec/Diag.re index 4b7a815c2e..a145c9d0e9 100644 --- a/src/haz3lweb/view/dec/Diag.re +++ b/src/haz3lweb/view/dec/Diag.re @@ -18,24 +18,22 @@ let tr_bl = (), ) => SvgUtil.Path.( - { - let (diag, junction) = - with_child_border - ? ( - L_({dx: Float.neg(short_tip_width), dy: short_tip_height}), - H_({dx: Float.neg(0.5 -. short_tip_width)}), - ) - : ( - L_({dx: Float.neg(tip_width), dy: 0.5 +. stretch_y}), - H_({dx: Float.neg(stretch_x)}), - ); - let path = - switch (hemi) { - | `North => [junction, diag] - | `South => [diag, junction] - }; - scale(s, path); - } + let (diag, junction) = + with_child_border + ? ( + L_({dx: Float.neg(short_tip_width), dy: short_tip_height}), + H_({dx: Float.neg(0.5 -. short_tip_width)}), + ) + : ( + L_({dx: Float.neg(tip_width), dy: 0.5 +. stretch_y}), + H_({dx: Float.neg(stretch_x)}), + ); + let path = + switch (hemi) { + | `North => [junction, diag] + | `South => [diag, junction] + }; + scale(s, path) ); // bottom left to top right let bl_tr = @@ -60,18 +58,16 @@ let tl_br = (), ) => SvgUtil.Path.( - { - let (diag, junction) = - with_child_border - ? ( - L_({dx: short_tip_width, dy: short_tip_height}), - H_({dx: 0.5 -. short_tip_width}), - ) - : (L_({dx: tip_width, dy: 0.5 +. stretch_y}), H_({dx: stretch_x})); - switch (hemi) { - | `North => [junction, diag] - | `South => [diag, junction] - }; + let (diag, junction) = + with_child_border + ? ( + L_({dx: short_tip_width, dy: short_tip_height}), + H_({dx: 0.5 -. short_tip_width}), + ) + : (L_({dx: tip_width, dy: 0.5 +. stretch_y}), H_({dx: stretch_x})); + switch (hemi) { + | `North => [junction, diag] + | `South => [diag, junction] } ); // bottom right to top left From f630de6c8e9a42f6285aed69f60f99395c389539 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 17 Aug 2024 19:36:01 -0400 Subject: [PATCH 32/77] css updated --- src/haz3lweb/www/style/cell.css | 19 +++++++++++++++++++ src/haz3lweb/www/style/exercise-mode.css | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index e10c38a126..0961a80216 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -62,6 +62,25 @@ color: var(--BR4); } +.title-edit .edit-icon { + margin-left: 0.5em; + cursor: pointer; + fill: #7a6219; +} + +.title-cell .title-edit { + font-size: 1.5rem; + font-weight: bold; + color: var(--light-text-color); + flex-grow: 1; + display: flex; + align-items: center; +} + +.title-edit .edit-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; +} + .cell-prompt { padding: 1em; } diff --git a/src/haz3lweb/www/style/exercise-mode.css b/src/haz3lweb/www/style/exercise-mode.css index 5eb8e2d74c..b69c7037f0 100644 --- a/src/haz3lweb/www/style/exercise-mode.css +++ b/src/haz3lweb/www/style/exercise-mode.css @@ -234,4 +234,4 @@ #main.Exercises .context-entry { max-width: fit-content; /* Correct implementation type sigs */ -} +} \ No newline at end of file From 8da0866c55aa19be0ed9c53961aad67bed11f5df Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 25 Aug 2024 14:32:39 -0400 Subject: [PATCH 33/77] Solved a bug where editing mode persisted when changing between exercises --- src/haz3lweb/Update.re | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 54c5749abe..551814a071 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -377,9 +377,11 @@ let rec apply = Model.save_and_return({...model, editors}); | SwitchScratchSlide(n) => let instructor_mode = model.settings.instructor_mode; - switch (switch_scratch_slide(model.editors, ~instructor_mode, n)) { + let editors = Editors.set_editing_prompt(model.editors, false); + let settings = {...model.settings, editing_prompt: false}; + switch (switch_scratch_slide(editors, ~instructor_mode, n)) { | None => Error(FailedToSwitch) - | Some(editors) => Model.save_and_return({...model, editors}) + | Some(editors) => Model.save_and_return({...model, editors, settings}) }; | SwitchDocumentationSlide(name) => switch (Editors.switch_example_slide(model.editors, name)) { From 0d75c7159419ef0e5a11b1c0aa3af41f17c3d755 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 25 Aug 2024 14:59:16 -0400 Subject: [PATCH 34/77] Added an edit button --- src/haz3lweb/Main.re | 2 +- src/haz3lweb/view/ExerciseMode.re | 11 +++-------- src/haz3lweb/view/Icons.re | 8 ++++++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 66ab086e81..5509564032 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -122,7 +122,7 @@ module App = { print_endline("Saving..."); schedule_action(Update.Save); }; - if (scroll_to_caret.contents && !model.settings.editing_prompt) { + if (scroll_to_caret.contents && !model.settings.instructor_mode) { scroll_to_caret := false; JsUtil.scroll_cursor_into_view_if_needed(); }; diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index dbf4fe25f7..d1084f0bfb 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -79,7 +79,8 @@ let view = let prompt_view = { let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); - print_endline(eds.prompt); + let msg = + msg @ [Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)))]; Cell.narrative_cell([ div( ~attrs=[Attr.class_("cell-prompt")], @@ -109,13 +110,7 @@ let view = ], (), ) - : div( - ~attrs=[ - Attr.class_("prompt-content"), - Attr.on_double_click(_ => inject(Set(EditingPrompt))), - ], - msg, - ) + : div(~attrs=[Attr.class_("prompt-content")], msg) : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 964480c52c..0939cb1f15 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -205,3 +205,11 @@ let backpack = "m438.25 148.18 41.09-6.3125v-34.773l7.9062-28.441s-37.945 17.387-48.996 34.766c-11.062 17.387-15.816 26.867-15.816 34.766 0 7.9062 15.816-0.003907 15.816-0.003907z", ], ); + +let pencil = + simple_icon( + ~view="0 0 512 512", + [ + "M403.914,0L54.044,349.871L0,512l162.128-54.044L512,108.086L403.914,0z M295.829,151.319l21.617,21.617L110.638,379.745 l-21.617-21.617L295.829,151.319z M71.532,455.932l-15.463-15.463l18.015-54.043l51.491,51.491L71.532,455.932z M153.871,422.979 l-21.617-21.617l206.809-206.809l21.617,21.617L153.871,422.979z M382.297,194.555l-64.852-64.852l21.617-21.617l64.852,64.852 L382.297,194.555z M360.679,86.468l43.234-43.235l64.853,64.853l-43.235,43.234L360.679,86.468z", + ], + ); From 757ef6b61af3409e604727998e5ea70f9be27c3f Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 25 Aug 2024 15:24:44 -0400 Subject: [PATCH 35/77] Implemented initial animation for edit button --- src/haz3lweb/view/ExerciseMode.re | 9 +- src/haz3lweb/www/style.css | 139 ++++++++++++++++++++++++++---- 2 files changed, 129 insertions(+), 19 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index d1084f0bfb..4116f66cfd 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -80,7 +80,13 @@ let view = let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); let msg = - msg @ [Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)))]; + msg + @ [ + div( + ~attrs=[Attr.class_("pencil-icon")], + [Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)))], + ), + ]; Cell.narrative_cell([ div( ~attrs=[Attr.class_("cell-prompt")], @@ -91,7 +97,6 @@ let view = ~attrs=[ Attr.class_("prompt-content"), Attr.value(eds.prompt), - Attr.id("prompt-input"), Attr.on_keydown(evt => if (evt##.keyCode === 13) { let new_prompt = Obj.magic(evt##.target)##.value; diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index b4d299f19b..1c42d2006d 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -220,6 +220,7 @@ svg { } @keyframes wobble { + 0%, 100% { transform: translateX(0%); @@ -311,17 +312,21 @@ body { :root::-webkit-scrollbar { width: 0.8em; } + :root::-webkit-scrollbar:horizontal { height: 0.8em; } + :root::-webkit-scrollbar-track { background-color: #fdf6e300; } + :root::-webkit-scrollbar-thumb { background-color: #e1d1a6; border-radius: 1em; border: 0.2em solid #fdf6e3; } + /* set button(top and bottom of the scrollbar) */ :root::-webkit-scrollbar-button, :root::-webkit-scrollbar-corner { @@ -333,13 +338,14 @@ body { grid-row: 2; grid-column: 1 / span 1; overflow: auto; - + } #main.Scratch, #main.Documentation { padding: 0.8em; } + #main.Exercises { padding: 1.2em 0.8em 1.2em 0.8em; display: flex; @@ -347,6 +353,7 @@ body { justify-content: space-between; gap: 1.2em; } + #main.Exercises .editor { /* TODO deprecate */ display: flex; @@ -354,10 +361,12 @@ body { justify-content: space-between; gap: 1.2em; } + #main.Scratch .cell, #main.Documentation .cell { min-width: 30em; } + #main.Exercises .cell { width: auto; } @@ -444,8 +453,9 @@ select { background-color: #bba874; border-radius: 0 0 1.3em 0; } -#top-bar .nut-menu .submenu-icon:hover + .submenu, -#top-bar .nut-menu .submenu-icon + .submenu:hover { + +#top-bar .nut-menu .submenu-icon:hover+.submenu, +#top-bar .nut-menu .submenu-icon+.submenu:hover { display: flex; } @@ -465,13 +475,16 @@ select { cursor: pointer; padding-right: 1em; } + #top-bar .nut-menu .named-menu-item:last-child { border-radius: 0 0 1.3em 0; } + #top-bar .nut-menu .named-menu-item:hover { background-color: #c7b480; color: white; } + #top-bar .nut-menu .named-menu-item .toggle-switch { margin-left: 0.5em; margin-right: 0.5em; @@ -479,15 +492,17 @@ select { #top-bar .nut-menu .top-menu-item:hover, #top-bar .nut-menu .top-menu-item:has(+ .submenu:hover) { - background-color: #bba874; /*c7b480*/ + background-color: #bba874; + /*c7b480*/ } + #top-bar .nut-menu .top-menu-item:hover svg, #top-bar .nut-menu .top-menu-item:has(+ .submenu:hover) svg { fill: var(--light-page-color); } #top-bar .nut-menu:hover, -#top-bar .nut-icon:hover + .nut-menu { +#top-bar .nut-icon:hover+.nut-menu { transform: scaleY(1); display: flex; border-radius: 0 0 1.3em 0; @@ -496,6 +511,7 @@ select { #top-bar .nut-menu .top-menu-item { position: relative; } + #top-bar .nut-menu .icon, #top-bar .nut-menu .link { display: flex; @@ -504,9 +520,11 @@ select { align-items: center; justify-content: center; } + #top-bar .nut-menu .top-menu-item:last-child { border-radius: 0 0 1.3em 0; } + #top-bar .nut-menu .top-menu-item:hover { background-color: #bba874; color: white; @@ -514,7 +532,8 @@ select { #top-bar .nut-icon { width: 2.75em; - min-width: 2.75em; /* Seems to be necessary at high zooms? */ + min-width: 2.75em; + /* Seems to be necessary at high zooms? */ height: 2.75em; display: flex; justify-content: center; @@ -577,6 +596,7 @@ select { background-color: #4fad66; border: solid 1px transparent; } + #top-bar .nut-menu .toggle-switch.active .toggle-knob { color: #4fad66; } @@ -727,6 +747,16 @@ select { font-size: 1rem; } +.cell-prompt .pencil-icon { + margin-left: 0.5em; + cursor: pointer; + fill: #7a6219; +} + +.cell-prompt .pencil-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; +} + .cell-caption { color: var(--top-bar-text); } @@ -918,21 +948,27 @@ svg.tile-selected { .code .token.Nul { color: var(--nul-text-color); } + .code .token.Any { color: var(--any-text-color); } + .code .token.Exp { color: var(--exp-text-color); } + .code .token.Pat { color: var(--pat-text-color); } + .code .token.Typ { color: var(--typ-text-color); } + .code .token.Rul { color: var(--rul-text-color); } + .code .token.TPat { color: var(--tpat-text-color); } @@ -940,21 +976,27 @@ svg.tile-selected { .tile-path.Nul { filter: url(#drop-shadow-Nul); } + .tile-path.Any { filter: url(#drop-shadow-Any); } + .tile-path.Exp { filter: url(#drop-shadow-Exp); } + .tile-path.Pat { filter: url(#drop-shadow-Pat); } + .tile-path.Typ { filter: url(#drop-shadow-Typ); } + .tile-path.Rul { filter: url(#drop-shadow-Rul); } + .tile-path.TPat { filter: url(#drop-shadow-TPat); } @@ -962,21 +1004,27 @@ svg.tile-selected { .tile-path.Nul.indicated { fill: var(--nul-bg-color); } + .tile-path.Any.indicated { fill: var(--any-bg-color); } + .tile-path.Exp.indicated { fill: var(--exp-bg-color); } + .tile-path.Pat.indicated { fill: var(--pat-bg-off-color); } + .tile-path.Typ.indicated { fill: var(--typ-bg-off-color); } + .tile-path.Rul.indicated { fill: var(--rul-bg-color); } + .tile-path.TPat.indicated { fill: var(--tpat-bg-off-color); } @@ -984,21 +1032,27 @@ svg.tile-selected { .tile-path.Nul.indicated-caret { fill: var(--nul-bg-color); } + .tile-path.Any.indicated-caret { fill: var(--any-bg-color); } + .tile-path.Exp.indicated-caret { fill: var(--exp-bg-off-color); } + .tile-path.Pat.indicated-caret { fill: var(--pat-bg-color); } + .tile-path.Typ.indicated-caret { fill: var(--typ-bg-color); } + .tile-path.Rul.indicated-caret { fill: var(--rul-bg-color); } + .tile-path.TPat.indicated-caret { fill: var(--tpat-bg-color); } @@ -1006,21 +1060,27 @@ svg.tile-selected { .tile-path.Nul.raised { filter: url(#raised-drop-shadow-Nul); } + .tile-path.Any.raised { filter: url(#raised-drop-shadow-Any); } + .tile-path.Exp.raised { filter: url(#raised-drop-shadow-Exp); } + .tile-path.Pat.raised { filter: url(#raised-drop-shadow-Pat); } + .tile-path.Typ.raised { filter: url(#raised-drop-shadow-Typ); } + .tile-path.Rul.raised { filter: url(#raised-drop-shadow-Rul); } + .tile-path.TPat.raised { filter: url(#raised-drop-shadow-TPat); } @@ -1028,21 +1088,27 @@ svg.tile-selected { .child-line.Nul { stroke: var(--nul-shadow-color); } + .child-line.Any { stroke: var(--any-shadow-color); } + .child-line.Exp { stroke: var(--exp-shadow-color); } + .child-line.Pat { stroke: var(--pat-shadow-color); } + .child-line.Typ { stroke: var(--typ-shadow-color); } + .child-line.Rul { stroke: var(--rul-shadow-color); } + .child-line.TPat { stroke: var(--tpat-shadow-color); } @@ -1050,21 +1116,27 @@ svg.tile-selected { #drop-shadow-Nul .tile-drop-shadow { flood-color: var(--nul-shadow-color); } + #drop-shadow-Any .tile-drop-shadow { flood-color: var(--any-shadow-color); } + #drop-shadow-Exp .tile-drop-shadow { flood-color: var(--exp-shadow-color); } + #drop-shadow-Pat .tile-drop-shadow { flood-color: var(--pat-shadow-color); } + #drop-shadow-Typ .tile-drop-shadow { flood-color: var(--typ-shadow-color); } + #drop-shadow-Rul .tile-drop-shadow { flood-color: var(--rul-shadow-color); } + #drop-shadow-TPat .tile-drop-shadow { flood-color: var(--tpat-shadow-color); } @@ -1072,21 +1144,27 @@ svg.tile-selected { #raised-drop-shadow-Nul .tile-drop-shadow { flood-color: var(--nul-shadow-color); } + #raised-drop-shadow-Any .tile-drop-shadow { flood-color: var(--any-shadow-color); } + #raised-drop-shadow-Exp .tile-drop-shadow { flood-color: var(--exp-shadow-color); } + #raised-drop-shadow-Pat .tile-drop-shadow { flood-color: var(--pat-shadow-color); } + #raised-drop-shadow-Typ .tile-drop-shadow { flood-color: var(--typ-shadow-color); } + #raised-drop-shadow-Rul .tile-drop-shadow { flood-color: var(--rul-shadow-color); } + #raised-drop-shadow-TPat .tile-drop-shadow { flood-color: var(--tpat-shadow-color); } @@ -1097,26 +1175,32 @@ svg.tile-selected { background-color: var(--nul-bg-color); color: var(--nul-off-color); } + .ci-header.Any { background-color: var(--any-bg-color); color: var(--any-off-color); } + .ci-header.Exp { background-color: var(--exp-bg-color); color: var(--exp-off-color); } + .ci-header.Pat { background-color: var(--pat-bg-color); color: var(--pat-off-color); } + .ci-header.Typ { background-color: var(--typ-bg-color); color: var(--typ-off-color); } + .ci-header.Rul { background-color: var(--rul-bg-color); color: var(--rul-off-color); } + .ci-header.TPat { background-color: var(--tpat-bg-color); color: var(--tpat-off-color); @@ -1128,11 +1212,13 @@ svg.tile-selected { stroke: var(--exp-text-color); fill: var(--exp-text-color); } + .caret-position-path.Pat.sibling, .caret-position-path.Pat.inner-cousin { stroke: var(--pat-text-color); fill: var(--pat-text-color); } + .caret-position-path.Typ.sibling, .caret-position-path.Typ.inner-cousin { stroke: var(--typ-text-color); @@ -1145,11 +1231,13 @@ svg.tile-selected { stroke: var(--exp-shadow-color); fill: var(--exp-shadow-color); } + .caret-position-path.Pat.anchor, .caret-position-path.Pat.current-caret-pos { stroke: var(--pat-text-color); fill: var(--pat-text-color); } + .caret-position-path.Typ.anchor, .caret-position-path.Typ.current-caret-pos { stroke: var(--typ-text-color); @@ -1242,6 +1330,7 @@ svg.tile-selected { } @keyframes blink { + from, to { opacity: 100%; @@ -1287,6 +1376,7 @@ svg.tile-selected { /* to advertise provenance display on-hover */ cursor: help; } + .typ-alias-view { color: var(--tpat-text-color); display: flex; @@ -1343,7 +1433,7 @@ svg.tile-selected { border-color: var(--err-color); } -.cursor-inspector .gamma:hover + .context-inspector, +.cursor-inspector .gamma:hover+.context-inspector, .context-inspector.visible { display: flex; } @@ -1401,6 +1491,7 @@ svg.tile-selected { scrollbar-color: #c7b480 #e4d6a6; scrollbar-width: thin; } + @supports (-moz-appearance: none) { .context-inspector { scroll-snap-type: y mandatory; @@ -1485,7 +1576,8 @@ svg.tile-selected { .bottom-bar .id-and-class .syntax-class { color: var(--top_bar_icon_fill); - color: #392f10; /*#fdf6e3;*/ + color: #392f10; + /*#fdf6e3;*/ } .bottom-bar .id-and-class .id { @@ -1554,7 +1646,7 @@ svg.tile-selected { gap: 0.75em; } -.explain-this .example + .example { +.explain-this .example+.example { border-top: 1px dotted #c7b480; padding-top: 0.75em; } @@ -1741,6 +1833,7 @@ svg.expandable path { padding: 0.5em; padding-left: 1.2em; } + .selected .cell-result { background-color: #efe7d6; } @@ -1755,6 +1848,7 @@ svg.expandable path { white-space: nowrap; font-family: "Source Code Pro", monospace; } + .cell-result .status .spinner { height: 50%; width: 60%; @@ -1764,15 +1858,18 @@ svg.expandable path { opacity: 0; transition: opacity 0.2s linear; } + .cell-result .status.pending .spinner { opacity: 0.5; transition: all 0.2s linear; } + .cell-result .status .eq { position: absolute; opacity: 1; transition: opacity 0.6s cubic-bezier(0.65, 0, 0.35, 1); } + .cell-result .status.pending .eq { opacity: 0; transition: opacity 0.6s cubic-bezier(0.65, 0, 0.35, 1); @@ -1786,10 +1883,12 @@ svg.expandable path { overflow-x: auto; */ transition: opacity 0.2s linear; } + .cell-result .result.pending { opacity: 0.3; transition: opacity 0.6s cubic-bezier(0.65, 0, 0.35, 1); } + .cell-result .result .exception { background-color: #ffa2a2; color: #7d0000; @@ -1806,6 +1905,7 @@ svg.expandable path { .cell-result .toggle-switch { mix-blend-mode: luminosity; } + .cell-result .equiv { font-family: 'Source Code Pro'; } @@ -1816,14 +1916,15 @@ svg.expandable path { border-left: 1px solid rgba(0, 0, 0, 0); } -.cell.selected + .cell-item { +.cell.selected+.cell-item { border-left: 1px solid var(--cell-selected-accent); } .result { width: 100%; } -.cell.deselected + .cell-item { + +.cell.deselected+.cell-item { border-left: 1px solid var(--top_bar_icon_fill); } @@ -1835,7 +1936,9 @@ svg.expandable path { .cell-item.cell-result .error-msg { padding: 0.25em; color: black; -}; +} + +; .cell-result .icon:hover svg { animation: wobble 0.6s ease 0s 1 normal forwards; @@ -2105,9 +2208,10 @@ svg.expandable path { background-color: #c7b480; align-items: center; } -#main > .test-percent, + +#main>.test-percent, /* TODO deprecate .editor */ -#main > .editor > .test-percent { +#main>.editor>.test-percent { position: absolute; top: 0; right: 0; @@ -2166,8 +2270,7 @@ svg.expandable path { background-color: rgb(255, 199, 125); } -.test-inspector { -} +.test-inspector {} .test-inspector .test-instance.Pass .DHCode { color: green; @@ -2222,6 +2325,7 @@ svg.expandable path { background-color: #c7b480; border: solid 1px transparent; } + .toggle-switch.active .toggle-knob { margin-left: 0.9em; margin-right: 1px; @@ -2349,6 +2453,7 @@ svg.expandable path { transform: scale(100%); cursor: not-allowed; } + .slide-img { border-radius: 0.4em; margin-bottom: 1em; @@ -2366,4 +2471,4 @@ svg.expandable path { .em { font-style: italic; -} +} \ No newline at end of file From 6a040f53bc62601f16565f6fe74dc9b6aea0b338 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 26 Aug 2024 16:14:20 -0400 Subject: [PATCH 36/77] Added more icons and initial attempts to solve saving issues --- src/haz3lschool/Exercise.re | 8 +++++ src/haz3lweb/Editors.re | 12 +++---- src/haz3lweb/Update.re | 12 +++---- src/haz3lweb/view/ExerciseMode.re | 57 +++++++++++++++++++------------ src/haz3lweb/view/Icons.re | 16 +++++++++ src/haz3lweb/www/style.css | 10 ++++-- 6 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 5ca293ea6a..280a71d4a1 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -482,6 +482,14 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let update_exercise_prompt = ({eds, _} as state: state, new_prompt: string) => { + ...state, + eds: { + ...eds, + prompt: new_prompt, + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 905ff46caf..c30bd43fcc 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -136,19 +136,15 @@ let set_editing_prompt = (editors: t, editing: bool): t => Exercises(n, specs, Exercise.set_editing_prompt(exercise, editing)) }; -let update_exercise_prompt = - (editors: t, new_prompt: string, instructor_mode: bool): t => +let update_exercise_prompt = (editors: t, new_prompt: string): t => switch (editors) { | Scratch(_) | Documentation(_) => editors - | Exercises(n, specs, _) => + | Exercises(n, specs, exercise) => Exercises( n, - specs, - Exercise.state_of_spec( - {...List.nth(specs, n), prompt: new_prompt}, - ~instructor_mode, - ), + ListUtil.update_nth(n, specs, spec => {{...spec, prompt: new_prompt}}), + Exercise.update_exercise_prompt(exercise, new_prompt), ) }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 551814a071..c072fdb50a 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -166,12 +166,15 @@ let update_settings = } | InstructorMode => let new_mode = !settings.instructor_mode; + let editors = Editors.set_editing_prompt(model.editors, false); + let editors = Editors.set_instructor_mode(editors, new_mode); { ...model, - editors: Editors.set_instructor_mode(model.editors, new_mode), + editors, settings: { ...settings, instructor_mode: !settings.instructor_mode, + editing_prompt: false, }, }; | EditingPrompt => @@ -523,12 +526,7 @@ let rec apply = | UpdatePrompt(new_prompt) => Model.save_and_return({ ...model, - editors: - Editors.update_exercise_prompt( - model.editors, - new_prompt, - model.settings.instructor_mode, - ), + editors: Editors.update_exercise_prompt(model.editors, new_prompt), }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 4116f66cfd..552cf0b0e9 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -76,6 +76,18 @@ let view = let title_view = Cell.title_cell(eds.title); + let update_prompt = _ => { + let new_prompt = + Obj.magic( + Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("prompt-input-box")), + )##.value; + let update_events = [ + inject(Set(EditingPrompt)), + inject(UpdatePrompt(new_prompt)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }; + let prompt_view = { let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); @@ -83,7 +95,7 @@ let view = msg @ [ div( - ~attrs=[Attr.class_("pencil-icon")], + ~attrs=[Attr.class_("edit-icon")], [Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)))], ), ]; @@ -93,29 +105,32 @@ let view = [ settings.instructor_mode ? settings.editing_prompt - ? input( - ~attrs=[ - Attr.class_("prompt-content"), - Attr.value(eds.prompt), - Attr.on_keydown(evt => - if (evt##.keyCode === 13) { - let new_prompt = Obj.magic(evt##.target)##.value; - let update_events = [ - inject(Set(EditingPrompt)), - inject(UpdatePrompt(new_prompt)), - ]; - Virtual_dom.Vdom.Effect.Many(update_events); - } else { - // This is placeholder until I figure out how to "do nothing" - inject( - FinishImportAll(None), - ); - } + ? div( + ~attrs=[Attr.class_("prompt-edit")], + [ + input( + ~attrs=[ + Attr.class_("prompt-text"), + Attr.id("prompt-input-box"), + Attr.value(eds.prompt), + ], + (), + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [Widgets.button(Icons.confirm, update_prompt)], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingPrompt)) + ), + ], ), ], - (), ) - : div(~attrs=[Attr.class_("prompt-content")], msg) + : div(~attrs=[Attr.class_("prompt-edit")], msg) : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index 0939cb1f15..d20bec80c6 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -213,3 +213,19 @@ let pencil = "M403.914,0L54.044,349.871L0,512l162.128-54.044L512,108.086L403.914,0z M295.829,151.319l21.617,21.617L110.638,379.745 l-21.617-21.617L295.829,151.319z M71.532,455.932l-15.463-15.463l18.015-54.043l51.491,51.491L71.532,455.932z M153.871,422.979 l-21.617-21.617l206.809-206.809l21.617,21.617L153.871,422.979z M382.297,194.555l-64.852-64.852l21.617-21.617l64.852,64.852 L382.297,194.555z M360.679,86.468l43.234-43.235l64.853,64.853l-43.235,43.234L360.679,86.468z", ], ); + +let confirm = + simple_icon( + ~view="0 0 32 32", + [ + "m16 0c8.836556 0 16 7.163444 16 16s-7.163444 16-16 16-16-7.163444-16-16 7.163444-16 16-16zm0 2c-7.7319865 0-14 6.2680135-14 14s6.2680135 14 14 14 14-6.2680135 14-14-6.2680135-14-14-14zm6.6208153 9.8786797c.3905243.3905242.3905243 1.0236892 0 1.4142135l-7.0710678 7.0710678c-.3626297.3626297-.9344751.3885319-1.3269928.0777064l-.0872208-.0777064-4.24264068-4.2426407c-.39052429-.3905242-.39052429-1.0236892 0-1.4142135.39052428-.3905243 1.02368928-.3905243 1.41421358 0l3.5348268 3.5348268 6.3646681-6.3632539c.3905243-.3905243 1.0236893-.3905243 1.4142136 0z", + ], + ); + +let cancel = + simple_icon( + ~view="0 0 32 32", + [ + "m16 0c8.836556 0 16 7.163444 16 16s-7.163444 16-16 16-16-7.163444-16-16 7.163444-16 16-16zm0 2c-7.7319865 0-14 6.2680135-14 14s6.2680135 14 14 14 14-6.2680135 14-14-6.2680135-14-14-14zm4.2426407 9.7573593c.3905243.3905243.3905243 1.0236893 0 1.4142136l-2.8284271 2.8284271 2.8284271 2.8284271c.3905243.3905243.3905243 1.0236893 0 1.4142136s-1.0236893.3905243-1.4142136 0l-2.8284271-2.8284271-2.8284271 2.8284271c-.3905243.3905243-1.0236893.3905243-1.4142136 0s-.3905243-1.0236893 0-1.4142136l2.8284271-2.8284271-2.8284271-2.8284271c-.3905243-.3905243-.3905243-1.0236893 0-1.4142136s1.0236893-.3905243 1.4142136 0l2.8284271 2.8284271 2.8284271-2.8284271c.3905243-.3905243 1.0236893-.3905243 1.4142136 0z", + ], + ); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 1c42d2006d..b9c907cfdb 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -747,13 +747,19 @@ select { font-size: 1rem; } -.cell-prompt .pencil-icon { +.cell-prompt .prompt-edit { + padding: 1em; + font-size: 1rem; + color: var(--light-text-color); +} + +.prompt-edit .edit-icon { margin-left: 0.5em; cursor: pointer; fill: #7a6219; } -.cell-prompt .pencil-icon:hover { +.prompt-edit .edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; } From 52b5342edd72e2090ea7b6c62538a83208c1ac87 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 26 Aug 2024 21:11:46 -0400 Subject: [PATCH 37/77] Changes done are now saved during page refreshes --- src/haz3lschool/Exercise.re | 90 +++++++------- src/haz3lschool/Gradescope.re | 9 +- src/haz3lweb/Editors.re | 2 +- src/haz3lweb/Export.re | 8 +- src/haz3lweb/Model.re | 9 +- src/haz3lweb/Store.re | 112 +++++++++--------- src/haz3lweb/Update.re | 16 ++- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 1 + .../exercises/Ex_RecursiveFibonacci.ml | 1 + 9 files changed, 141 insertions(+), 107 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 280a71d4a1..ce044dec74 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -53,6 +53,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type p('code) = { + id: Id.t, title: string, version: int, module_name: string, @@ -67,15 +68,12 @@ module F = (ExerciseEnv: ExerciseEnv) => { syntax_tests, }; - [@deriving (show({with_path: false}), sexp, yojson)] - type key = (string, int); - - let key_of = p => { - (p.title, p.version); + let id_of = p => { + p.id; }; - let find_key_opt = (key, specs: list(p('code))) => { - specs |> Util.ListUtil.findi_opt(spec => key_of(spec) == key); + let find_id_opt = (id, specs: list(p('code))) => { + specs |> Util.ListUtil.findi_opt(spec => id_of(spec) == id); }; [@deriving (show({with_path: false}), sexp, yojson)] @@ -96,6 +94,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let map = (p: p('a), f: 'a => 'b): p('b) => { { + id: p.id, title: p.title, version: p.version, module_name: p.module_name, @@ -134,10 +133,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { eds, }; - let key_of_state = ({eds, _}) => key_of(eds); - [@deriving (show({with_path: false}), sexp, yojson)] - type persistent_state = (pos, list((pos, PersistentZipper.t))); + type persistent_state = (pos, list((pos, PersistentZipper.t)), string); let editor_of_state: state => Editor.t = ({pos, eds, _}) => @@ -284,6 +281,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let transition: transitionary_spec => spec = ( { + id, title, version, module_name, @@ -320,6 +318,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { {tests, hints}; }; { + id, title, version, module_name, @@ -339,6 +338,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let eds_of_spec: spec => eds = ( { + id, title, version, module_name, @@ -372,6 +372,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { {tests, hints}; }; { + id, title, version, module_name, @@ -508,21 +509,22 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let persistent_state_of_state = - ({pos, _} as state: state, ~instructor_mode: bool) => { + ({pos, eds} as state: state, ~instructor_mode: bool) => { let zippers = positioned_editors(state) |> List.filter(((pos, _)) => visible_in(pos, ~instructor_mode)) |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - (pos, zippers); + (pos, zippers, eds.prompt); }; let unpersist_state = ( - (pos, positioned_zippers): persistent_state, + (pos, positioned_zippers, prompt): persistent_state, ~spec: spec, ~instructor_mode: bool, + ~editing_prompt: bool, ) : state => { let lookup = (pos, default) => @@ -548,33 +550,36 @@ module F = (ExerciseEnv: ExerciseEnv) => { ); let hidden_tests_tests = lookup(HiddenTests, spec.hidden_tests.tests); - set_instructor_mode( - { - pos, - eds: { - title: spec.title, - version: spec.version, - module_name: spec.module_name, - prompt: spec.prompt, - point_distribution: spec.point_distribution, - prelude, - correct_impl, - your_tests: { - tests: your_tests_tests, - required: spec.your_tests.required, - provided: spec.your_tests.provided, - }, - your_impl, - hidden_bugs, - hidden_tests: { - tests: hidden_tests_tests, - hints: spec.hidden_tests.hints, + let state = + set_instructor_mode( + { + pos, + eds: { + id: spec.id, + title: spec.title, + version: spec.version, + module_name: spec.module_name, + prompt, + point_distribution: spec.point_distribution, + prelude, + correct_impl, + your_tests: { + tests: your_tests_tests, + required: spec.your_tests.required, + provided: spec.your_tests.provided, + }, + your_impl, + hidden_bugs, + hidden_tests: { + tests: hidden_tests_tests, + hints: spec.hidden_tests.hints, + }, + syntax_tests: spec.syntax_tests, }, - syntax_tests: spec.syntax_tests, }, - }, - instructor_mode, - ); + instructor_mode, + ); + set_editing_prompt(state, editing_prompt); }; // # Stitching @@ -993,6 +998,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { ); let hidden_tests_tests = Zipper.next_blank(); { + id: Id.mk(), title, version: 1, module_name, @@ -1019,8 +1025,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { [@deriving (show({with_path: false}), sexp, yojson)] type exercise_export = { - cur_exercise: key, - exercise_data: list((key, persistent_state)), + cur_exercise: Id.t, + exercise_data: list((Id.t, persistent_state)), }; let serialize_exercise = (exercise, ~instructor_mode) => { @@ -1029,11 +1035,11 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> Sexplib.Sexp.to_string; }; - let deserialize_exercise = (data, ~spec, ~instructor_mode) => { + let deserialize_exercise = (data, ~spec, ~instructor_mode, ~editing_prompt) => { data |> Sexplib.Sexp.of_string |> persistent_state_of_sexp - |> unpersist_state(~spec, ~instructor_mode); + |> unpersist_state(~spec, ~instructor_mode, ~editing_prompt); }; let deserialize_exercise_export = data => { diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index 930bde1e64..4e397b4d0e 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -37,7 +37,7 @@ type report = { }; [@deriving (sexp, yojson)] type section = { - name: string, + id: Id.t, report, }; @@ -111,17 +111,18 @@ module Main = { let hw = name_to_exercise_export(hw_path); let export_chapter = hw.exercise_data - |> List.map(~f=(((name, _) as key, persistent_state)) => { - switch (find_key_opt(key, specs)) { + |> List.map(~f=((id, persistent_state)) => { + switch (find_id_opt(id, specs)) { | Some((_n, spec)) => let exercise = unpersist_state( persistent_state, ~spec, ~instructor_mode=true, + ~editing_prompt=false, ); let report = exercise |> gen_grading_report; - {name, report}; + {id, report}; | None => failwith("Invalid spec") // | None => (key |> yojson_of_key |> Yojson.Safe.to_string, "?") } diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index c30bd43fcc..1e8102983c 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -143,7 +143,7 @@ let update_exercise_prompt = (editors: t, new_prompt: string): t => | Exercises(n, specs, exercise) => Exercises( n, - ListUtil.update_nth(n, specs, spec => {{...spec, prompt: new_prompt}}), + specs, Exercise.update_exercise_prompt(exercise, new_prompt), ) }; diff --git a/src/haz3lweb/Export.re b/src/haz3lweb/Export.re index 0dda0c794f..e1ba07ee06 100644 --- a/src/haz3lweb/Export.re +++ b/src/haz3lweb/Export.re @@ -55,7 +55,13 @@ let import_all = (data, ~specs) => { let settings = Store.Settings.import(all.settings); Store.ExplainThisModel.import(all.explainThisModel); let instructor_mode = settings.instructor_mode; + let editing_prompt = settings.editing_prompt; Store.Scratch.import(~settings=settings.core.evaluation, all.scratch); - Store.Exercise.import(all.exercise, ~specs, ~instructor_mode); + Store.Exercise.import( + all.exercise, + ~specs, + ~instructor_mode, + ~editing_prompt, + ); Log.import(all.log); }; diff --git a/src/haz3lweb/Model.re b/src/haz3lweb/Model.re index 2d6a4dc3cd..02a1225541 100644 --- a/src/haz3lweb/Model.re +++ b/src/haz3lweb/Model.re @@ -57,7 +57,12 @@ let blank = mk(Editors.Scratch(0, []), ModelResults.empty, CachedStatics.empty); let load_editors = - (~settings, ~mode: Settings.mode, ~instructor_mode: bool) + ( + ~settings, + ~mode: Settings.mode, + ~instructor_mode: bool, + ~editing_prompt: bool, + ) : (Editors.t, ModelResults.t) => switch (mode) { | Scratch => @@ -71,6 +76,7 @@ let load_editors = Store.Exercise.load( ~specs=ExerciseSettings.exercises, ~instructor_mode, + ~editing_prompt, ); (Exercises(n, specs, exercise), ModelResults.empty); }; @@ -94,6 +100,7 @@ let load = (init_model: t): t => { ~settings=settings.core.evaluation, ~mode=settings.mode, ~instructor_mode=settings.instructor_mode, + ~editing_prompt=settings.editing_prompt, ); let ui_state = init_model.ui_state; let statics = Editors.mk_statics(~settings, editors); diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 0c4ac3fe23..fbafa4505f 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -233,44 +233,33 @@ module Exercise = { let cur_exercise_key = "CUR_EXERCISE"; - let keystring_of_key = key => { - key |> sexp_of_key |> Sexplib.Sexp.to_string; - }; - - let keystring_of = p => { - key_of(p) |> keystring_of_key; - }; - - let key_of_keystring = keystring => { - keystring |> Sexplib.Sexp.of_string |> key_of_sexp; - }; - - let save_exercise_key = key => { - JsUtil.set_localstore(cur_exercise_key, keystring_of_key(key)); + let save_exercise_id = id => { + JsUtil.set_localstore(cur_exercise_key, Id.to_string(id)); }; let save_exercise = (exercise, ~instructor_mode) => { - let key = Exercise.key_of_state(exercise); - let keystring = keystring_of_key(key); - let value = Exercise.serialize_exercise(exercise, ~instructor_mode); - JsUtil.set_localstore(keystring, value); + let keystring = Id.to_string(exercise.eds.id); + let data = Exercise.serialize_exercise(exercise, ~instructor_mode); + JsUtil.set_localstore(keystring, data); }; let init_exercise = (spec, ~instructor_mode) => { - let key = Exercise.key_of(spec); - let keystring = keystring_of_key(key); + let keystring = Id.to_string(spec.id); let exercise = Exercise.state_of_spec(spec, ~instructor_mode); save_exercise(exercise, ~instructor_mode); JsUtil.set_localstore(cur_exercise_key, keystring); exercise; }; - let load_exercise = (key, spec, ~instructor_mode): Exercise.state => { - let keystring = keystring_of_key(key); + let load_exercise = + (spec, ~instructor_mode, ~editing_prompt): Exercise.state => { + let keystring = Id.to_string(spec.id); switch (JsUtil.get_localstore(keystring)) { | Some(data) => let exercise = - try(Exercise.deserialize_exercise(data, ~spec, ~instructor_mode)) { + try( + deserialize_exercise(data, ~spec, ~instructor_mode, ~editing_prompt) + ) { | _ => init_exercise(spec, ~instructor_mode) }; JsUtil.set_localstore(cur_exercise_key, keystring); @@ -280,8 +269,7 @@ module Exercise = { }; let save = ((n, specs, exercise), ~instructor_mode) => { - let key = key_of(List.nth(specs, n)); - let keystring = keystring_of_key(key); + let keystring = Id.to_string(List.nth(specs, n).id); save_exercise(exercise, ~instructor_mode); JsUtil.set_localstore(cur_exercise_key, keystring); }; @@ -299,47 +287,62 @@ module Exercise = { exercises; }; - let load = (~specs, ~instructor_mode) => { + let load = (~specs, ~instructor_mode, ~editing_prompt) => { switch (JsUtil.get_localstore(cur_exercise_key)) { | Some(keystring) => - let key = key_of_keystring(keystring); - switch (Exercise.find_key_opt(key, specs)) { - | Some((n, spec)) => - switch (JsUtil.get_localstore(keystring)) { - | Some(data) => - let exercise = - try(deserialize_exercise(data, ~spec, ~instructor_mode)) { - | _ => init_exercise(spec, ~instructor_mode) - }; - (n, specs, exercise); + switch (Id.of_string(keystring)) { + | Some(id) => + switch (Exercise.find_id_opt(id, specs)) { + | Some((n, spec)) => + switch (JsUtil.get_localstore(keystring)) { + | Some(data) => + let exercise = + try( + Exercise.deserialize_exercise( + data, + ~spec, + ~instructor_mode, + ~editing_prompt, + ) + ) { + | _ => init_exercise(spec, ~instructor_mode) + }; + (n, specs, exercise); + | None => + // initialize exercise from spec + let exercise = Exercise.state_of_spec(spec, ~instructor_mode); + save_exercise(exercise, ~instructor_mode); + (n, specs, exercise); + } | None => // initialize exercise from spec - let exercise = Exercise.state_of_spec(spec, ~instructor_mode); - save_exercise(exercise, ~instructor_mode); - (n, specs, exercise); + let first_spec = List.nth(specs, 0); + ( + 0, + specs, + load_exercise(first_spec, ~instructor_mode, ~editing_prompt), + ); } - | None => - // invalid current exercise key saved, load the first exercise - let first_spec = List.nth(specs, 0); - let first_key = Exercise.key_of(first_spec); - (0, specs, load_exercise(first_key, first_spec, ~instructor_mode)); - }; + | None => failwith("parse error") + } | None => init(~instructor_mode) }; }; - let prep_exercise_export = (~specs, ~instructor_mode) => { + let prep_exercise_export = (~specs, ~instructor_mode, ~editing_prompt) => { { cur_exercise: - key_of_keystring( - Option.get(JsUtil.get_localstore(cur_exercise_key)), + Id.t_of_sexp( + Sexplib.Sexp.of_string( + Option.get(JsUtil.get_localstore(cur_exercise_key)), + ), ), exercise_data: specs |> List.map(spec => { - let key = Exercise.key_of(spec); + let key = spec.id; let exercise = - load_exercise(key, spec, ~instructor_mode) + load_exercise(spec, ~instructor_mode, ~editing_prompt) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); }), @@ -347,7 +350,7 @@ module Exercise = { }; let serialize_exercise_export = (~specs, ~instructor_mode) => { - prep_exercise_export(~specs, ~instructor_mode) + prep_exercise_export(~specs, ~instructor_mode, ~editing_prompt=false) |> sexp_of_exercise_export |> Sexplib.Sexp.to_string; }; @@ -356,12 +359,12 @@ module Exercise = { serialize_exercise_export(~specs, ~instructor_mode); }; - let import = (data, ~specs, ~instructor_mode) => { + let import = (data, ~specs, ~instructor_mode, ~editing_prompt) => { let exercise_export = data |> deserialize_exercise_export; - save_exercise_key(exercise_export.cur_exercise); + save_exercise_id(exercise_export.cur_exercise); exercise_export.exercise_data |> List.iter(((key, persistent_state)) => { - let spec = Exercise.find_key_opt(key, specs); + let spec = Exercise.find_id_opt(key, specs); switch (spec) { | None => print_endline("Warning: saved key does not correspond to exercise") @@ -371,6 +374,7 @@ module Exercise = { persistent_state, ~spec, ~instructor_mode, + ~editing_prompt, ), ~instructor_mode, ) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index c072fdb50a..adc9de2ced 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -259,7 +259,8 @@ let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => }; let switch_scratch_slide = - (editors: Editors.t, ~instructor_mode, idx: int): option(Editors.t) => + (editors: Editors.t, ~instructor_mode, ~editing_prompt, idx: int) + : option(Editors.t) => switch (editors) { | Documentation(_) => None | Scratch(n, _) when n == idx => None @@ -268,8 +269,8 @@ let switch_scratch_slide = | Exercises(_, specs, _) when idx >= List.length(specs) => None | Exercises(_, specs, _) => let spec = List.nth(specs, idx); - let key = Exercise.key_of(spec); - let exercise = Store.Exercise.load_exercise(key, spec, ~instructor_mode); + let exercise = + Store.Exercise.load_exercise(spec, ~instructor_mode, ~editing_prompt); Some(Exercises(idx, specs, exercise)); }; @@ -382,7 +383,14 @@ let rec apply = let instructor_mode = model.settings.instructor_mode; let editors = Editors.set_editing_prompt(model.editors, false); let settings = {...model.settings, editing_prompt: false}; - switch (switch_scratch_slide(editors, ~instructor_mode, n)) { + switch ( + switch_scratch_slide( + editors, + ~instructor_mode, + ~editing_prompt=false, + n, + ) + ) { | None => Error(FailedToSwitch) | Some(editors) => Model.save_and_return({...model, editors, settings}) }; diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index ab0a0b5ee7..393c98bb51 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -4,6 +4,7 @@ let prompt = Ex_OddlyRecursive_prompt.prompt let exercise : Exercise.spec = { + id = Option.get (Id.of_string "3335e34d-d211-4332-91e2-815e9e183885"); title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index 1e95c4719d..9dfc47beda 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -4,6 +4,7 @@ let prompt = Ex_RecursiveFibonacci_prompt.prompt let exercise : Exercise.spec = { + id = Option.get (Id.of_string "12f5e34d-d211-4332-91e2-815e9e183885"); title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; From 93056b4656ab14df4138aa7ecb348202dd41ec3f Mon Sep 17 00:00:00 2001 From: facundoy Date: Thu, 29 Aug 2024 21:12:43 -0400 Subject: [PATCH 38/77] Increased size of textbox --- src/haz3lweb/view/ExerciseMode.re | 7 +++---- src/haz3lweb/www/style.css | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 552cf0b0e9..87d5766095 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -108,13 +108,12 @@ let view = ? div( ~attrs=[Attr.class_("prompt-edit")], [ - input( + textarea( ~attrs=[ Attr.class_("prompt-text"), Attr.id("prompt-input-box"), - Attr.value(eds.prompt), ], - (), + msg, ), div( ~attrs=[Attr.class_("edit-icon")], @@ -131,7 +130,7 @@ let view = ], ) : div(~attrs=[Attr.class_("prompt-edit")], msg) - : div(~attrs=[Attr.class_("prompt-content")], msg), + : div(~attrs=[Attr.class_("prompt-edit")], msg), ], ), ]); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index b9c907cfdb..9d8f1de8de 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -747,10 +747,22 @@ select { font-size: 1rem; } +.prompt-edit .prompt-text { + width: 500px; + height: 300px; + padding: 10px; + font-size: 16px; + + white-space: pre-wrap; + overflow-wrap: break-word; + word-wrap: break-word; +} + .cell-prompt .prompt-edit { padding: 1em; font-size: 1rem; color: var(--light-text-color); + display: flex; } .prompt-edit .edit-icon { @@ -761,6 +773,7 @@ select { .prompt-edit .edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; + display: flex; } .cell-caption { From bc305e674e408546592a2dd7a5fce78ee3f76d45 Mon Sep 17 00:00:00 2001 From: facundoy Date: Thu, 29 Aug 2024 23:01:18 -0400 Subject: [PATCH 39/77] Fixed a bug where the prompt would not display correctly after editing it. Improved code display in prompt --- src/haz3lweb/view/ExerciseMode.re | 6 +++--- src/haz3lweb/www/style.css | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 87d5766095..a3f18b9b05 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -113,7 +113,7 @@ let view = Attr.class_("prompt-text"), Attr.id("prompt-input-box"), ], - msg, + [text(eds.prompt)], ), div( ~attrs=[Attr.class_("edit-icon")], @@ -129,8 +129,8 @@ let view = ), ], ) - : div(~attrs=[Attr.class_("prompt-edit")], msg) - : div(~attrs=[Attr.class_("prompt-edit")], msg), + : div(~attrs=[Attr.class_("prompt-content")], msg) + : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), ]); diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 9d8f1de8de..8b0fc74dfc 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -745,6 +745,7 @@ select { .cell-prompt { padding: 1em; font-size: 1rem; + line-height: 1.6; } .prompt-edit .prompt-text { @@ -754,7 +755,6 @@ select { font-size: 16px; white-space: pre-wrap; - overflow-wrap: break-word; word-wrap: break-word; } @@ -765,15 +765,23 @@ select { display: flex; } -.prompt-edit .edit-icon { +.prompt-content { + display: flex; +} + +.prompt-content .code { + margin-left: 0.25em; + margin-right: 0.25em; +} + +.edit-icon { margin-left: 0.5em; cursor: pointer; fill: #7a6219; } -.prompt-edit .edit-icon:hover { +.prompt-content .edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; - display: flex; } .cell-caption { From 1c9286504413538d47a0387e973191f3f582da15 Mon Sep 17 00:00:00 2001 From: facundoy Date: Thu, 29 Aug 2024 23:24:02 -0400 Subject: [PATCH 40/77] Improved icons animations and code display, and improved code to allow prompt to have line breaks --- src/haz3lweb/view/ExplainThis.re | 4 ++++ src/haz3lweb/www/style.css | 13 +++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/haz3lweb/view/ExplainThis.re b/src/haz3lweb/view/ExplainThis.re index d8186758d2..a689067946 100644 --- a/src/haz3lweb/view/ExplainThis.re +++ b/src/haz3lweb/view/ExplainThis.re @@ -157,6 +157,7 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { let (inner_msg, mapping) = highlight(~inject, d, id, mapping); (List.append(msg, [inner_msg]), mapping); | Omd.Emph(_, d) => + print_endline("empth"); let (d, mapping) = translate_inline(d, [], mapping, ~inject); ( List.append( @@ -174,6 +175,9 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { ), mapping, ); + | Omd.Soft_break(_) => + print_endline("break"); + (List.append(msg, [Node.br()]), mapping); | _ => (msg, mapping) }; }; diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 8b0fc74dfc..c7de174563 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -762,25 +762,18 @@ select { padding: 1em; font-size: 1rem; color: var(--light-text-color); - display: flex; } -.prompt-content { - display: flex; -} - -.prompt-content .code { - margin-left: 0.25em; - margin-right: 0.25em; -} .edit-icon { margin-left: 0.5em; cursor: pointer; fill: #7a6219; + display: inline-flex; + vertical-align: top; } -.prompt-content .edit-icon:hover { +.edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; } From 7398692aa23edcf3f7beb41d5fc49460f058428b Mon Sep 17 00:00:00 2001 From: facundoy Date: Thu, 29 Aug 2024 23:24:52 -0400 Subject: [PATCH 41/77] Deleted print_endline statements --- src/haz3lweb/view/ExplainThis.re | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/haz3lweb/view/ExplainThis.re b/src/haz3lweb/view/ExplainThis.re index a689067946..a8c10ac9bf 100644 --- a/src/haz3lweb/view/ExplainThis.re +++ b/src/haz3lweb/view/ExplainThis.re @@ -157,7 +157,6 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { let (inner_msg, mapping) = highlight(~inject, d, id, mapping); (List.append(msg, [inner_msg]), mapping); | Omd.Emph(_, d) => - print_endline("empth"); let (d, mapping) = translate_inline(d, [], mapping, ~inject); ( List.append( @@ -176,7 +175,6 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { mapping, ); | Omd.Soft_break(_) => - print_endline("break"); (List.append(msg, [Node.br()]), mapping); | _ => (msg, mapping) }; From 431da052cb99cff6484dadaeb849da0619e7a59c Mon Sep 17 00:00:00 2001 From: facundoy Date: Thu, 29 Aug 2024 23:25:47 -0400 Subject: [PATCH 42/77] Style changes --- src/haz3lweb/view/ExplainThis.re | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/haz3lweb/view/ExplainThis.re b/src/haz3lweb/view/ExplainThis.re index a8c10ac9bf..c8d1dff2d6 100644 --- a/src/haz3lweb/view/ExplainThis.re +++ b/src/haz3lweb/view/ExplainThis.re @@ -174,8 +174,7 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { ), mapping, ); - | Omd.Soft_break(_) => - (List.append(msg, [Node.br()]), mapping); + | Omd.Soft_break(_) => (List.append(msg, [Node.br()]), mapping) | _ => (msg, mapping) }; }; From 5adc764c9d18a22df234c4a4fca2575847e519c7 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 16 Sep 2024 16:43:35 -0400 Subject: [PATCH 43/77] Updated local dependencies and fixed conflict in Diag.re --- src/haz3lweb/view/dec/Diag.re | 56 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/haz3lweb/view/dec/Diag.re b/src/haz3lweb/view/dec/Diag.re index 4b7a815c2e..a145c9d0e9 100644 --- a/src/haz3lweb/view/dec/Diag.re +++ b/src/haz3lweb/view/dec/Diag.re @@ -18,24 +18,22 @@ let tr_bl = (), ) => SvgUtil.Path.( - { - let (diag, junction) = - with_child_border - ? ( - L_({dx: Float.neg(short_tip_width), dy: short_tip_height}), - H_({dx: Float.neg(0.5 -. short_tip_width)}), - ) - : ( - L_({dx: Float.neg(tip_width), dy: 0.5 +. stretch_y}), - H_({dx: Float.neg(stretch_x)}), - ); - let path = - switch (hemi) { - | `North => [junction, diag] - | `South => [diag, junction] - }; - scale(s, path); - } + let (diag, junction) = + with_child_border + ? ( + L_({dx: Float.neg(short_tip_width), dy: short_tip_height}), + H_({dx: Float.neg(0.5 -. short_tip_width)}), + ) + : ( + L_({dx: Float.neg(tip_width), dy: 0.5 +. stretch_y}), + H_({dx: Float.neg(stretch_x)}), + ); + let path = + switch (hemi) { + | `North => [junction, diag] + | `South => [diag, junction] + }; + scale(s, path) ); // bottom left to top right let bl_tr = @@ -60,18 +58,16 @@ let tl_br = (), ) => SvgUtil.Path.( - { - let (diag, junction) = - with_child_border - ? ( - L_({dx: short_tip_width, dy: short_tip_height}), - H_({dx: 0.5 -. short_tip_width}), - ) - : (L_({dx: tip_width, dy: 0.5 +. stretch_y}), H_({dx: stretch_x})); - switch (hemi) { - | `North => [junction, diag] - | `South => [diag, junction] - }; + let (diag, junction) = + with_child_border + ? ( + L_({dx: short_tip_width, dy: short_tip_height}), + H_({dx: 0.5 -. short_tip_width}), + ) + : (L_({dx: tip_width, dy: 0.5 +. stretch_y}), H_({dx: stretch_x})); + switch (hemi) { + | `North => [junction, diag] + | `South => [diag, junction] } ); // bottom right to top left From 8a0582726e917c8dc551d50526f3a8fb7843d762 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 22 Sep 2024 15:04:23 -0400 Subject: [PATCH 44/77] Added relevant update actions --- src/haz3lweb/Log.re | 2 ++ src/haz3lweb/Update.re | 4 +++- src/haz3lweb/UpdateAction.re | 10 +++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index eb9b8a7100..ca8f04a3ec 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -25,6 +25,8 @@ let is_action_logged: UpdateAction.t => bool = | Redo | UpdateResult(_) | UpdatePrompt(_) + | UpdatePointDist(_) + | UpdateTestReq(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 4216806eea..b3b123d9c4 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -568,7 +568,9 @@ let apply = Model.save_and_return({ ...model, editors: Editors.update_exercise_prompt(model.editors, new_prompt), - }) + }); + | UpdatePointDist(_) => Ok(model) + | UpdateTestReq(_) => Ok(model) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index bb5c131c90..e27814f6c9 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -89,7 +89,9 @@ type t = | ToggleStepper(ModelResults.Key.t) | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) - | UpdatePrompt(string); + | UpdatePrompt(string) + | UpdatePointDist(int) + | UpdateTestReq(int); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -143,6 +145,8 @@ let is_edit: t => bool = | FinishImportScratchpad(_) | ResetCurrentEditor | UpdatePrompt(_) + | UpdatePointDist(_) + | UpdateTestReq(_) | Reset | TAB => true | UpdateResult(_) @@ -195,6 +199,8 @@ let reevaluate_post_update: t => bool = | Export(_) | UpdateResult(_) | UpdatePrompt(_) + | UpdatePointDist(_) + | UpdateTestReq(_) | SwitchEditor(_) | DebugConsole(_) | Benchmark(_) => false @@ -238,6 +244,8 @@ let should_scroll_to_caret = | UpdateResult(_) | ToggleStepper(_) | UpdatePrompt(_) + | UpdatePointDist(_) + | UpdateTestReq(_) | StepperAction(_, StepBackward | StepForward(_)) => false | FinishImportScratchpad(_) | FinishImportAll(_) From bb114757546fa8a705d7ee4642bfae8b31cc429e Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 22 Sep 2024 16:26:04 -0400 Subject: [PATCH 45/77] Added functionality to toggle between editing and finalized states --- src/haz3lschool/Exercise.re | 75 ++++++++++++++++++++++++++++++++++++ src/haz3lweb/Editors.re | 36 +++++++++++++++++ src/haz3lweb/Init.ml | 2 + src/haz3lweb/Log.re | 2 +- src/haz3lweb/Settings.re | 2 + src/haz3lweb/Update.re | 37 ++++++++++++++++-- src/haz3lweb/UpdateAction.re | 18 ++++++--- src/haz3lweb/view/Page.re | 7 +++- 8 files changed, 168 insertions(+), 11 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 9875928566..2398fd2718 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -493,6 +493,81 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let set_editing_test_num = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; + }, + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + + let update_test_num = ({eds, _} as state: state, new_test_num: int) => { + ...state, + eds: { + ...eds, + your_tests: { + ...eds.your_tests, + required: new_test_num, + }, + }, + }; + + let set_editing_point_dist = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; + }, + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + + let update_point_dist = + ({eds, _} as state: state, new_point_dist: int, dist: string) => { + let updated_point_distribution = + switch (dist) { + | "test_validation" => { + ...eds.point_distribution, + test_validation: new_point_dist, + } + | "mutation_testing" => { + ...eds.point_distribution, + mutation_testing: new_point_dist, + } + | "impl_grading" => { + ...eds.point_distribution, + impl_grading: new_point_dist, + } + | _ => eds.point_distribution + }; + + { + ...state, + eds: { + ...eds, + point_distribution: updated_point_distribution, + }, + }; + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 401d945742..589aea305e 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -137,6 +137,42 @@ let update_exercise_prompt = (editors: t, new_prompt: string): t => ) }; +let set_editing_test_num = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_test_num(exercise, editing)) + }; + +let update_test_num = (editors: t, new_test_num: int): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.update_test_num(exercise, new_test_num)) + }; + +let set_editing_point_dist = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_point_dist(exercise, editing)) + }; + +let update_point_dist = (editors: t, new_point_dist: int, dist: string): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises( + n, + specs, + Exercise.update_point_dist(exercise, new_point_dist, dist), + ) + }; + let reset_nth_slide = (~settings: CoreSettings.t, n, slides): list(Editor.t) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Init.ml b/src/haz3lweb/Init.ml index ffc7e1d294..ee90bc0429 100644 --- a/src/haz3lweb/Init.ml +++ b/src/haz3lweb/Init.ml @@ -27,6 +27,8 @@ let startup : PersistentData.t = context_inspector = false; instructor_mode = true; editing_prompt = false; + editing_point_dist = false; + editing_test_num = false; benchmark = false; explainThis = { show = true; show_feedback = false; highlight = NoHighlight }; diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index ca8f04a3ec..619bc94317 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -26,7 +26,7 @@ let is_action_logged: UpdateAction.t => bool = | UpdateResult(_) | UpdatePrompt(_) | UpdatePointDist(_) - | UpdateTestReq(_) + | UpdateTestNum(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index 0e9af6bbc5..a17a44208f 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -23,6 +23,8 @@ type t = { context_inspector: bool, instructor_mode: bool, editing_prompt: bool, + editing_point_dist: bool, + editing_test_num: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index b3b123d9c4..7baae1e2a4 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -176,6 +176,8 @@ let update_settings = ...settings, instructor_mode: !settings.instructor_mode, editing_prompt: false, + editing_test_num: false, + editing_point_dist: false, }, }; | EditingPrompt => @@ -188,6 +190,26 @@ let update_settings = editing_prompt: editing, }, }; + | EditingTestNum => + let editing = !settings.editing_test_num; + { + ...model, + editors: Editors.set_editing_test_num(model.editors, editing), + settings: { + ...settings, + editing_test_num: editing, + }, + }; + | EditingPointDist => + let editing = !settings.editing_point_dist; + { + ...model, + editors: Editors.set_editing_test_num(model.editors, editing), + settings: { + ...settings, + editing_point_dist: editing, + }, + }; | Mode(mode) => { ...model, settings: { @@ -568,9 +590,18 @@ let apply = Model.save_and_return({ ...model, editors: Editors.update_exercise_prompt(model.editors, new_prompt), - }); - | UpdatePointDist(_) => Ok(model) - | UpdateTestReq(_) => Ok(model) + }) + | UpdatePointDist(new_point_dist, dist) => + Model.save_and_return({ + ...model, + editors: + Editors.update_point_dist(model.editors, new_point_dist, dist), + }) + | UpdateTestNum(new_test_num) => + Model.save_and_return({ + ...model, + editors: Editors.update_test_num(model.editors, new_test_num), + }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index e27814f6c9..4bed091537 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -25,6 +25,8 @@ type settings_action = | ContextInspector | InstructorMode | EditingPrompt + | EditingPointDist + | EditingTestNum | Evaluation(evaluation_settings_action) | ExplainThis(ExplainThisModel.Settings.action) | Mode(Settings.mode); @@ -90,8 +92,8 @@ type t = | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) | UpdatePrompt(string) - | UpdatePointDist(int) - | UpdateTestReq(int); + | UpdatePointDist(int, string) + | UpdateTestNum(int); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -126,6 +128,8 @@ let is_edit: t => bool = | ContextInspector | InstructorMode | EditingPrompt + | EditingPointDist + | EditingTestNum | Evaluation(_) => false } | SetMeta(meta_action) => @@ -146,7 +150,7 @@ let is_edit: t => bool = | ResetCurrentEditor | UpdatePrompt(_) | UpdatePointDist(_) - | UpdateTestReq(_) + | UpdateTestNum(_) | Reset | TAB => true | UpdateResult(_) @@ -183,6 +187,8 @@ let reevaluate_post_update: t => bool = | Dynamics | InstructorMode | EditingPrompt + | EditingPointDist + | EditingTestNum | Mode(_) => true } | SetMeta(meta_action) => @@ -200,7 +206,7 @@ let reevaluate_post_update: t => bool = | UpdateResult(_) | UpdatePrompt(_) | UpdatePointDist(_) - | UpdateTestReq(_) + | UpdateTestNum(_) | SwitchEditor(_) | DebugConsole(_) | Benchmark(_) => false @@ -232,6 +238,8 @@ let should_scroll_to_caret = | ContextInspector | InstructorMode | EditingPrompt + | EditingPointDist + | EditingTestNum | Evaluation(_) => false } | SetMeta(meta_action) => @@ -245,7 +253,7 @@ let should_scroll_to_caret = | ToggleStepper(_) | UpdatePrompt(_) | UpdatePointDist(_) - | UpdateTestReq(_) + | UpdateTestNum(_) | StepperAction(_, StepBackward | StepForward(_)) => false | FinishImportScratchpad(_) | FinishImportAll(_) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 15d872ba2c..68eae653e8 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -24,9 +24,12 @@ let key_handler = switch (Keyboard.handle_key_event(key)) { | None => Ignore | Some(action) => - get_settings(model).editing_prompt + let settings = get_settings(model); + settings.editing_prompt + || settings.editing_test_num + || settings.editing_point_dist ? Many([inject(action)]) - : Many([Prevent_default, Stop_propagation, inject(action)]) + : Many([Prevent_default, Stop_propagation, inject(action)]); } }; }; From c44374d5d8fc6967107f6558fb2675728a3494bc Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 22 Sep 2024 17:16:13 -0400 Subject: [PATCH 46/77] Adding code that got deleted during the merge --- src/haz3lweb/Main.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 194e46f820..0b90e99d7f 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -83,6 +83,7 @@ module App = { schedule_action(Haz3lweb.Update.SetMeta(FontMetrics(fm))) ); + NinjaKeys.initialize(NinjaKeys.options(schedule_action)); JsUtil.focus_clipboard_shim(); /* initialize state. */ From bf68618117fc683d18490861e5dac32dae545cb6 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 22 Sep 2024 17:17:04 -0400 Subject: [PATCH 47/77] Adding code that got deleted during the merge --- src/haz3lweb/Main.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 194e46f820..0b90e99d7f 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -83,6 +83,7 @@ module App = { schedule_action(Haz3lweb.Update.SetMeta(FontMetrics(fm))) ); + NinjaKeys.initialize(NinjaKeys.options(schedule_action)); JsUtil.focus_clipboard_shim(); /* initialize state. */ From 49d91b41a04af30dd0226f615d3d340a6173f254 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 22 Sep 2024 17:23:41 -0400 Subject: [PATCH 48/77] Solved bug where editing mode persisted between exercises --- src/haz3lweb/Update.re | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 7baae1e2a4..0c252fe39f 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -497,13 +497,15 @@ let apply = | SwitchScratchSlide(n) => let instructor_mode = model.settings.instructor_mode; let editors = Editors.set_editing_prompt(model.editors, false); - let settings = {...model.settings, editing_prompt: false}; + let settings = {...model.settings, editing_prompt: false, editing_point_dist: false, editing_test_num: false}; switch ( switch_scratch_slide( editors, ~settings=model.settings.core, ~instructor_mode, ~editing_prompt=false, + ~editing_point_dist=false, + ~editing_test_num=false, n, ) ) { From a938192778701b4d9b291d2b263e330d8d3983ec Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Fri, 27 Sep 2024 13:46:28 -0400 Subject: [PATCH 49/77] PersistentState type made into record field, now also storing hidden bugs; persistent_state_of_state and unpersist_state updated to now save and load to and from local memory respectively --- src/haz3lschool/Exercise.re | 77 ++++++++++++++++--------------- src/haz3lweb/Editors.re | 8 +++- src/haz3lweb/Update.re | 1 + src/haz3lweb/view/Cell.re | 20 +++++++- src/haz3lweb/view/ExerciseMode.re | 21 +++++++-- src/haz3lweb/view/Icons.re | 2 +- src/haz3lweb/www/style/cell.css | 21 +++++++++ 7 files changed, 103 insertions(+), 47 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 0a23efaea5..482658c113 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -134,25 +134,23 @@ module F = (ExerciseEnv: ExerciseEnv) => { eds, }; - // [@deriving (show({with_path: false}), sexp, yojson)] - type persistent_state = ( - pos, - list((pos, PersistentZipper.t)), - string, - list(wrong_impl(Editor.t)), - ); - - /* - - type PersistentEditor = ; - - type persistent_eds = p(PersistentEditor.t); - + [@deriving (show({with_path: false}), sexp, yojson)] type persistent_state = { - pos, - persistent_eds, + focus: pos, + editors: list((pos, PersistentZipper.t)), + title: string, + hidden_bugs: list(wrong_impl(PersistentZipper.t)), + // NOTE: Add new fields to record here as new instructor editable features are + // implemented (eg. prelude: PersistentZipper.t when adding the feature + // to edit the prelude). After adding these field(s), you will need to + // go into persistent_state_of_state and unpersist_state to implement + // how these fields are saved and loaded to and from local memory + // respectively. + // NOTE: It may be helpful to look at changes made in the mutant-add-delete + // branch in the Hazelgrove repo to see and understand where changes + // were made. It is likely that new implementations of editble features + // will follow a similar route. }; - */ let editor_of_state: state => Editor.t = ({pos, eds, _}) => @@ -517,9 +515,10 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; - let add_buggy_impl = (state: state, ~editing_title) => { + let add_buggy_impl = + (~settings: CoreSettings.t, state: state, ~editing_title) => { let new_buggy_impl = { - impl: Editor.init(Zipper.init()), + impl: Editor.init(Zipper.init(), ~settings), hint: "no hint available", }; let new_state = { @@ -584,12 +583,22 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - (state.pos, zippers, state.eds.title); + let persistent_hidden_bugs = + state.eds.hidden_bugs + |> List.map(({impl, hint}) => { + {impl: PersistentZipper.persist(Editor.(impl.state.zipper)), hint} + }); + { + focus: state.pos, + editors: zippers, + title: state.eds.title, + hidden_bugs: persistent_hidden_bugs, + }; }; let unpersist_state = ( - (pos, positioned_zippers, title, hints): persistent_state, + {focus, editors, title, hidden_bugs}: persistent_state, ~spec: spec, ~instructor_mode: bool, ~editing_title: bool, @@ -598,34 +607,28 @@ module F = (ExerciseEnv: ExerciseEnv) => { : state => { let lookup = (pos, default) => if (visible_in(pos, ~instructor_mode)) { - let persisted_zipper = List.assoc(pos, positioned_zippers); + let persisted_zipper = List.assoc(pos, editors); let zipper = PersistentZipper.unpersist(persisted_zipper); Editor.init(zipper, ~settings); } else { Editor.init(default, ~settings); }; - print_endline("Unpersisting State Now"); let prelude = lookup(Prelude, spec.prelude); let correct_impl = lookup(CorrectImpl, spec.correct_impl); let your_tests_tests = lookup(YourTestsValidation, spec.your_tests.tests); let your_impl = lookup(YourImpl, spec.your_impl); - let (_, hidden_bugs) = - List.fold_left( - ((i, hidden_bugs: list(wrong_impl(Editor.t))), hint) => { - let persisted_zipper = - List.assoc(HiddenBugs(i), positioned_zippers); - let zipper = PersistentZipper.unpersist(persisted_zipper); - let impl = Editor.init(zipper); - (i + 1, hidden_bugs @ [{impl, hint}]); - }, - (0, []), - hints, - ); + let hidden_bugs = + hidden_bugs + |> List.map(({impl, hint}) => { + let impl = + Editor.init(PersistentZipper.unpersist(impl), ~settings); + {impl, hint}; + }); let hidden_tests_tests = lookup(HiddenTests, spec.hidden_tests.tests); let state = set_instructor_mode( { - pos, + pos: focus, eds: { id: spec.id, title, @@ -1025,14 +1028,12 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let serialize_exercise = (exercise, ~instructor_mode) => { - print_endline("Serializing Exercise Now"); persistent_state_of_state(exercise, ~instructor_mode) |> sexp_of_persistent_state |> Sexplib.Sexp.to_string; }; let deserialize_exercise = (data, ~spec, ~instructor_mode, ~editing_title) => { - print_endline("Deserializing Exercise Now"); data |> Sexplib.Sexp.of_string |> persistent_state_of_sexp diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index ace11664d6..3b1bcfdd12 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -132,12 +132,16 @@ let update_exercise_title = (editors: t, new_title: string): t => Exercises(n, specs, Exercise.update_exercise_title(exercise, new_title)) }; -let add_buggy_impl = (editors: t, ~editing_title) => { +let add_buggy_impl = (~settings: CoreSettings.t, editors: t, ~editing_title) => { switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.add_buggy_impl(exercise, ~editing_title)) + Exercises( + n, + specs, + Exercise.add_buggy_impl(~settings, exercise, ~editing_title), + ) }; }; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index c30d360cc2..ac82a917de 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -574,6 +574,7 @@ let apply = ...model, editors: Editors.add_buggy_impl( + ~settings=model.settings.core, model.editors, ~editing_title=model.settings.editing_title, ), diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index b9b7dab9f2..0fb56b1060 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -316,7 +316,6 @@ let editor_view = ]), ], [ - div(~attr=Attr.class_("cell-item"), Option.to_list(caption)), div( ~attrs=[ Attr.classes(["cell-item"]), @@ -355,6 +354,25 @@ let title_cell = title => { ]); }; +let wrong_impl_caption = (~inject, sub: string, n: int) => { + div( + ~attrs=[Attr.class_("wrong-impl-cell-caption")], + [ + caption("", ~rest=sub), + div( + ~attrs=[Attr.class_("instructor-edit-icon")], + [ + Widgets.button( + Icons.delete, + _ => inject(UpdateAction.DeleteBuggyImplementation(n)), + ~tooltip="Delete Buggy Implementation", + ), + ], + ), + ], + ); +}; + /* An editor view that is not selectable or editable, * and does not show error holes or test results. * Used in Docs to display the header example */ diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 4170d8ef0f..50f9bbd235 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -69,6 +69,18 @@ let view = ); }; + let update_title = _ => { + let new_title = + Obj.magic( + Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("title-input-box")), + )##.value; + let update_events = [ + inject(Set(EditingTitle)), + inject(UpdateTitle(new_title)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }; + let title_view = { Cell.simple_cell_view([ div( @@ -229,11 +241,10 @@ let view = ~attrs=[Attr.class_("wrong-impl-cell-caption")], [ div( - ~attrs= - [ - Attr.class_("instructor-edit-icon"), - Attr.id("add-icon"), - ], + ~attrs=[ + Attr.class_("instructor-edit-icon"), + Attr.id("add-icon"), + ], [ Widgets.button( Icons.add, diff --git a/src/haz3lweb/view/Icons.re b/src/haz3lweb/view/Icons.re index a4edb9206a..06bba15d53 100644 --- a/src/haz3lweb/view/Icons.re +++ b/src/haz3lweb/view/Icons.re @@ -253,7 +253,7 @@ let command_palette_sparkle = "m505.08 561.96c-10.16 36.805-29.699 70.34-56.707 97.328-27.008 26.984-60.559 46.5-97.371 56.633 36.82 10.152 70.375 29.688 97.383 56.695 27.008 27.008 46.543 60.562 56.695 97.383 10.145-36.824 29.676-70.387 56.684-97.395 27.012-27.012 60.57-46.543 97.398-56.684-36.816-10.121-70.375-29.633-97.383-56.621-27.012-26.988-46.547-60.531-56.699-97.34z", "m849 507.24c-46.578-13.02-82.977-49.418-96-96-13.09 46.758-49.766 83.203-96.602 96 46.812 12.844 83.469 49.273 96.602 96 13.043-46.566 49.434-82.957 96-96z", "m554.76 426.6c6.5195-23.285 24.715-41.48 48-48-23.297-6.5-41.5-24.707-48-48-6.5 23.293-24.707 41.5-48 48 23.281 6.5195 41.477 24.715 48 48z", - ] + ], ); let add = diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index 0961a80216..a3fd22369e 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -85,6 +85,27 @@ padding: 1em; } +.wrong-impl-cell-caption { + flex-grow: 1; + display: flex; + align-items: center; +} + +.instructor-edit-icon { + margin-top: 0.175em; + margin-left: 1em; + cursor: pointer; + fill: #7a6219; +} + +#add-icon { + margin-left: 0em; +} + +.instructor-edit-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; +} + /* DOCUMENTATION SLIDES */ .slide-img { From 5514fc575cf448e85f6455e4315e79e27224bf71 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 28 Sep 2024 17:53:02 -0400 Subject: [PATCH 50/77] Implemented the storing/exporting of new changes --- src/haz3lschool/Exercise.re | 24 +++++++++++++++++++++--- src/haz3lschool/Gradescope.re | 2 ++ src/haz3lweb/Export.re | 4 ++++ src/haz3lweb/Model.re | 6 ++++++ src/haz3lweb/Store.re | 34 ++++++++++++++++++++++++++++++++-- src/haz3lweb/Update.re | 11 ++++++++++- 6 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 2398fd2718..15264bd8e4 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -603,6 +603,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~spec: spec, ~instructor_mode: bool, ~editing_prompt: bool, + ~editing_point_dist: bool, + ~editing_test_num: bool, ~settings: CoreSettings.t, ) : state => { @@ -658,7 +660,9 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, instructor_mode, ); - set_editing_prompt(state, editing_prompt); + let state = set_editing_prompt(state, editing_prompt); + let state = set_editing_point_dist(state, editing_point_dist); + set_editing_test_num(state, editing_test_num); }; // # Stitching @@ -1039,11 +1043,25 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> Sexplib.Sexp.to_string; }; - let deserialize_exercise = (data, ~spec, ~instructor_mode, ~editing_prompt) => { + let deserialize_exercise = + ( + data, + ~spec, + ~instructor_mode, + ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, + ) => { data |> Sexplib.Sexp.of_string |> persistent_state_of_sexp - |> unpersist_state(~spec, ~instructor_mode, ~editing_prompt); + |> unpersist_state( + ~spec, + ~instructor_mode, + ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, + ); }; let deserialize_exercise_export = data => { diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index 2320051936..a7834b65b4 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -118,6 +118,8 @@ module Main = { ~spec, ~instructor_mode=true, ~editing_prompt=false, + ~editing_point_dist=false, + ~editing_test_num=false, ); let report = exercise |> gen_grading_report; {id, report}; diff --git a/src/haz3lweb/Export.re b/src/haz3lweb/Export.re index 9c684397b7..f5de47ad69 100644 --- a/src/haz3lweb/Export.re +++ b/src/haz3lweb/Export.re @@ -56,6 +56,8 @@ let import_all = (data, ~specs) => { Store.ExplainThisModel.import(all.explainThisModel); let instructor_mode = settings.instructor_mode; let editing_prompt = settings.editing_prompt; + let editing_point_dist = settings.editing_point_dist; + let editing_test_num = settings.editing_test_num; Store.Scratch.import(~settings=settings.core, all.scratch); Store.Exercise.import( ~settings=settings.core, @@ -63,6 +65,8 @@ let import_all = (data, ~specs) => { ~specs, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ); Log.import(all.log); }; diff --git a/src/haz3lweb/Model.re b/src/haz3lweb/Model.re index d4bb5cfdc1..301463c86a 100644 --- a/src/haz3lweb/Model.re +++ b/src/haz3lweb/Model.re @@ -59,6 +59,8 @@ let load_editors = ~mode: Settings.mode, ~instructor_mode: bool, ~editing_prompt: bool, + ~editing_point_dist: bool, + ~editing_test_num: bool, ) : (Editors.t, ModelResults.t) => switch (mode) { @@ -75,6 +77,8 @@ let load_editors = ~specs=ExerciseSettings.exercises, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ); (Exercises(n, specs, exercise), ModelResults.empty); }; @@ -99,6 +103,8 @@ let load = (init_model: t): t => { ~mode=settings.mode, ~instructor_mode=settings.instructor_mode, ~editing_prompt=settings.editing_prompt, + ~editing_point_dist=settings.editing_point_dist, + ~editing_test_num=settings.editing_test_num, ); let ui_state = init_model.ui_state; {editors, settings, results, explainThisModel, ui_state}; diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 8458f6643a..3411efa608 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -280,7 +280,14 @@ module Exercise = { }; let load_exercise = - (~settings: CoreSettings.t, spec, ~instructor_mode, ~editing_prompt) + ( + ~settings: CoreSettings.t, + spec, + ~instructor_mode, + ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, + ) : Exercise.state => { let keystring = Id.to_string(spec.id); switch (JsUtil.get_localstore(keystring)) { @@ -292,6 +299,8 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ~settings, ) ) { @@ -325,7 +334,14 @@ module Exercise = { }; let load = - (~settings: CoreSettings.t, ~specs, ~instructor_mode, ~editing_prompt) + ( + ~settings: CoreSettings.t, + ~specs, + ~instructor_mode, + ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, + ) : (int, list(p(ZipperBase.t)), state) => { switch (JsUtil.get_localstore(cur_exercise_key)) { | Some(keystring) => @@ -342,6 +358,8 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ~settings, ) ) { @@ -365,6 +383,8 @@ module Exercise = { first_spec, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ~settings, ), ); @@ -381,6 +401,8 @@ module Exercise = { ~instructor_mode: bool, ~settings: CoreSettings.t, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ) : exercise_export => { { @@ -400,6 +422,8 @@ module Exercise = { ~instructor_mode, ~settings, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); @@ -414,6 +438,8 @@ module Exercise = { ~instructor_mode, ~settings, ~editing_prompt=false, + ~editing_point_dist=false, + ~editing_test_num=false, ) |> sexp_of_exercise_export |> Sexplib.Sexp.to_string; @@ -430,6 +456,8 @@ module Exercise = { ~instructor_mode: bool, ~settings: CoreSettings.t, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ) => { let exercise_export = data |> deserialize_exercise_export; save_exercise_id(exercise_export.cur_exercise); @@ -446,6 +474,8 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ~settings, ), ~instructor_mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 0c252fe39f..d6f514bf61 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -285,6 +285,8 @@ let switch_scratch_slide = ~instructor_mode, idx: int, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ) : option(Editors.t) => switch (editors) { @@ -301,6 +303,8 @@ let switch_scratch_slide = ~instructor_mode, ~settings, ~editing_prompt, + ~editing_point_dist, + ~editing_test_num, ); Some(Exercises(idx, specs, exercise)); }; @@ -497,7 +501,12 @@ let apply = | SwitchScratchSlide(n) => let instructor_mode = model.settings.instructor_mode; let editors = Editors.set_editing_prompt(model.editors, false); - let settings = {...model.settings, editing_prompt: false, editing_point_dist: false, editing_test_num: false}; + let settings = { + ...model.settings, + editing_prompt: false, + editing_point_dist: false, + editing_test_num: false, + }; switch ( switch_scratch_slide( editors, From 824969a5d98eba3c3fbd7b413cf7666c9d38f40a Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 28 Sep 2024 19:46:48 -0400 Subject: [PATCH 51/77] Changed actions from edited data to edited section --- src/haz3lschool/Exercise.re | 91 +++++++++++++++++++++-------------- src/haz3lschool/Gradescope.re | 5 +- src/haz3lweb/Editors.re | 40 ++++++++++----- src/haz3lweb/Export.re | 10 ++-- src/haz3lweb/Grading.re | 24 +++++++++ src/haz3lweb/Init.ml | 5 +- src/haz3lweb/Log.re | 5 +- src/haz3lweb/Model.re | 15 +++--- src/haz3lweb/Settings.re | 5 +- src/haz3lweb/Store.re | 50 +++++++++++-------- src/haz3lweb/Update.re | 64 +++++++++++++++--------- src/haz3lweb/UpdateAction.re | 45 +++++++++-------- src/haz3lweb/view/Page.re | 5 +- 13 files changed, 234 insertions(+), 130 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 15264bd8e4..be944f5323 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -493,7 +493,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; - let set_editing_test_num = ({eds, _} as state: state, editing: bool) => { + let set_editing_test_val_rep = ({eds, _} as state: state, editing: bool) => { ...state, eds: { ...eds, @@ -511,7 +511,8 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; - let update_test_num = ({eds, _} as state: state, new_test_num: int) => { + let update_test_val_rep = + ({eds, _} as state: state, new_test_num: int, new_dist: int) => { ...state, eds: { ...eds, @@ -519,10 +520,14 @@ module F = (ExerciseEnv: ExerciseEnv) => { ...eds.your_tests, required: new_test_num, }, + point_distribution: { + ...eds.point_distribution, + test_validation: new_dist, + }, }, }; - let set_editing_point_dist = ({eds, _} as state: state, editing: bool) => { + let set_editing_mut_test_rep = ({eds, _} as state: state, editing: bool) => { ...state, eds: { ...eds, @@ -540,32 +545,44 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; - let update_point_dist = - ({eds, _} as state: state, new_point_dist: int, dist: string) => { - let updated_point_distribution = - switch (dist) { - | "test_validation" => { - ...eds.point_distribution, - test_validation: new_point_dist, - } - | "mutation_testing" => { - ...eds.point_distribution, - mutation_testing: new_point_dist, - } - | "impl_grading" => { - ...eds.point_distribution, - impl_grading: new_point_dist, - } - | _ => eds.point_distribution - }; + let update_mut_test_rep = ({eds, _} as state: state, new_dist: int) => { + ...state, + eds: { + ...eds, + point_distribution: { + ...eds.point_distribution, + mutation_testing: new_dist, + }, + }, + }; - { - ...state, - eds: { - ...eds, - point_distribution: updated_point_distribution, + let set_editing_impl_grd_rep = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; }, - }; + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + + let update_impl_grd_rep = ({eds, _} as state: state, new_dist: int) => { + ...state, + eds: { + ...eds, + point_distribution: { + ...eds.point_distribution, + impl_grading: new_dist, + }, + }, }; let visible_in = (pos, ~instructor_mode) => { @@ -603,8 +620,9 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~spec: spec, ~instructor_mode: bool, ~editing_prompt: bool, - ~editing_point_dist: bool, - ~editing_test_num: bool, + ~editing_test_val_rep: bool, + ~editing_mut_test_rep: bool, + ~editing_impl_grd_rep: bool, ~settings: CoreSettings.t, ) : state => { @@ -661,8 +679,9 @@ module F = (ExerciseEnv: ExerciseEnv) => { instructor_mode, ); let state = set_editing_prompt(state, editing_prompt); - let state = set_editing_point_dist(state, editing_point_dist); - set_editing_test_num(state, editing_test_num); + let state = set_editing_test_val_rep(state, editing_test_val_rep); + let state = set_editing_mut_test_rep(state, editing_mut_test_rep); + set_editing_impl_grd_rep(state, editing_impl_grd_rep); }; // # Stitching @@ -1049,8 +1068,9 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) => { data |> Sexplib.Sexp.of_string @@ -1059,8 +1079,9 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ); }; diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index a7834b65b4..dc113226fd 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -118,8 +118,9 @@ module Main = { ~spec, ~instructor_mode=true, ~editing_prompt=false, - ~editing_point_dist=false, - ~editing_test_num=false, + ~editing_test_val_rep=false, + ~editing_mut_test_rep=false, + ~editing_impl_grd_rep=false, ); let report = exercise |> gen_grading_report; {id, report}; diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 589aea305e..3c8eb232aa 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -137,40 +137,56 @@ let update_exercise_prompt = (editors: t, new_prompt: string): t => ) }; -let set_editing_test_num = (editors: t, editing: bool): t => +let set_editing_test_val_rep = (editors: t, editing: bool): t => switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.set_editing_test_num(exercise, editing)) + Exercises(n, specs, Exercise.set_editing_test_val_rep(exercise, editing)) }; -let update_test_num = (editors: t, new_test_num: int): t => +let update_test_val_rep = (editors: t, new_test_num: int, new_dist: int): t => switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.update_test_num(exercise, new_test_num)) + Exercises( + n, + specs, + Exercise.update_test_val_rep(exercise, new_test_num, new_dist), + ) }; -let set_editing_point_dist = (editors: t, editing: bool): t => +let set_editing_mut_test_rep = (editors: t, editing: bool): t => switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.set_editing_point_dist(exercise, editing)) + Exercises(n, specs, Exercise.set_editing_mut_test_rep(exercise, editing)) }; -let update_point_dist = (editors: t, new_point_dist: int, dist: string): t => +let update_mut_test_rep = (editors: t, new_dist: int): t => switch (editors) { | Scratch(_) | Documentation(_) => editors | Exercises(n, specs, exercise) => - Exercises( - n, - specs, - Exercise.update_point_dist(exercise, new_point_dist, dist), - ) + Exercises(n, specs, Exercise.update_mut_test_rep(exercise, new_dist)) + }; + +let set_editing_impl_grd_rep = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_impl_grd_rep(exercise, editing)) + }; + +let update_impl_grd_rep = (editors: t, new_dist: int): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.update_impl_grd_rep(exercise, new_dist)) }; let reset_nth_slide = (~settings: CoreSettings.t, n, slides): list(Editor.t) => { diff --git a/src/haz3lweb/Export.re b/src/haz3lweb/Export.re index f5de47ad69..409d9d9faf 100644 --- a/src/haz3lweb/Export.re +++ b/src/haz3lweb/Export.re @@ -56,8 +56,9 @@ let import_all = (data, ~specs) => { Store.ExplainThisModel.import(all.explainThisModel); let instructor_mode = settings.instructor_mode; let editing_prompt = settings.editing_prompt; - let editing_point_dist = settings.editing_point_dist; - let editing_test_num = settings.editing_test_num; + let editing_test_val_rep = settings.editing_test_val_rep; + let editing_mut_test_rep = settings.editing_mut_test_rep; + let editing_impl_grd_rep = settings.editing_impl_grd_rep; Store.Scratch.import(~settings=settings.core, all.scratch); Store.Exercise.import( ~settings=settings.core, @@ -65,8 +66,9 @@ let import_all = (data, ~specs) => { ~specs, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ); Log.import(all.log); }; diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index e16827b918..d6ab057993 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -53,6 +53,18 @@ module TestValidationReport = { }; }; + // let update_requirements = _ => { + // let new_prompt = + // Obj.magic( + // Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("prompt-input-box")), + // )##.value; + // let update_events = [ + // inject(Set(EditingPrompt)), + // inject(UpdatePrompt(new_prompt)), + // ]; + // Virtual_dom.Vdom.Effect.Many(update_events); + // }; + let view = (~inject, report: t, max_points: int) => { Cell.report_footer_view([ div( @@ -236,6 +248,18 @@ module MutationTestingReport = { // }; // }; + // let update_requirements = _ => { + // let new_prompt = + // Obj.magic( + // Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("prompt-input-box")), + // )##.value; + // let update_events = [ + // inject(Set(EditingPrompt)), + // inject(UpdatePrompt(new_prompt)), + // ]; + // Virtual_dom.Vdom.Effect.Many(update_events); + // }; + let view = (~inject, report: t, max_points: int) => if (max_points == 0) { Node.div([]); diff --git a/src/haz3lweb/Init.ml b/src/haz3lweb/Init.ml index ee90bc0429..5d0db9162e 100644 --- a/src/haz3lweb/Init.ml +++ b/src/haz3lweb/Init.ml @@ -27,8 +27,9 @@ let startup : PersistentData.t = context_inspector = false; instructor_mode = true; editing_prompt = false; - editing_point_dist = false; - editing_test_num = false; + editing_test_val_rep = false; + editing_mut_test_rep = false; + editing_impl_grd_rep = false; benchmark = false; explainThis = { show = true; show_feedback = false; highlight = NoHighlight }; diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index 619bc94317..c72213c16c 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -25,8 +25,9 @@ let is_action_logged: UpdateAction.t => bool = | Redo | UpdateResult(_) | UpdatePrompt(_) - | UpdatePointDist(_) - | UpdateTestNum(_) + | UpdateTestValRep(_) + | UpdateMutTestRep(_) + | UpdateImplGrdRep(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Model.re b/src/haz3lweb/Model.re index 301463c86a..a515b08e6a 100644 --- a/src/haz3lweb/Model.re +++ b/src/haz3lweb/Model.re @@ -59,8 +59,9 @@ let load_editors = ~mode: Settings.mode, ~instructor_mode: bool, ~editing_prompt: bool, - ~editing_point_dist: bool, - ~editing_test_num: bool, + ~editing_test_val_rep: bool, + ~editing_mut_test_rep: bool, + ~editing_impl_grd_rep: bool, ) : (Editors.t, ModelResults.t) => switch (mode) { @@ -77,8 +78,9 @@ let load_editors = ~specs=ExerciseSettings.exercises, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ); (Exercises(n, specs, exercise), ModelResults.empty); }; @@ -103,8 +105,9 @@ let load = (init_model: t): t => { ~mode=settings.mode, ~instructor_mode=settings.instructor_mode, ~editing_prompt=settings.editing_prompt, - ~editing_point_dist=settings.editing_point_dist, - ~editing_test_num=settings.editing_test_num, + ~editing_test_val_rep=settings.editing_test_val_rep, + ~editing_mut_test_rep=settings.editing_mut_test_rep, + ~editing_impl_grd_rep=settings.editing_impl_grd_rep, ); let ui_state = init_model.ui_state; {editors, settings, results, explainThisModel, ui_state}; diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index a17a44208f..9840eb1de9 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -23,8 +23,9 @@ type t = { context_inspector: bool, instructor_mode: bool, editing_prompt: bool, - editing_point_dist: bool, - editing_test_num: bool, + editing_test_val_rep: bool, + editing_mut_test_rep: bool, + editing_impl_grd_rep: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 3411efa608..d5cbcf977e 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -285,8 +285,9 @@ module Exercise = { spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) : Exercise.state => { let keystring = Id.to_string(spec.id); @@ -299,8 +300,9 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ~settings, ) ) { @@ -339,8 +341,9 @@ module Exercise = { ~specs, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) : (int, list(p(ZipperBase.t)), state) => { switch (JsUtil.get_localstore(cur_exercise_key)) { @@ -358,8 +361,9 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ~settings, ) ) { @@ -383,8 +387,9 @@ module Exercise = { first_spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ~settings, ), ); @@ -401,8 +406,9 @@ module Exercise = { ~instructor_mode: bool, ~settings: CoreSettings.t, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) : exercise_export => { { @@ -422,8 +428,9 @@ module Exercise = { ~instructor_mode, ~settings, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); @@ -438,8 +445,9 @@ module Exercise = { ~instructor_mode, ~settings, ~editing_prompt=false, - ~editing_point_dist=false, - ~editing_test_num=false, + ~editing_test_val_rep=false, + ~editing_mut_test_rep=false, + ~editing_impl_grd_rep=false, ) |> sexp_of_exercise_export |> Sexplib.Sexp.to_string; @@ -456,8 +464,9 @@ module Exercise = { ~instructor_mode: bool, ~settings: CoreSettings.t, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) => { let exercise_export = data |> deserialize_exercise_export; save_exercise_id(exercise_export.cur_exercise); @@ -474,8 +483,9 @@ module Exercise = { ~spec, ~instructor_mode, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ~settings, ), ~instructor_mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index d6f514bf61..de0f121723 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -176,8 +176,9 @@ let update_settings = ...settings, instructor_mode: !settings.instructor_mode, editing_prompt: false, - editing_test_num: false, - editing_point_dist: false, + editing_test_val_rep: false, + editing_mut_test_rep: false, + editing_impl_grd_rep: false, }, }; | EditingPrompt => @@ -190,24 +191,34 @@ let update_settings = editing_prompt: editing, }, }; - | EditingTestNum => - let editing = !settings.editing_test_num; + | EditingTestValRep => + let editing = !settings.editing_test_val_rep; { ...model, - editors: Editors.set_editing_test_num(model.editors, editing), + editors: Editors.set_editing_test_val_rep(model.editors, editing), settings: { ...settings, - editing_test_num: editing, + editing_test_val_rep: editing, }, }; - | EditingPointDist => - let editing = !settings.editing_point_dist; + | EditingMutTestRep => + let editing = !settings.editing_mut_test_rep; { ...model, - editors: Editors.set_editing_test_num(model.editors, editing), + editors: Editors.set_editing_mut_test_rep(model.editors, editing), settings: { ...settings, - editing_point_dist: editing, + editing_mut_test_rep: editing, + }, + }; + | EditingImplGrdRep => + let editing = !settings.editing_impl_grd_rep; + { + ...model, + editors: Editors.set_editing_impl_grd_rep(model.editors, editing), + settings: { + ...settings, + editing_impl_grd_rep: editing, }, }; | Mode(mode) => { @@ -285,8 +296,9 @@ let switch_scratch_slide = ~instructor_mode, idx: int, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ) : option(Editors.t) => switch (editors) { @@ -303,8 +315,9 @@ let switch_scratch_slide = ~instructor_mode, ~settings, ~editing_prompt, - ~editing_point_dist, - ~editing_test_num, + ~editing_test_val_rep, + ~editing_mut_test_rep, + ~editing_impl_grd_rep, ); Some(Exercises(idx, specs, exercise)); }; @@ -504,8 +517,9 @@ let apply = let settings = { ...model.settings, editing_prompt: false, - editing_point_dist: false, - editing_test_num: false, + editing_test_val_rep: false, + editing_mut_test_rep: false, + editing_impl_grd_rep: false, }; switch ( switch_scratch_slide( @@ -513,8 +527,9 @@ let apply = ~settings=model.settings.core, ~instructor_mode, ~editing_prompt=false, - ~editing_point_dist=false, - ~editing_test_num=false, + ~editing_test_val_rep=false, + ~editing_mut_test_rep=false, + ~editing_impl_grd_rep=false, n, ) ) { @@ -602,16 +617,21 @@ let apply = ...model, editors: Editors.update_exercise_prompt(model.editors, new_prompt), }) - | UpdatePointDist(new_point_dist, dist) => + | UpdateTestValRep(new_test_num, new_dist) => Model.save_and_return({ ...model, editors: - Editors.update_point_dist(model.editors, new_point_dist, dist), + Editors.update_test_val_rep(model.editors, new_test_num, new_dist), + }) + | UpdateMutTestRep(new_dist) => + Model.save_and_return({ + ...model, + editors: Editors.update_mut_test_rep(model.editors, new_dist), }) - | UpdateTestNum(new_test_num) => + | UpdateImplGrdRep(new_dist) => Model.save_and_return({ ...model, - editors: Editors.update_test_num(model.editors, new_test_num), + editors: Editors.update_impl_grd_rep(model.editors, new_dist), }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 4bed091537..d23fa9b389 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -25,8 +25,9 @@ type settings_action = | ContextInspector | InstructorMode | EditingPrompt - | EditingPointDist - | EditingTestNum + | EditingTestValRep + | EditingMutTestRep + | EditingImplGrdRep | Evaluation(evaluation_settings_action) | ExplainThis(ExplainThisModel.Settings.action) | Mode(Settings.mode); @@ -48,11 +49,6 @@ type benchmark_action = | Start | Finish; -[@deriving (show({with_path: false}), sexp, yojson)] -type edit_prompt = - | Prompt - | Model; - [@deriving (show({with_path: false}), sexp, yojson)] type export_action = | ExportScratchSlide @@ -92,8 +88,9 @@ type t = | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) | UpdatePrompt(string) - | UpdatePointDist(int, string) - | UpdateTestNum(int); + | UpdateTestValRep(int, int) + | UpdateMutTestRep(int) + | UpdateImplGrdRep(int); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -128,8 +125,9 @@ let is_edit: t => bool = | ContextInspector | InstructorMode | EditingPrompt - | EditingPointDist - | EditingTestNum + | EditingTestValRep + | EditingMutTestRep + | EditingImplGrdRep | Evaluation(_) => false } | SetMeta(meta_action) => @@ -149,8 +147,9 @@ let is_edit: t => bool = | FinishImportScratchpad(_) | ResetCurrentEditor | UpdatePrompt(_) - | UpdatePointDist(_) - | UpdateTestNum(_) + | UpdateTestValRep(_) + | UpdateMutTestRep(_) + | UpdateImplGrdRep(_) | Reset | TAB => true | UpdateResult(_) @@ -187,8 +186,9 @@ let reevaluate_post_update: t => bool = | Dynamics | InstructorMode | EditingPrompt - | EditingPointDist - | EditingTestNum + | EditingTestValRep + | EditingMutTestRep + | EditingImplGrdRep | Mode(_) => true } | SetMeta(meta_action) => @@ -205,8 +205,9 @@ let reevaluate_post_update: t => bool = | Export(_) | UpdateResult(_) | UpdatePrompt(_) - | UpdatePointDist(_) - | UpdateTestNum(_) + | UpdateTestValRep(_) + | UpdateMutTestRep(_) + | UpdateImplGrdRep(_) | SwitchEditor(_) | DebugConsole(_) | Benchmark(_) => false @@ -238,8 +239,9 @@ let should_scroll_to_caret = | ContextInspector | InstructorMode | EditingPrompt - | EditingPointDist - | EditingTestNum + | EditingTestValRep + | EditingMutTestRep + | EditingImplGrdRep | Evaluation(_) => false } | SetMeta(meta_action) => @@ -252,8 +254,9 @@ let should_scroll_to_caret = | UpdateResult(_) | ToggleStepper(_) | UpdatePrompt(_) - | UpdatePointDist(_) - | UpdateTestNum(_) + | UpdateTestValRep(_) + | UpdateMutTestRep(_) + | UpdateImplGrdRep(_) | StepperAction(_, StepBackward | StepForward(_)) => false | FinishImportScratchpad(_) | FinishImportAll(_) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 68eae653e8..be2e274f25 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -26,8 +26,9 @@ let key_handler = | Some(action) => let settings = get_settings(model); settings.editing_prompt - || settings.editing_test_num - || settings.editing_point_dist + || settings.editing_test_val_rep + || settings.editing_mut_test_rep + || settings.editing_impl_grd_rep ? Many([inject(action)]) : Many([Prevent_default, Stop_propagation, inject(action)]); } From ddd2b87d0831fad36c4455dded8dba7c1cff374d Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 28 Sep 2024 21:56:05 -0400 Subject: [PATCH 52/77] Implemented text-box and icons for editing the test validation report. Fixed an issue that prevented saves between refreshes. --- src/haz3lschool/Exercise.re | 22 +++-- src/haz3lweb/Grading.re | 136 ++++++++++++++++++++++++++---- src/haz3lweb/view/ExerciseMode.re | 2 + src/haz3lweb/view/Page.re | 3 + src/haz3lweb/www/style.css | 5 ++ 5 files changed, 145 insertions(+), 23 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index be944f5323..9fe25110d5 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -134,7 +134,13 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; [@deriving (show({with_path: false}), sexp, yojson)] - type persistent_state = (pos, list((pos, PersistentZipper.t)), string); + type persistent_state = ( + pos, + list((pos, PersistentZipper.t)), + string, + point_distribution, + int, + ); let editor_of_state: state => Editor.t = ({pos, eds, _}) => @@ -611,12 +617,18 @@ module F = (ExerciseEnv: ExerciseEnv) => { |> List.map(((pos, editor)) => { (pos, PersistentZipper.persist(Editor.(editor.state.zipper))) }); - (pos, zippers, eds.prompt); + ( + pos, + zippers, + eds.prompt, + eds.point_distribution, + eds.your_tests.required, + ); }; let unpersist_state = ( - (pos, positioned_zippers, prompt): persistent_state, + (pos, positioned_zippers, prompt, point_distribution, required): persistent_state, ~spec: spec, ~instructor_mode: bool, ~editing_prompt: bool, @@ -659,12 +671,12 @@ module F = (ExerciseEnv: ExerciseEnv) => { version: spec.version, module_name: spec.module_name, prompt, - point_distribution: spec.point_distribution, + point_distribution, prelude, correct_impl, your_tests: { tests: your_tests_tests, - required: spec.your_tests.required, + required, provided: spec.your_tests.provided, }, your_impl, diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index d6ab057993..01c027275d 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -1,7 +1,9 @@ +open Util; open Virtual_dom.Vdom; open Node; include Haz3lschool.Grading.F(Exercise.ExerciseEnv); +include Update; let score_view = ((earned: points, max: points)) => { div( @@ -53,28 +55,126 @@ module TestValidationReport = { }; }; - // let update_requirements = _ => { - // let new_prompt = - // Obj.magic( - // Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("prompt-input-box")), - // )##.value; - // let update_events = [ - // inject(Set(EditingPrompt)), - // inject(UpdatePrompt(new_prompt)), - // ]; - // Virtual_dom.Vdom.Effect.Many(update_events); - // }; - - let view = (~inject, report: t, max_points: int) => { + let view = + ( + ~inject, + report: t, + max_points: int, + max_tests: int, + settings: Settings.t, + ) => { Cell.report_footer_view([ div( ~attrs=[Attr.classes(["test-summary"])], [ - div( - ~attrs=[Attr.class_("test-text")], - [score_view(score_of_percent(percentage(report), max_points))] - @ textual_summary(report), - ), + settings.instructor_mode + ? settings.editing_test_val_rep + ? div( + ~attrs=[Attr.class_("test-val-rep-edit")], + [ + div( + ~attrs=[Attr.class_("input-field")], + [ + label([text("New point max:")]), + input( + ~attrs=[ + Attr.type_("number"), + Attr.class_("point-num-input"), + Attr.id("point-max-input"), + Attr.value(string_of_int(max_points)), + ], + (), + ), + ], + ), + div( + ~attrs=[Attr.class_("input-field")], + [ + label([text("Tests required:")]), + input( + ~attrs=[ + Attr.type_("number"), + Attr.class_("point-num-input"), + Attr.id("test-required-input"), + Attr.value(string_of_int(max_tests)), + ], + (), + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button( + Icons.confirm, + _ => { + let new_dist = + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id("point-max-input"), + ), + )##.value; + let new_test_num = + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id( + "test-required-input", + ), + ), + )##.value; + + let update_events = [ + inject(Set(EditingTestValRep)), + inject( + UpdateTestValRep( + int_of_string(new_test_num), + int_of_string(new_dist), + ), + ), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }, + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingTestValRep)) + ), + ], + ), + ], + ) + : div( + ~attrs=[Attr.class_("test-text")], + [ + score_view( + score_of_percent(percentage(report), max_points), + ), + ] + @ textual_summary(report) + @ [ + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingTestValRep)) + ), + ], + ), + ], + ) + : div( + ~attrs=[Attr.class_("test-text")], + [ + score_view( + score_of_percent(percentage(report), max_points), + ), + ] + @ textual_summary(report), + ), ] @ Option.to_list( report.test_results diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 75c065e94e..76158ac1cf 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -206,6 +206,8 @@ let view = ~inject, grading_report.test_validation_report, grading_report.point_distribution.test_validation, + eds.your_tests.required, + settings, ), ], ), diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index be2e274f25..aa0f20589e 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -71,6 +71,9 @@ let handlers = }), ]; model.settings.editing_prompt + || model.settings.editing_test_val_rep + || model.settings.editing_mut_test_rep + || model.settings.editing_impl_grd_rep ? attrs : attrs @ [Attr.on_keypress(_ => Effect.Prevent_default)]; }; diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index f666eaecba..728558d02d 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -180,4 +180,9 @@ ninja-keys { .edit-icon:hover { animation: wobble 0.6s ease 0s 1 normal forwards; +} + +.point-num-input { + width: 50px; + font-size: 12px; } \ No newline at end of file From 869917f4e230c5c03031bfdb2871dacc78e12ff7 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 6 Oct 2024 14:05:03 -0400 Subject: [PATCH 53/77] Implemented editable 'provided tests' in the Test Validation Report --- src/haz3lschool/Exercise.re | 8 +++++++- src/haz3lweb/Editors.re | 10 ++++++++-- src/haz3lweb/Grading.re | 25 +++++++++++++++++++++++++ src/haz3lweb/Update.re | 9 +++++++-- src/haz3lweb/UpdateAction.re | 2 +- src/haz3lweb/view/ExerciseMode.re | 5 +++-- 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 9fe25110d5..5015b3ff41 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -518,13 +518,19 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let update_test_val_rep = - ({eds, _} as state: state, new_test_num: int, new_dist: int) => { + ( + {eds, _} as state: state, + new_test_num: int, + new_dist: int, + new_prov: int, + ) => { ...state, eds: { ...eds, your_tests: { ...eds.your_tests, required: new_test_num, + provided: new_prov, }, point_distribution: { ...eds.point_distribution, diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 3c8eb232aa..5fa0465d44 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -145,7 +145,8 @@ let set_editing_test_val_rep = (editors: t, editing: bool): t => Exercises(n, specs, Exercise.set_editing_test_val_rep(exercise, editing)) }; -let update_test_val_rep = (editors: t, new_test_num: int, new_dist: int): t => +let update_test_val_rep = + (editors: t, new_test_num: int, new_dist: int, new_prov: int): t => switch (editors) { | Scratch(_) | Documentation(_) => editors @@ -153,7 +154,12 @@ let update_test_val_rep = (editors: t, new_test_num: int, new_dist: int): t => Exercises( n, specs, - Exercise.update_test_val_rep(exercise, new_test_num, new_dist), + Exercise.update_test_val_rep( + exercise, + new_test_num, + new_dist, + new_prov, + ), ) }; diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index 01c027275d..a45bb90633 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -61,6 +61,7 @@ module TestValidationReport = { report: t, max_points: int, max_tests: int, + prov_tests: int, settings: Settings.t, ) => { Cell.report_footer_view([ @@ -102,6 +103,21 @@ module TestValidationReport = { ), ], ), + div( + ~attrs=[Attr.class_("input-field")], + [ + label([text("Tests provided:")]), + input( + ~attrs=[ + Attr.type_("number"), + Attr.class_("point-num-input"), + Attr.id("test-provided-input"), + Attr.value(string_of_int(prov_tests)), + ], + (), + ), + ], + ), div( ~attrs=[Attr.class_("edit-icon")], [ @@ -122,6 +138,14 @@ module TestValidationReport = { ), ), )##.value; + let new_prov_test = + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id( + "test-provided-input", + ), + ), + )##.value; let update_events = [ inject(Set(EditingTestValRep)), @@ -129,6 +153,7 @@ module TestValidationReport = { UpdateTestValRep( int_of_string(new_test_num), int_of_string(new_dist), + int_of_string(new_prov_test), ), ), ]; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index de0f121723..67281e817b 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -617,11 +617,16 @@ let apply = ...model, editors: Editors.update_exercise_prompt(model.editors, new_prompt), }) - | UpdateTestValRep(new_test_num, new_dist) => + | UpdateTestValRep(new_test_num, new_dist, new_prov) => Model.save_and_return({ ...model, editors: - Editors.update_test_val_rep(model.editors, new_test_num, new_dist), + Editors.update_test_val_rep( + model.editors, + new_test_num, + new_dist, + new_prov, + ), }) | UpdateMutTestRep(new_dist) => Model.save_and_return({ diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index d23fa9b389..93a2ed0667 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -88,7 +88,7 @@ type t = | StepperAction(ModelResults.Key.t, stepper_action) | UpdateResult(ModelResults.t) | UpdatePrompt(string) - | UpdateTestValRep(int, int) + | UpdateTestValRep(int, int, int) | UpdateMutTestRep(int) | UpdateImplGrdRep(int); diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 76158ac1cf..1de06c0b78 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -81,7 +81,7 @@ let view = let prompt_view = { let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); - let msg = + let new_msg = msg @ [ div( @@ -119,7 +119,7 @@ let view = ), ], ) - : div(~attrs=[Attr.class_("prompt-content")], msg) + : div(~attrs=[Attr.class_("prompt-content")], new_msg) : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), @@ -207,6 +207,7 @@ let view = grading_report.test_validation_report, grading_report.point_distribution.test_validation, eds.your_tests.required, + eds.your_tests.provided, settings, ), ], From 746a54b2a0b7879b27461871528b7e04c7c0caeb Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 6 Oct 2024 14:11:44 -0400 Subject: [PATCH 54/77] Fixed a bug where the pencil icon appeared outside of instructor mode --- src/haz3lweb/view/ExerciseMode.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 75c065e94e..8340f73e17 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -81,7 +81,7 @@ let view = let prompt_view = { let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); - let msg = + let new_msg = msg @ [ div( @@ -119,7 +119,7 @@ let view = ), ], ) - : div(~attrs=[Attr.class_("prompt-content")], msg) + : div(~attrs=[Attr.class_("prompt-content")], new_msg) : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), From e47f631daf35bd422620eb5cb8825f97f016efe1 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 6 Oct 2024 14:51:08 -0400 Subject: [PATCH 55/77] Implemented editable point distribution for the Mutation Testing Report --- src/haz3lweb/Grading.re | 78 ++++++++++++++++++++++++++++--- src/haz3lweb/view/ExerciseMode.re | 1 + 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index a45bb90633..e85316b221 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -220,10 +220,73 @@ module MutationTestingReport = { include MutationTestingReport; open Haz3lcore; - let summary_message = (~score, ~total, ~found): Node.t => + let summary_message = + (~inject, ~score, ~total, ~found, ~max_points, settings: Settings.t) + : Node.t => div( - ~attrs=[Attr.classes(["test-text"])], - [score_view(score), text(summary_str(~total, ~found))], + ~attrs=[Attr.class_("test-text")], + settings.instructor_mode + ? settings.editing_mut_test_rep + ? [ + div( + ~attrs=[Attr.class_("input-field")], + [ + label([text("New point max:")]), + input( + ~attrs=[ + Attr.type_("number"), + Attr.class_("point-num-input"), + Attr.id("point-max-input"), + Attr.value(string_of_int(max_points)), + ], + (), + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button( + Icons.confirm, + _ => { + let new_dist = + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id("point-max-input"), + ), + )##.value; + + let update_events = [ + inject(Set(EditingMutTestRep)), + inject(UpdateMutTestRep(int_of_string(new_dist))), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }, + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingMutTestRep)) + ), + ], + ), + ] + : [ + score_view(score), + text(summary_str(~total, ~found)), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingMutTestRep)) + ), + ], + ), + ] + : [score_view(score), text(summary_str(~total, ~found))], ); let bar = (~inject, instances) => @@ -245,7 +308,7 @@ module MutationTestingReport = { ), ); - let summary = (~inject, ~report, ~max_points) => { + let summary = (~inject, ~report, ~max_points, settings: Settings.t) => { let total = List.length(report.results); let found = List.length( @@ -263,9 +326,12 @@ module MutationTestingReport = { ], [ summary_message( + ~inject, ~score=score_of_percent(percentage(report), max_points), ~total, ~found, + ~max_points, + settings, ), bar(~inject, report.results), ], @@ -385,7 +451,7 @@ module MutationTestingReport = { // Virtual_dom.Vdom.Effect.Many(update_events); // }; - let view = (~inject, report: t, max_points: int) => + let view = (~inject, report: t, max_points: int, settings: Settings.t) => if (max_points == 0) { Node.div([]); } else { @@ -398,7 +464,7 @@ module MutationTestingReport = { ), individual_reports(~inject, report.results), ], - ~footer=Some(summary(~inject, ~report, ~max_points)), + ~footer=Some(summary(~inject, ~report, ~max_points, settings)), ); }; }; diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 1de06c0b78..bdc4a74063 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -234,6 +234,7 @@ let view = ~inject, grading_report.mutation_testing_report, grading_report.point_distribution.mutation_testing, + settings, ), ); let your_impl_view = { From 1b89fd3917d999b4aa5bf2d1bd89df8e90658559 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Fri, 11 Oct 2024 15:17:02 -0400 Subject: [PATCH 56/77] commiting before merge with dev --- src/haz3lschool/Exercise.re | 36 +++++++++++++++++++++--------------- src/haz3lweb/Main.re | 2 ++ src/haz3lweb/Update.re | 8 ++++---- src/haz3lweb/view/Page.re | 2 ++ 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 482658c113..453bed86d5 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -142,12 +142,12 @@ module F = (ExerciseEnv: ExerciseEnv) => { hidden_bugs: list(wrong_impl(PersistentZipper.t)), // NOTE: Add new fields to record here as new instructor editable features are // implemented (eg. prelude: PersistentZipper.t when adding the feature - // to edit the prelude). After adding these field(s), you will need to + // to edit the prelude). After adding these field(s), we will need to // go into persistent_state_of_state and unpersist_state to implement // how these fields are saved and loaded to and from local memory // respectively. - // NOTE: It may be helpful to look at changes made in the mutant-add-delete - // branch in the Hazelgrove repo to see and understand where changes + // NOTE: It may be helpful to look at changes made in the mutant-add-delete and title-editor + // branches in the Hazel repo to see and understand where changes // were made. It is likely that new implementations of editble features // will follow a similar route. }; @@ -160,7 +160,15 @@ module F = (ExerciseEnv: ExerciseEnv) => { | YourTestsValidation => eds.your_tests.tests | YourTestsTesting => eds.your_tests.tests | YourImpl => eds.your_impl - | HiddenBugs(i) => List.nth(eds.hidden_bugs, i).impl + | HiddenBugs(i) => + print_string("Index is: "); + print_int(i); + print_endline(""); + print_string("And the length is: "); + print_int(List.length(eds.hidden_bugs)); + print_endline(""); + let ret = List.nth(eds.hidden_bugs, i).impl; + ret; | HiddenTests => eds.hidden_tests.tests }; @@ -534,27 +542,25 @@ module F = (ExerciseEnv: ExerciseEnv) => { let delete_buggy_impl = (state: state, index: int) => { let length = List.length(state.eds.hidden_bugs); - let flag = length > 1; let editor_on = - flag - ? List.nth( - state.eds.hidden_bugs, - index < length - 1 ? index + 1 : index - 1, - ). + length > 1 + ? List.nth(state.eds.hidden_bugs, index < length - 1 ? index + 1 : 0). impl : state.eds.your_tests.tests; - let position = - flag - ? HiddenBugs(index < length - 1 ? index : index - 1) - : YourTestsValidation; + let pos = + length > 1 + ? HiddenBugs(index < length - 1 ? index : 0) : YourTestsValidation; let new_state = { - pos: position, + pos, eds: { ...state.eds, hidden_bugs: List.filteri((i, _) => i != index, state.eds.hidden_bugs), }, }; + print_string("New pos is: "); + print_string(show_pos(pos)); + print_endline(""); put_editor(new_state, editor_on); }; diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 8c918dd025..97fe56b7d5 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -105,7 +105,9 @@ module App = { ~inject, ) => { open Incr.Let_syntax; + // This model seems to be the root of the problem. It seems to be the old model. let%map model = model; + /* Note: mapping over the old_model here may trigger an additional redraw */ Component.create( diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index ac82a917de..d44e2626ca 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -580,10 +580,10 @@ let apply = ), }) | DeleteBuggyImplementation(index) => - Model.save_and_return({ - ...model, - editors: Editors.delete_buggy_impl(model.editors, index), - }) + print_endline("Deleting Buggy Impl"); + let editors = Editors.delete_buggy_impl(model.editors, index); + // print_endline(Editors.show(editors)); + Model.save_and_return({...model, editors}); }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 34737c6b18..66ed0ea588 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -100,7 +100,9 @@ let main_view = ~inject: UpdateAction.t => Ui_effect.t(unit), {settings, editors, explainThisModel, results, ui_state, _}: Model.t, ) => { + print_endline("here, at main view, getting editor"); let editor = Editors.get_editor(editors); + print_endline("got editor!"); let cursor_info = Indicated.ci_of(editor.state.zipper, editor.state.meta.statics.info_map); let highlights = From c762c91e00e05ebaccc2e6dcfd2d32870c9a9b94 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 20 Oct 2024 13:48:34 -0400 Subject: [PATCH 57/77] style changes --- src/haz3lweb/Grading.re | 111 ++++++++++++++++++++++++++---- src/haz3lweb/view/ExerciseMode.re | 1 + 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index e85316b221..5610c3d0f0 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -633,7 +633,13 @@ module ImplGradingReport = { }; let view = - (~inject, ~report: t, ~syntax_report: SyntaxReport.t, ~max_points: int) => { + ( + ~inject, + ~report: t, + ~syntax_report: SyntaxReport.t, + ~max_points: int, + ~settings: Settings.t, + ) => { Cell.panel( ~classes=["cell-item", "panel", "test-panel"], [ @@ -649,18 +655,99 @@ module ImplGradingReport = { div( ~attrs=[Attr.classes(["test-summary"])], [ - div( - ~attrs=[Attr.class_("test-text")], - [ - score_view( - score_of_percent( - percentage(report, syntax_report), - max_points, + settings.instructor_mode + ? settings.editing_impl_grd_rep + ? Node.div([ + div( + ~attrs=[Attr.class_("input-field")], + [ + label([text("New point max:")]), + input( + ~attrs=[ + Attr.type_("number"), + Attr.class_("point-num-input"), + Attr.id("point-max-input"), + Attr.value(string_of_int(max_points)), + ], + (), + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button( + Icons.confirm, + _ => { + let new_dist = + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id( + "point-max-input", + ), + ), + )##.value; + + let update_events = [ + inject(Set(EditingImplGrdRep)), + inject( + UpdateImplGrdRep( + int_of_string(new_dist), + ), + ), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }, + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingImplGrdRep)) + ), + ], + ), + ]) + : Node.div([ + div( + ~attrs=[Attr.class_("test-text")], + [ + score_view( + score_of_percent( + percentage(report, syntax_report), + max_points, + ), + ), + ] + @ textual_summary(report) + @ [ + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingImplGrdRep)) + ), + ], + ), + ], + ), + ]) + : Node.div([ + div( + ~attrs=[Attr.class_("test-text")], + [ + score_view( + score_of_percent( + percentage(report, syntax_report), + max_points, + ), + ), + ] + @ textual_summary(report), ), - ), - ] - @ textual_summary(report), - ), + ]), ] @ Option.to_list( report.test_results diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index bdc4a74063..eb75dc1099 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -295,6 +295,7 @@ let view = ~report=grading_report.impl_grading_report, ~syntax_report=grading_report.syntax_report, ~max_points=grading_report.point_distribution.impl_grading, + ~settings, ), ); [score_view, title_view, prompt_view] From d26947cfcec18e5db20dc95fa34479b8ef10dce8 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 27 Oct 2024 13:48:37 -0400 Subject: [PATCH 58/77] Added relevant update actions and implemented the storing/exporting of new changes for the new module name --- src/haz3lschool/Exercise.re | 29 +++++++++++++++++++++++++++++ src/haz3lschool/Gradescope.re | 1 + src/haz3lweb/Editors.re | 20 ++++++++++++++++++++ src/haz3lweb/Export.re | 2 ++ src/haz3lweb/Init.ml | 1 + src/haz3lweb/Log.re | 1 + src/haz3lweb/Model.re | 3 +++ src/haz3lweb/Settings.re | 1 + src/haz3lweb/Store.re | 10 ++++++++++ src/haz3lweb/Update.re | 20 ++++++++++++++++++++ src/haz3lweb/UpdateAction.re | 10 +++++++++- src/haz3lweb/view/Page.re | 1 + 12 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 5015b3ff41..953a85fe7d 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -597,6 +597,33 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let set_editing_module_name = ({eds, _} as state: state, editing: bool) => { + ...state, + eds: { + ...eds, + prelude: Editor.set_read_only(eds.prelude, editing), + correct_impl: Editor.set_read_only(eds.correct_impl, editing), + your_tests: { + let tests = Editor.set_read_only(eds.your_tests.tests, editing); + { + tests, + required: eds.your_tests.required, + provided: eds.your_tests.provided, + }; + }, + your_impl: Editor.set_read_only(eds.your_impl, editing), + }, + }; + + let update_module_name = + ({eds, _} as state: state, new_module_name: string) => { + ...state, + eds: { + ...eds, + module_name: new_module_name, + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode @@ -641,6 +668,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~editing_test_val_rep: bool, ~editing_mut_test_rep: bool, ~editing_impl_grd_rep: bool, + ~editing_module_name: bool, ~settings: CoreSettings.t, ) : state => { @@ -699,6 +727,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { let state = set_editing_prompt(state, editing_prompt); let state = set_editing_test_val_rep(state, editing_test_val_rep); let state = set_editing_mut_test_rep(state, editing_mut_test_rep); + let state = set_editing_module_name(state, editing_module_name); set_editing_impl_grd_rep(state, editing_impl_grd_rep); }; diff --git a/src/haz3lschool/Gradescope.re b/src/haz3lschool/Gradescope.re index dc113226fd..c8a8305995 100644 --- a/src/haz3lschool/Gradescope.re +++ b/src/haz3lschool/Gradescope.re @@ -121,6 +121,7 @@ module Main = { ~editing_test_val_rep=false, ~editing_mut_test_rep=false, ~editing_impl_grd_rep=false, + ~editing_module_name=false, ); let report = exercise |> gen_grading_report; {id, report}; diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 5fa0465d44..a2ede71e4e 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -195,6 +195,26 @@ let update_impl_grd_rep = (editors: t, new_dist: int): t => Exercises(n, specs, Exercise.update_impl_grd_rep(exercise, new_dist)) }; +let set_editing_module_name = (editors: t, editing: bool): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.set_editing_module_name(exercise, editing)) + }; + +let update_module_name = (editors: t, new_module_name: string): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises( + n, + specs, + Exercise.update_module_name(exercise, new_module_name), + ) + }; + let reset_nth_slide = (~settings: CoreSettings.t, n, slides): list(Editor.t) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Export.re b/src/haz3lweb/Export.re index 409d9d9faf..d77ce204d6 100644 --- a/src/haz3lweb/Export.re +++ b/src/haz3lweb/Export.re @@ -59,6 +59,7 @@ let import_all = (data, ~specs) => { let editing_test_val_rep = settings.editing_test_val_rep; let editing_mut_test_rep = settings.editing_mut_test_rep; let editing_impl_grd_rep = settings.editing_impl_grd_rep; + let editing_module_name = settings.editing_module_name; Store.Scratch.import(~settings=settings.core, all.scratch); Store.Exercise.import( ~settings=settings.core, @@ -69,6 +70,7 @@ let import_all = (data, ~specs) => { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ); Log.import(all.log); }; diff --git a/src/haz3lweb/Init.ml b/src/haz3lweb/Init.ml index 5d0db9162e..effa6e12ad 100644 --- a/src/haz3lweb/Init.ml +++ b/src/haz3lweb/Init.ml @@ -30,6 +30,7 @@ let startup : PersistentData.t = editing_test_val_rep = false; editing_mut_test_rep = false; editing_impl_grd_rep = false; + editing_module_name = false; benchmark = false; explainThis = { show = true; show_feedback = false; highlight = NoHighlight }; diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index c72213c16c..ef8b70dfd5 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -28,6 +28,7 @@ let is_action_logged: UpdateAction.t => bool = | UpdateTestValRep(_) | UpdateMutTestRep(_) | UpdateImplGrdRep(_) + | UpdateModuleName(_) | ToggleStepper(_) | StepperAction(_, StepForward(_) | StepBackward) | UpdateExplainThisModel(_) => true; diff --git a/src/haz3lweb/Model.re b/src/haz3lweb/Model.re index a515b08e6a..5d4f6d36a7 100644 --- a/src/haz3lweb/Model.re +++ b/src/haz3lweb/Model.re @@ -62,6 +62,7 @@ let load_editors = ~editing_test_val_rep: bool, ~editing_mut_test_rep: bool, ~editing_impl_grd_rep: bool, + ~editing_module_name: bool, ) : (Editors.t, ModelResults.t) => switch (mode) { @@ -81,6 +82,7 @@ let load_editors = ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ); (Exercises(n, specs, exercise), ModelResults.empty); }; @@ -108,6 +110,7 @@ let load = (init_model: t): t => { ~editing_test_val_rep=settings.editing_test_val_rep, ~editing_mut_test_rep=settings.editing_mut_test_rep, ~editing_impl_grd_rep=settings.editing_impl_grd_rep, + ~editing_module_name=settings.editing_module_name, ); let ui_state = init_model.ui_state; {editors, settings, results, explainThisModel, ui_state}; diff --git a/src/haz3lweb/Settings.re b/src/haz3lweb/Settings.re index 9840eb1de9..bded832645 100644 --- a/src/haz3lweb/Settings.re +++ b/src/haz3lweb/Settings.re @@ -26,6 +26,7 @@ type t = { editing_test_val_rep: bool, editing_mut_test_rep: bool, editing_impl_grd_rep: bool, + editing_module_name: bool, benchmark: bool, explainThis: ExplainThisModel.Settings.t, mode, diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index d5cbcf977e..973ebd0dc1 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -288,6 +288,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name: bool, ) : Exercise.state => { let keystring = Id.to_string(spec.id); @@ -303,6 +304,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ~settings, ) ) { @@ -344,6 +346,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name: bool, ) : (int, list(p(ZipperBase.t)), state) => { switch (JsUtil.get_localstore(cur_exercise_key)) { @@ -364,6 +367,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ~settings, ) ) { @@ -390,6 +394,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ~settings, ), ); @@ -409,6 +414,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name: bool, ) : exercise_export => { { @@ -431,6 +437,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ) |> Exercise.persistent_state_of_state(~instructor_mode); (key, exercise); @@ -448,6 +455,7 @@ module Exercise = { ~editing_test_val_rep=false, ~editing_mut_test_rep=false, ~editing_impl_grd_rep=false, + ~editing_module_name=false, ) |> sexp_of_exercise_export |> Sexplib.Sexp.to_string; @@ -467,6 +475,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name: bool, ) => { let exercise_export = data |> deserialize_exercise_export; save_exercise_id(exercise_export.cur_exercise); @@ -486,6 +495,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ~settings, ), ~instructor_mode, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 67281e817b..622382e07b 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -179,6 +179,7 @@ let update_settings = editing_test_val_rep: false, editing_mut_test_rep: false, editing_impl_grd_rep: false, + editing_module_name: false, }, }; | EditingPrompt => @@ -221,6 +222,16 @@ let update_settings = editing_impl_grd_rep: editing, }, }; + | EditingModuleName => + let editing = !settings.editing_module_name; + { + ...model, + editors: Editors.set_editing_module_name(model.editors, editing), + settings: { + ...settings, + editing_module_name: editing, + }, + }; | Mode(mode) => { ...model, settings: { @@ -299,6 +310,7 @@ let switch_scratch_slide = ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name: bool, ) : option(Editors.t) => switch (editors) { @@ -318,6 +330,7 @@ let switch_scratch_slide = ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ); Some(Exercises(idx, specs, exercise)); }; @@ -520,6 +533,7 @@ let apply = editing_test_val_rep: false, editing_mut_test_rep: false, editing_impl_grd_rep: false, + editing_module_name: false, }; switch ( switch_scratch_slide( @@ -530,6 +544,7 @@ let apply = ~editing_test_val_rep=false, ~editing_mut_test_rep=false, ~editing_impl_grd_rep=false, + ~editing_module_name=false, n, ) ) { @@ -638,6 +653,11 @@ let apply = ...model, editors: Editors.update_impl_grd_rep(model.editors, new_dist), }) + | UpdateModuleName(new_module_name) => + Model.save_and_return({ + ...model, + editors: Editors.update_module_name(model.editors, new_module_name), + }) }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); }; diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 93a2ed0667..86dc33bc12 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -28,6 +28,7 @@ type settings_action = | EditingTestValRep | EditingMutTestRep | EditingImplGrdRep + | EditingModuleName | Evaluation(evaluation_settings_action) | ExplainThis(ExplainThisModel.Settings.action) | Mode(Settings.mode); @@ -90,7 +91,8 @@ type t = | UpdatePrompt(string) | UpdateTestValRep(int, int, int) | UpdateMutTestRep(int) - | UpdateImplGrdRep(int); + | UpdateImplGrdRep(int) + | UpdateModuleName(string); module Failure = { [@deriving (show({with_path: false}), sexp, yojson)] @@ -128,6 +130,7 @@ let is_edit: t => bool = | EditingTestValRep | EditingMutTestRep | EditingImplGrdRep + | EditingModuleName | Evaluation(_) => false } | SetMeta(meta_action) => @@ -150,6 +153,7 @@ let is_edit: t => bool = | UpdateTestValRep(_) | UpdateMutTestRep(_) | UpdateImplGrdRep(_) + | UpdateModuleName(_) | Reset | TAB => true | UpdateResult(_) @@ -189,6 +193,7 @@ let reevaluate_post_update: t => bool = | EditingTestValRep | EditingMutTestRep | EditingImplGrdRep + | EditingModuleName | Mode(_) => true } | SetMeta(meta_action) => @@ -208,6 +213,7 @@ let reevaluate_post_update: t => bool = | UpdateTestValRep(_) | UpdateMutTestRep(_) | UpdateImplGrdRep(_) + | UpdateModuleName(_) | SwitchEditor(_) | DebugConsole(_) | Benchmark(_) => false @@ -242,6 +248,7 @@ let should_scroll_to_caret = | EditingTestValRep | EditingMutTestRep | EditingImplGrdRep + | EditingModuleName | Evaluation(_) => false } | SetMeta(meta_action) => @@ -257,6 +264,7 @@ let should_scroll_to_caret = | UpdateTestValRep(_) | UpdateMutTestRep(_) | UpdateImplGrdRep(_) + | UpdateModuleName(_) | StepperAction(_, StepBackward | StepForward(_)) => false | FinishImportScratchpad(_) | FinishImportAll(_) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index aa0f20589e..d797cf0bc2 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -74,6 +74,7 @@ let handlers = || model.settings.editing_test_val_rep || model.settings.editing_mut_test_rep || model.settings.editing_impl_grd_rep + || model.settings.editing_module_name ? attrs : attrs @ [Attr.on_keypress(_ => Effect.Prevent_default)]; }; From 84849fb97329d08d630bde24a2e29fdbb93c96ed Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 27 Oct 2024 16:05:43 -0400 Subject: [PATCH 59/77] Implemented the UI for the module_name and the textbox for the editing --- src/haz3lschool/Exercise.re | 15 ++++++- src/haz3lweb/Store.re | 8 ++-- src/haz3lweb/Update.re | 2 +- src/haz3lweb/view/ExerciseMode.re | 69 ++++++++++++++++++++++++++++++- src/haz3lweb/view/Page.re | 1 + 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 953a85fe7d..2bd7cfc53e 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -140,6 +140,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { string, point_distribution, int, + string, ); let editor_of_state: state => Editor.t = @@ -656,12 +657,20 @@ module F = (ExerciseEnv: ExerciseEnv) => { eds.prompt, eds.point_distribution, eds.your_tests.required, + eds.module_name, ); }; let unpersist_state = ( - (pos, positioned_zippers, prompt, point_distribution, required): persistent_state, + ( + pos, + positioned_zippers, + prompt, + point_distribution, + required, + module_name, + ): persistent_state, ~spec: spec, ~instructor_mode: bool, ~editing_prompt: bool, @@ -703,7 +712,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { id: spec.id, title: spec.title, version: spec.version, - module_name: spec.module_name, + module_name, prompt, point_distribution, prelude, @@ -1118,6 +1127,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ) => { data |> Sexplib.Sexp.of_string @@ -1129,6 +1139,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, + ~editing_module_name, ); }; diff --git a/src/haz3lweb/Store.re b/src/haz3lweb/Store.re index 973ebd0dc1..b88cc5589b 100644 --- a/src/haz3lweb/Store.re +++ b/src/haz3lweb/Store.re @@ -288,7 +288,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, - ~editing_module_name: bool, + ~editing_module_name, ) : Exercise.state => { let keystring = Id.to_string(spec.id); @@ -346,7 +346,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, - ~editing_module_name: bool, + ~editing_module_name, ) : (int, list(p(ZipperBase.t)), state) => { switch (JsUtil.get_localstore(cur_exercise_key)) { @@ -414,7 +414,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, - ~editing_module_name: bool, + ~editing_module_name, ) : exercise_export => { { @@ -475,7 +475,7 @@ module Exercise = { ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, - ~editing_module_name: bool, + ~editing_module_name, ) => { let exercise_export = data |> deserialize_exercise_export; save_exercise_id(exercise_export.cur_exercise); diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 622382e07b..884204230c 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -310,7 +310,7 @@ let switch_scratch_slide = ~editing_test_val_rep, ~editing_mut_test_rep, ~editing_impl_grd_rep, - ~editing_module_name: bool, + ~editing_module_name, ) : option(Editors.t) => switch (editors) { diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index eb75dc1099..31571e1725 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -66,6 +66,73 @@ let view = }; let title_view = Cell.title_cell(eds.title); + let update_module_name = _ => { + let new_module_name = + Obj.magic( + Js_of_ocaml.Js.some(JsUtil.get_elem_by_id("module-name-input")), + )##.value; + let update_events = [ + inject(Set(EditingModuleName)), + inject(UpdateModuleName(new_module_name)), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }; + + let module_name_view = { + settings.instructor_mode + ? Cell.narrative_cell([ + div( + ~attrs=[Attr.class_("cell-module-name")], + [ + settings.editing_module_name + ? div( + ~attrs=[Attr.class_("module-name-edit")], + [ + label([text("Module name:")]), + input( + ~attrs=[ + Attr.type_("text"), + Attr.class_("text-input"), + Attr.id("module-name-input"), + Attr.value(eds.module_name), + ], + (), + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [Widgets.button(Icons.confirm, update_module_name)], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject(Set(EditingModuleName)) + ), + ], + ), + ], + ) + : div( + ~attrs=[Attr.class_("prompt-content")], + [ + text("Module name: "), + text(eds.module_name), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingModuleName)) + ), + ], + ), + ], + ), + ], + ), + ]) + : Node.none; + }; + let update_prompt = _ => { let new_prompt = Obj.magic( @@ -298,7 +365,7 @@ let view = ~settings, ), ); - [score_view, title_view, prompt_view] + [score_view, title_view, module_name_view, prompt_view] @ render_cells( settings, [ diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index d797cf0bc2..1d84c25ea8 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -29,6 +29,7 @@ let key_handler = || settings.editing_test_val_rep || settings.editing_mut_test_rep || settings.editing_impl_grd_rep + || settings.editing_module_name ? Many([inject(action)]) : Many([Prevent_default, Stop_propagation, inject(action)]); } From 45e614314a74182ca6b1a28f1654100bb2a53819 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 9 Nov 2024 15:33:03 -0500 Subject: [PATCH 60/77] Solved a conflict I missed --- src/haz3lweb/view/Page.re | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index e1fe99986e..1b3f872fbc 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -72,15 +72,12 @@ let handlers = inject(PerformAction(Paste(pasted_text))); }), ]; -<<<<<<< HEAD model.settings.editing_prompt + || modle.settings.editing_title || model.settings.editing_test_val_rep || model.settings.editing_mut_test_rep || model.settings.editing_impl_grd_rep || model.settings.editing_module_name -======= - model.settings.editing_title ->>>>>>> title-editor ? attrs : attrs @ [Attr.on_keypress(_ => Effect.Prevent_default)]; }; From c1f984d6121b9cf7d902b1bd6518ee50705f93df Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 9 Nov 2024 15:38:00 -0500 Subject: [PATCH 61/77] Fixed a spelling error --- src/haz3lweb/view/Page.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 1b3f872fbc..42e3f57ef1 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -73,7 +73,7 @@ let handlers = }), ]; model.settings.editing_prompt - || modle.settings.editing_title + || model.settings.editing_title || model.settings.editing_test_val_rep || model.settings.editing_mut_test_rep || model.settings.editing_impl_grd_rep From ae33a8b5c37d15883cf26c4fea85316190ffd3af Mon Sep 17 00:00:00 2001 From: facundoy Date: Sat, 9 Nov 2024 16:24:51 -0500 Subject: [PATCH 62/77] Style changes to the module name cell --- src/haz3lweb/view/ExerciseMode.re | 2 +- src/haz3lweb/www/style.css | 42 ------------------------------- src/haz3lweb/www/style/cell.css | 40 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 2cfc8c4f35..e2b3d1cbd6 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -180,7 +180,7 @@ let view = ], ) : div( - ~attrs=[Attr.class_("prompt-content")], + ~attrs=[Attr.class_("module-name-text")], [ text("Module name: "), text(eds.module_name), diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index 728558d02d..91c4b8c2bb 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -143,46 +143,4 @@ ninja-keys { --ninja-footer-background: var(--T2); --ninja-modal-shadow: 0px 10px 20px var(--menu-shadow); --ninja-overflow-background: none; -} - -/* PROMPT EDITING */ - -.cell-prompt { - padding: 1em; - font-size: 1rem; - line-height: 1.6; -} - -.prompt-edit .prompt-text { - width: 500px; - height: 300px; - padding: 10px; - font-size: 16px; - - white-space: pre-wrap; - word-wrap: break-word; -} - -.cell-prompt .prompt-edit { - padding: 1em; - font-size: 1rem; - color: var(--light-text-color); -} - - -.edit-icon { - margin-left: 0.5em; - cursor: pointer; - fill: #7a6219; - display: inline-flex; - vertical-align: top; -} - -.edit-icon:hover { - animation: wobble 0.6s ease 0s 1 normal forwards; -} - -.point-num-input { - width: 50px; - font-size: 12px; } \ No newline at end of file diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index a3fd22369e..e4c5849e0e 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -132,3 +132,43 @@ .file-select-button { display: none; } + +/* EDITING */ + +.cell-module-name { + padding: 1em; +} + +.prompt-edit .prompt-text { + width: 500px; + height: 300px; + padding: 10px; + font-size: 16px; + + white-space: pre-wrap; + word-wrap: break-word; +} + +.cell-prompt .prompt-edit { + padding: 1em; + font-size: 1rem; + color: var(--light-text-color); +} + + +.edit-icon { + margin-left: 0.5em; + cursor: pointer; + fill: #7a6219; + display: inline-flex; + vertical-align: top; +} + +.edit-icon:hover { + animation: wobble 0.6s ease 0s 1 normal forwards; +} + +.point-num-input { + width: 50px; + font-size: 12px; +} \ No newline at end of file From a09ceb29539aa0e4fa8a4a813a49397702be817d Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 11 Nov 2024 16:37:57 -0500 Subject: [PATCH 63/77] Changed implementation of provided tests so that it detects how many tests were written instead of having to type it --- src/haz3lweb/Grading.re | 29 +++++------------------------ src/haz3lweb/view/ExerciseMode.re | 1 - 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index 5610c3d0f0..7e774b870e 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -61,7 +61,6 @@ module TestValidationReport = { report: t, max_points: int, max_tests: int, - prov_tests: int, settings: Settings.t, ) => { Cell.report_footer_view([ @@ -103,21 +102,6 @@ module TestValidationReport = { ), ], ), - div( - ~attrs=[Attr.class_("input-field")], - [ - label([text("Tests provided:")]), - input( - ~attrs=[ - Attr.type_("number"), - Attr.class_("point-num-input"), - Attr.id("test-provided-input"), - Attr.value(string_of_int(prov_tests)), - ], - (), - ), - ], - ), div( ~attrs=[Attr.class_("edit-icon")], [ @@ -139,13 +123,10 @@ module TestValidationReport = { ), )##.value; let new_prov_test = - Obj.magic( - Js_of_ocaml.Js.some( - JsUtil.get_elem_by_id( - "test-provided-input", - ), - ), - )##.value; + switch (report.test_results) { + | Some(test_results) => test_results.total + | None => 0 + }; let update_events = [ inject(Set(EditingTestValRep)), @@ -153,7 +134,7 @@ module TestValidationReport = { UpdateTestValRep( int_of_string(new_test_num), int_of_string(new_dist), - int_of_string(new_prov_test), + new_prov_test, ), ), ]; diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index e2b3d1cbd6..097a5de944 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -341,7 +341,6 @@ let view = grading_report.test_validation_report, grading_report.point_distribution.test_validation, eds.your_tests.required, - eds.your_tests.provided, settings, ), ], From 4dae0f6a0b2a90d898393bd93c5c4f9a0f86ffa4 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sun, 17 Nov 2024 12:31:07 -0500 Subject: [PATCH 64/77] Prevented caret scrolling (through switch in main.apply function) when editing title --- src/haz3lschool/Exercise.re | 2 +- src/haz3lweb/Main.re | 68 ++++++++++++++++++++++-------------- src/haz3lweb/Main.rei | 1 + src/haz3lweb/UpdateAction.re | 6 ++-- src/haz3lweb/view/Page.re | 4 +-- 5 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 src/haz3lweb/Main.rei diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index e96bebdec6..c0b2684f50 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -147,7 +147,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { // how these fields are saved and loaded to and from local memory // respectively. // NOTE: It may be helpful to look at changes made in the mutant-add-delete and title-editor - // branches in the Hazel repo to see and understand where changes + // branches in the Hazel repository to see and understand where changes // were made. It is likely that new implementations of editble features // will follow a similar route. }; diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index af8058b770..51e1db527c 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -21,35 +21,49 @@ let restart_caret_animation = () => let apply = (model, action, ~schedule_action): Model.t => { restart_caret_animation(); - if (UpdateAction.is_edit(action)) { - last_edit_action := JsUtil.timestamp(); - edit_action_applied := true; - }; - if (Update.should_scroll_to_caret(action)) { - scroll_to_caret := true; - }; - last_edit_action := JsUtil.timestamp(); - switch ( - try({ - let new_model = Update.apply(model, action, ~schedule_action); - Log.update(action); - new_model; - }) { - | exc => + + let get_settings = (model: Model.t): Settings.t => model.settings; + + switch (action, get_settings(model).editing_title) { + | (UpdateAction.PerformAction(Insert(_)), true) => model + | (UpdateAction.PerformAction(Destruct(_)), true) => model + | (action, _) => + if (UpdateAction.is_edit(action)) { + last_edit_action := JsUtil.timestamp(); + edit_action_applied := true; + }; + let old_scroll = scroll_to_caret.contents; + if (Update.should_scroll_to_caret(action)) { + scroll_to_caret := true; Printf.printf( - "ERROR: Exception during apply: %s\n", - Printexc.to_string(exc), + "DEBUG: Scroll set to true for action: %s (was: %b)\n", + UpdateAction.show(action), + old_scroll, ); - Error(Exception(Printexc.to_string(exc))); - } - ) { - | Ok(model) => model - | Error(FailedToPerform(err)) => - print_endline(Update.Failure.show(FailedToPerform(err))); - model; - | Error(err) => - print_endline(Update.Failure.show(err)); - model; + }; + last_edit_action := JsUtil.timestamp(); + switch ( + try({ + let new_model = Update.apply(model, action, ~schedule_action); + Log.update(action); + new_model; + }) { + | exc => + Printf.printf( + "ERROR: Exception during apply: %s\n", + Printexc.to_string(exc), + ); + Error(Exception(Printexc.to_string(exc))); + } + ) { + | Ok(model) => model + | Error(FailedToPerform(err)) => + print_endline(Update.Failure.show(FailedToPerform(err))); + model; + | Error(err) => + print_endline(Update.Failure.show(err)); + model; + }; }; }; diff --git a/src/haz3lweb/Main.rei b/src/haz3lweb/Main.rei new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/haz3lweb/Main.rei @@ -0,0 +1 @@ + diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 05c5ee7153..9028b97f32 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -224,6 +224,7 @@ let should_scroll_to_caret = fun | Set(s_action) => switch (s_action) { + | EditingTitle => false | Mode(_) => true | Captions | SecondaryIcons @@ -235,7 +236,6 @@ let should_scroll_to_caret = | Benchmark | ContextInspector | InstructorMode - | EditingTitle | Evaluation(_) => false } | SetMeta(meta_action) => @@ -248,8 +248,6 @@ let should_scroll_to_caret = | UpdateResult(_) | ToggleStepper(_) | UpdateTitle(_) - | AddBuggyImplementation - | DeleteBuggyImplementation(_) | StepperAction(_, StepBackward | StepForward(_)) => false | FinishImportScratchpad(_) | FinishImportAll(_) @@ -261,6 +259,8 @@ let should_scroll_to_caret = | Undo | Redo | TAB + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | Startup => true | PerformAction(a) => switch (a) { diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 66ed0ea588..f354172eff 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -25,7 +25,7 @@ let key_handler = | None => Ignore | Some(action) => get_settings(model).editing_title - ? Many([inject(action)]) + ? Many([Stop_propagation, inject(action)]) : Many([Prevent_default, Stop_propagation, inject(action)]) } }; @@ -100,9 +100,7 @@ let main_view = ~inject: UpdateAction.t => Ui_effect.t(unit), {settings, editors, explainThisModel, results, ui_state, _}: Model.t, ) => { - print_endline("here, at main view, getting editor"); let editor = Editors.get_editor(editors); - print_endline("got editor!"); let cursor_info = Indicated.ci_of(editor.state.zipper, editor.state.meta.statics.info_map); let highlights = From 81e02864e64025cfede134eeeea3716d298accb3 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sun, 17 Nov 2024 14:58:36 -0500 Subject: [PATCH 65/77] fixed problems with attempting to place caret on recently deleted editor --- src/haz3lschool/Exercise.re | 5 ++--- src/haz3lweb/Main.re | 6 ------ src/haz3lweb/Main.rei | 1 - src/haz3lweb/Update.re | 1 - src/haz3lweb/view/Cell.re | 9 ++++++++- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 src/haz3lweb/Main.rei diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index c0b2684f50..0181575e13 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -551,11 +551,10 @@ module F = (ExerciseEnv: ExerciseEnv) => { index < length - 1 ? index + 1 : index - 1, ). impl - : state.eds.your_tests.tests; + : state.eds.your_impl; let pos = length > 1 - ? HiddenBugs(index < length - 1 ? index : index - 1) - : YourTestsValidation; + ? HiddenBugs(index < length - 1 ? index : index - 1) : YourImpl; let new_state = { pos, eds: { diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 51e1db527c..769d759e35 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -32,14 +32,8 @@ let apply = (model, action, ~schedule_action): Model.t => { last_edit_action := JsUtil.timestamp(); edit_action_applied := true; }; - let old_scroll = scroll_to_caret.contents; if (Update.should_scroll_to_caret(action)) { scroll_to_caret := true; - Printf.printf( - "DEBUG: Scroll set to true for action: %s (was: %b)\n", - UpdateAction.show(action), - old_scroll, - ); }; last_edit_action := JsUtil.timestamp(); switch ( diff --git a/src/haz3lweb/Main.rei b/src/haz3lweb/Main.rei deleted file mode 100644 index 8b13789179..0000000000 --- a/src/haz3lweb/Main.rei +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 65fac663e9..3522ed6aba 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -617,7 +617,6 @@ let apply = (model: Model.t, update: t, ~schedule_action): Result.t(Model.t) => }) | DeleteBuggyImplementation(index) => let editors = Editors.delete_buggy_impl(model.editors, index); - print_endline(Editors.show(editors)); Model.save_and_return({...model, editors}); }; m |> Result.map(~f=update_cached_data(~schedule_action, update)); diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 0fb56b1060..031605e69c 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -360,7 +360,14 @@ let wrong_impl_caption = (~inject, sub: string, n: int) => { [ caption("", ~rest=sub), div( - ~attrs=[Attr.class_("instructor-edit-icon")], + ~attrs=[ + Attr.class_("instructor-edit-icon"), + Attr.on_mousedown(_ => + Virtual_dom.Vdom.Effect.( + Many([Prevent_default, Stop_propagation]) + ) + ), + ], [ Widgets.button( Icons.delete, From 02f21d45d9bc63cd8898aacff0e03a6d091a8247 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 24 Nov 2024 15:50:49 -0500 Subject: [PATCH 66/77] Provided number of tests for test validation changes based on the number of tests in the editor box during instructor mode --- src/haz3lschool/Exercise.re | 51 ++++++++++++++++++------------ src/haz3lweb/Editors.re | 60 +++++++++++++++++++++++++++++------- src/haz3lweb/Grading.re | 6 ---- src/haz3lweb/Update.re | 17 +++++----- src/haz3lweb/UpdateAction.re | 2 +- 5 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index eff5b3b69a..8b00d84d1f 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -177,7 +177,13 @@ module F = (ExerciseEnv: ExerciseEnv) => { | HiddenTests => eds.hidden_tests.tests }; - let put_editor = ({pos, eds, _} as state: state, editor: Editor.t) => + let put_editor = + ( + {pos, eds, _} as state: state, + editor: Editor.t, + new_prov_test: int, + instructor_mode: bool, + ) => switch (pos) { | Prelude => { ...state, @@ -194,16 +200,29 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, } | YourTestsValidation - | YourTestsTesting => { - ...state, - eds: { - ...eds, - your_tests: { - ...eds.your_tests, - tests: editor, + | YourTestsTesting => + instructor_mode + ? { + ...state, + eds: { + ...eds, + your_tests: { + ...eds.your_tests, + tests: editor, + provided: new_prov_test, + }, + } /* Set 'provided' to 1 if editing_test_val_rep is true */ + } + : { + ...state, + eds: { + ...eds, + your_tests: { + ...eds.your_tests, + tests: editor, + }, }, - }, - } + } | YourImpl => { ...state, eds: { @@ -542,7 +561,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; let new_state = set_editing_title(new_state, editing_title); - put_editor(new_state, new_buggy_impl.impl); + put_editor(new_state, new_buggy_impl.impl, 0, false); }; let delete_buggy_impl = (state: state, index: int) => { @@ -567,7 +586,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { List.filteri((i, _) => i != index, state.eds.hidden_bugs), }, }; - put_editor(new_state, editor_on); + put_editor(new_state, editor_on, 0, false); }; let set_editing_prompt = ({eds, _} as state: state, editing: bool) => { @@ -635,19 +654,13 @@ module F = (ExerciseEnv: ExerciseEnv) => { }; let update_test_val_rep = - ( - {eds, _} as state: state, - new_test_num: int, - new_dist: int, - new_prov: int, - ) => { + ({eds, _} as state: state, new_test_num: int, new_dist: int) => { ...state, eds: { ...eds, your_tests: { ...eds.your_tests, required: new_test_num, - provided: new_prov, }, point_distribution: { ...eds.point_distribution, diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 229e411f17..8acf066980 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -37,7 +37,44 @@ let put_editor = (ed: Editor.t, eds: t): t => assert(List.mem_assoc(name, slides)); Documentation(name, slides |> ListUtil.update_assoc((name, ed))); | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.put_editor(exercise, ed)) + Exercises(n, specs, Exercise.put_editor(exercise, ed, 0, false)) + }; + +let put_editor_action = + ( + ed: Editor.t, + eds: t, + results: ModelResults.t, + ~settings: CoreSettings.t, + instructor_mode: bool, + ) + : t => + switch (eds) { + | Scratch(n, slides) => + assert(n < List.length(slides)); + Scratch(n, Util.ListUtil.put_nth(n, ed, slides)); + | Documentation(name, slides) => + assert(List.mem_assoc(name, slides)); + Documentation(name, slides |> ListUtil.update_assoc((name, ed))); + | Exercises(n, specs, exercise) => + let stitched_dynamics = + Exercise.stitch_dynamic( + settings, + exercise, + settings.dynamics ? Some(results) : None, + ); + let test_results = + ModelResult.test_results(stitched_dynamics.test_validation.result); + let new_prov_test = + switch (test_results) { + | Some(test_results) => test_results.total + | None => 0 + }; + Exercises( + n, + specs, + Exercise.put_editor(exercise, ed, new_prov_test, instructor_mode), + ); }; let update = (f: Editor.t => Editor.t, editors: t): t => @@ -47,7 +84,13 @@ let update_opt = (editors: t, f: Editor.t => option(Editor.t)): option(t) => editors |> get_editor |> f |> Option.map(put_editor(_, editors)); let perform_action = - (~settings: CoreSettings.t, editors: t, a: Action.t) + ( + ~settings: CoreSettings.t, + editors: t, + a: Action.t, + results: ModelResults.t, + instructor_mode: bool, + ) : UpdateAction.Result.t(t) => { let settings = switch (editors) { @@ -59,7 +102,8 @@ let perform_action = }; switch (Perform.go(~settings, a, get_editor(editors))) { | Error(err) => Error(FailedToPerform(err)) - | Ok(ed) => Ok(put_editor(ed, editors)) + | Ok(ed) => + Ok(put_editor_action(ed, editors, results, ~settings, instructor_mode)) }; }; @@ -182,8 +226,7 @@ let set_editing_test_val_rep = (editors: t, editing: bool): t => Exercises(n, specs, Exercise.set_editing_test_val_rep(exercise, editing)) }; -let update_test_val_rep = - (editors: t, new_test_num: int, new_dist: int, new_prov: int): t => +let update_test_val_rep = (editors: t, new_test_num: int, new_dist: int): t => switch (editors) { | Scratch(_) | Documentation(_) => editors @@ -191,12 +234,7 @@ let update_test_val_rep = Exercises( n, specs, - Exercise.update_test_val_rep( - exercise, - new_test_num, - new_dist, - new_prov, - ), + Exercise.update_test_val_rep(exercise, new_test_num, new_dist), ) }; diff --git a/src/haz3lweb/Grading.re b/src/haz3lweb/Grading.re index 7e774b870e..1661748c9b 100644 --- a/src/haz3lweb/Grading.re +++ b/src/haz3lweb/Grading.re @@ -122,11 +122,6 @@ module TestValidationReport = { ), ), )##.value; - let new_prov_test = - switch (report.test_results) { - | Some(test_results) => test_results.total - | None => 0 - }; let update_events = [ inject(Set(EditingTestValRep)), @@ -134,7 +129,6 @@ module TestValidationReport = { UpdateTestValRep( int_of_string(new_test_num), int_of_string(new_dist), - new_prov_test, ), ), ]; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index aeb0831244..6d6e13ef38 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -488,7 +488,13 @@ let ui_state_update = let apply = (model: Model.t, update: t, ~schedule_action): Result.t(Model.t) => { let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => { switch ( - Editors.perform_action(~settings=model.settings.core, model.editors, a) + Editors.perform_action( + ~settings=model.settings.core, + model.editors, + a, + model.results, + model.settings.instructor_mode, + ) ) { | Error(err) => Error(err) | Ok(editors) => Ok({...model, editors}) @@ -702,16 +708,11 @@ let apply = (model: Model.t, update: t, ~schedule_action): Result.t(Model.t) => ...model, editors: Editors.update_exercise_prompt(model.editors, new_prompt), }) - | UpdateTestValRep(new_test_num, new_dist, new_prov) => + | UpdateTestValRep(new_test_num, new_dist) => Model.save_and_return({ ...model, editors: - Editors.update_test_val_rep( - model.editors, - new_test_num, - new_dist, - new_prov, - ), + Editors.update_test_val_rep(model.editors, new_test_num, new_dist), }) | UpdateMutTestRep(new_dist) => Model.save_and_return({ diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index b60452aa32..30cd8a7f9f 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -100,7 +100,7 @@ type t = | AddBuggyImplementation | DeleteBuggyImplementation(int) | UpdatePrompt(string) - | UpdateTestValRep(int, int, int) + | UpdateTestValRep(int, int) | UpdateMutTestRep(int) | UpdateImplGrdRep(int) | UpdateModuleName(string); From 95a7593de85d10b0675301d962cd6e74a2ea2181 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 24 Nov 2024 20:40:27 -0500 Subject: [PATCH 67/77] Fixed caret scrolling following title-editor branch changes --- src/haz3lschool/Exercise.re | 2 +- src/haz3lweb/Main.re | 68 ++++++++++++++++++++++-------------- src/haz3lweb/Main.rei | 1 + src/haz3lweb/UpdateAction.re | 16 ++++----- src/haz3lweb/view/Page.re | 4 +-- 5 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 src/haz3lweb/Main.rei diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index 8b00d84d1f..b37a37eb6d 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -150,7 +150,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { // how these fields are saved and loaded to and from local memory // respectively. // NOTE: It may be helpful to look at changes made in the mutant-add-delete and title-editor - // branches in the Hazel repo to see and understand where changes + // branches in the Hazel repository to see and understand where changes // were made. It is likely that new implementations of editble features // will follow a similar route. }; diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index af8058b770..51e1db527c 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -21,35 +21,49 @@ let restart_caret_animation = () => let apply = (model, action, ~schedule_action): Model.t => { restart_caret_animation(); - if (UpdateAction.is_edit(action)) { - last_edit_action := JsUtil.timestamp(); - edit_action_applied := true; - }; - if (Update.should_scroll_to_caret(action)) { - scroll_to_caret := true; - }; - last_edit_action := JsUtil.timestamp(); - switch ( - try({ - let new_model = Update.apply(model, action, ~schedule_action); - Log.update(action); - new_model; - }) { - | exc => + + let get_settings = (model: Model.t): Settings.t => model.settings; + + switch (action, get_settings(model).editing_title) { + | (UpdateAction.PerformAction(Insert(_)), true) => model + | (UpdateAction.PerformAction(Destruct(_)), true) => model + | (action, _) => + if (UpdateAction.is_edit(action)) { + last_edit_action := JsUtil.timestamp(); + edit_action_applied := true; + }; + let old_scroll = scroll_to_caret.contents; + if (Update.should_scroll_to_caret(action)) { + scroll_to_caret := true; Printf.printf( - "ERROR: Exception during apply: %s\n", - Printexc.to_string(exc), + "DEBUG: Scroll set to true for action: %s (was: %b)\n", + UpdateAction.show(action), + old_scroll, ); - Error(Exception(Printexc.to_string(exc))); - } - ) { - | Ok(model) => model - | Error(FailedToPerform(err)) => - print_endline(Update.Failure.show(FailedToPerform(err))); - model; - | Error(err) => - print_endline(Update.Failure.show(err)); - model; + }; + last_edit_action := JsUtil.timestamp(); + switch ( + try({ + let new_model = Update.apply(model, action, ~schedule_action); + Log.update(action); + new_model; + }) { + | exc => + Printf.printf( + "ERROR: Exception during apply: %s\n", + Printexc.to_string(exc), + ); + Error(Exception(Printexc.to_string(exc))); + } + ) { + | Ok(model) => model + | Error(FailedToPerform(err)) => + print_endline(Update.Failure.show(FailedToPerform(err))); + model; + | Error(err) => + print_endline(Update.Failure.show(err)); + model; + }; }; }; diff --git a/src/haz3lweb/Main.rei b/src/haz3lweb/Main.rei new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/haz3lweb/Main.rei @@ -0,0 +1 @@ + diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 30cd8a7f9f..b611f4c44e 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -254,6 +254,12 @@ let should_scroll_to_caret = fun | Set(s_action) => switch (s_action) { + | EditingTitle => false + | EditingPrompt => false + | EditingTestValRep => false + | EditingMutTestRep => false + | EditingImplGrdRep => false + | EditingModuleName => false | Mode(_) => true | Captions | SecondaryIcons @@ -265,12 +271,6 @@ let should_scroll_to_caret = | Benchmark | ContextInspector | InstructorMode - | EditingTitle - | EditingPrompt - | EditingTestValRep - | EditingMutTestRep - | EditingImplGrdRep - | EditingModuleName | Evaluation(_) => false } | SetMeta(meta_action) => @@ -283,8 +283,6 @@ let should_scroll_to_caret = | UpdateResult(_) | ToggleStepper(_) | UpdateTitle(_) - | AddBuggyImplementation - | DeleteBuggyImplementation(_) | UpdatePrompt(_) | UpdateTestValRep(_) | UpdateMutTestRep(_) @@ -301,6 +299,8 @@ let should_scroll_to_caret = | Undo | Redo | TAB + | AddBuggyImplementation + | DeleteBuggyImplementation(_) | Startup => true | PerformAction(a) => switch (a) { diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 42e3f57ef1..ad2f6f6604 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -31,7 +31,7 @@ let key_handler = || settings.editing_mut_test_rep || settings.editing_impl_grd_rep || settings.editing_module_name - ? Many([inject(action)]) + ? Many([Stop_propagation, inject(action)]) : Many([Prevent_default, Stop_propagation, inject(action)]); } }; @@ -111,9 +111,7 @@ let main_view = ~inject: UpdateAction.t => Ui_effect.t(unit), {settings, editors, explainThisModel, results, ui_state, _}: Model.t, ) => { - print_endline("here, at main view, getting editor"); let editor = Editors.get_editor(editors); - print_endline("got editor!"); let cursor_info = Indicated.ci_of(editor.state.zipper, editor.state.meta.statics.info_map); let highlights = From 0b040a558f3e30eccb0a34409bf39ca6fe55c616 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 24 Nov 2024 20:55:10 -0500 Subject: [PATCH 68/77] Prevented caret scrolling for all editing modes and prevented caret scrolling when moving the caret with arrows --- src/haz3lweb/Main.re | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 51e1db527c..61ca50ce75 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -24,9 +24,19 @@ let apply = (model, action, ~schedule_action): Model.t => { let get_settings = (model: Model.t): Settings.t => model.settings; - switch (action, get_settings(model).editing_title) { + let settings = get_settings(model); + let editing_mode = + settings.editing_prompt + || settings.editing_title + || settings.editing_test_val_rep + || settings.editing_mut_test_rep + || settings.editing_impl_grd_rep + || settings.editing_module_name; + + switch (action, editing_mode) { | (UpdateAction.PerformAction(Insert(_)), true) => model | (UpdateAction.PerformAction(Destruct(_)), true) => model + | (UpdateAction.PerformAction(Move(_)), true) => model | (action, _) => if (UpdateAction.is_edit(action)) { last_edit_action := JsUtil.timestamp(); From 9e7900d7f444e55a97b4a95d77e1ae2631f419cc Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 24 Nov 2024 21:16:56 -0500 Subject: [PATCH 69/77] Fixed problems with attempting to place caret on recently deleted editor following title-editor branch --- src/haz3lschool/Exercise.re | 7 +++---- src/haz3lweb/Main.re | 6 ------ src/haz3lweb/Main.rei | 1 - src/haz3lweb/view/Cell.re | 9 ++++++++- 4 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 src/haz3lweb/Main.rei diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index b37a37eb6d..c6090fe827 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -211,7 +211,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { tests: editor, provided: new_prov_test, }, - } /* Set 'provided' to 1 if editing_test_val_rep is true */ + }, } : { ...state, @@ -573,11 +573,10 @@ module F = (ExerciseEnv: ExerciseEnv) => { index < length - 1 ? index + 1 : index - 1, ). impl - : state.eds.your_tests.tests; + : state.eds.your_impl; let pos = length > 1 - ? HiddenBugs(index < length - 1 ? index : index - 1) - : YourTestsValidation; + ? HiddenBugs(index < length - 1 ? index : index - 1) : YourImpl; let new_state = { pos, eds: { diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 61ca50ce75..a1dcf198b3 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -42,14 +42,8 @@ let apply = (model, action, ~schedule_action): Model.t => { last_edit_action := JsUtil.timestamp(); edit_action_applied := true; }; - let old_scroll = scroll_to_caret.contents; if (Update.should_scroll_to_caret(action)) { scroll_to_caret := true; - Printf.printf( - "DEBUG: Scroll set to true for action: %s (was: %b)\n", - UpdateAction.show(action), - old_scroll, - ); }; last_edit_action := JsUtil.timestamp(); switch ( diff --git a/src/haz3lweb/Main.rei b/src/haz3lweb/Main.rei deleted file mode 100644 index 8b13789179..0000000000 --- a/src/haz3lweb/Main.rei +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index c10d3faa0e..166e8bd200 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -360,7 +360,14 @@ let wrong_impl_caption = (~inject, sub: string, n: int) => { [ caption("", ~rest=sub), div( - ~attrs=[Attr.class_("instructor-edit-icon")], + ~attrs=[ + Attr.class_("instructor-edit-icon"), + Attr.on_mousedown(_ => + Virtual_dom.Vdom.Effect.( + Many([Prevent_default, Stop_propagation]) + ) + ), + ], [ Widgets.button( Icons.delete, From b5fe793555c784d631b577c40874b22bc4d16dc0 Mon Sep 17 00:00:00 2001 From: facundoy Date: Fri, 29 Nov 2024 15:27:39 -0500 Subject: [PATCH 70/77] Fixed bug where the number of provided tests only updated after the next action, causing unitended behavior --- src/haz3lweb/Main.re | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index a1dcf198b3..0db2b43815 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -60,7 +60,23 @@ let apply = (model, action, ~schedule_action): Model.t => { Error(Exception(Printexc.to_string(exc))); } ) { - | Ok(model) => model + | Ok(model) => + let ed = Editors.get_editor(model.editors); + let updated_model = + model.settings.instructor_mode + ? { + ...model, + editors: + Editors.put_editor_action( + ed, + model.editors, + model.results, + ~settings=model.settings.core, + model.settings.instructor_mode, + ), + } + : model; + updated_model; | Error(FailedToPerform(err)) => print_endline(Update.Failure.show(FailedToPerform(err))); model; From 038908eb8ab90d0677eaa85d32b1de413ffead3c Mon Sep 17 00:00:00 2001 From: facundoy Date: Fri, 29 Nov 2024 15:54:17 -0500 Subject: [PATCH 71/77] Cleaned up code by eliminating old changes --- src/haz3lschool/Exercise.re | 54 ++++++++++++++++--------------------- src/haz3lweb/Editors.re | 47 +++++++++++--------------------- src/haz3lweb/Main.re | 16 +++++------ src/haz3lweb/Update.re | 8 +----- 4 files changed, 47 insertions(+), 78 deletions(-) diff --git a/src/haz3lschool/Exercise.re b/src/haz3lschool/Exercise.re index c6090fe827..2b3414e494 100644 --- a/src/haz3lschool/Exercise.re +++ b/src/haz3lschool/Exercise.re @@ -177,13 +177,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { | HiddenTests => eds.hidden_tests.tests }; - let put_editor = - ( - {pos, eds, _} as state: state, - editor: Editor.t, - new_prov_test: int, - instructor_mode: bool, - ) => + let put_editor = ({pos, eds, _} as state: state, editor: Editor.t) => switch (pos) { | Prelude => { ...state, @@ -200,29 +194,16 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, } | YourTestsValidation - | YourTestsTesting => - instructor_mode - ? { - ...state, - eds: { - ...eds, - your_tests: { - ...eds.your_tests, - tests: editor, - provided: new_prov_test, - }, - }, - } - : { - ...state, - eds: { - ...eds, - your_tests: { - ...eds.your_tests, - tests: editor, - }, + | YourTestsTesting => { + ...state, + eds: { + ...eds, + your_tests: { + ...eds.your_tests, + tests: editor, }, - } + }, + } | YourImpl => { ...state, eds: { @@ -561,7 +542,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; let new_state = set_editing_title(new_state, editing_title); - put_editor(new_state, new_buggy_impl.impl, 0, false); + put_editor(new_state, new_buggy_impl.impl); }; let delete_buggy_impl = (state: state, index: int) => { @@ -585,7 +566,7 @@ module F = (ExerciseEnv: ExerciseEnv) => { List.filteri((i, _) => i != index, state.eds.hidden_bugs), }, }; - put_editor(new_state, editor_on, 0, false); + put_editor(new_state, editor_on); }; let set_editing_prompt = ({eds, _} as state: state, editing: bool) => { @@ -783,6 +764,17 @@ module F = (ExerciseEnv: ExerciseEnv) => { }, }; + let update_prov_tests = ({eds, _} as state: state, new_prov_tests: int) => { + ...state, + eds: { + ...eds, + your_tests: { + ...eds.your_tests, + provided: new_prov_tests, + }, + }, + }; + let visible_in = (pos, ~instructor_mode) => { switch (pos) { | Prelude => instructor_mode diff --git a/src/haz3lweb/Editors.re b/src/haz3lweb/Editors.re index 8acf066980..78946cdeb5 100644 --- a/src/haz3lweb/Editors.re +++ b/src/haz3lweb/Editors.re @@ -37,26 +37,13 @@ let put_editor = (ed: Editor.t, eds: t): t => assert(List.mem_assoc(name, slides)); Documentation(name, slides |> ListUtil.update_assoc((name, ed))); | Exercises(n, specs, exercise) => - Exercises(n, specs, Exercise.put_editor(exercise, ed, 0, false)) + Exercises(n, specs, Exercise.put_editor(exercise, ed)) }; -let put_editor_action = - ( - ed: Editor.t, - eds: t, - results: ModelResults.t, - ~settings: CoreSettings.t, - instructor_mode: bool, - ) - : t => +let obtain_new_prov_tests = + (eds: t, results: ModelResults.t, ~settings: CoreSettings.t): int => switch (eds) { - | Scratch(n, slides) => - assert(n < List.length(slides)); - Scratch(n, Util.ListUtil.put_nth(n, ed, slides)); - | Documentation(name, slides) => - assert(List.mem_assoc(name, slides)); - Documentation(name, slides |> ListUtil.update_assoc((name, ed))); - | Exercises(n, specs, exercise) => + | Exercises(_, _, exercise) => let stitched_dynamics = Exercise.stitch_dynamic( settings, @@ -70,11 +57,8 @@ let put_editor_action = | Some(test_results) => test_results.total | None => 0 }; - Exercises( - n, - specs, - Exercise.put_editor(exercise, ed, new_prov_test, instructor_mode), - ); + new_prov_test; + | _ => 0 }; let update = (f: Editor.t => Editor.t, editors: t): t => @@ -84,13 +68,7 @@ let update_opt = (editors: t, f: Editor.t => option(Editor.t)): option(t) => editors |> get_editor |> f |> Option.map(put_editor(_, editors)); let perform_action = - ( - ~settings: CoreSettings.t, - editors: t, - a: Action.t, - results: ModelResults.t, - instructor_mode: bool, - ) + (~settings: CoreSettings.t, editors: t, a: Action.t) : UpdateAction.Result.t(t) => { let settings = switch (editors) { @@ -102,8 +80,7 @@ let perform_action = }; switch (Perform.go(~settings, a, get_editor(editors))) { | Error(err) => Error(FailedToPerform(err)) - | Ok(ed) => - Ok(put_editor_action(ed, editors, results, ~settings, instructor_mode)) + | Ok(ed) => Ok(put_editor(ed, editors)) }; }; @@ -290,6 +267,14 @@ let update_module_name = (editors: t, new_module_name: string): t => ) }; +let update_prov_tests = (editors: t, new_prov_tests: int): t => + switch (editors) { + | Scratch(_) + | Documentation(_) => editors + | Exercises(n, specs, exercise) => + Exercises(n, specs, Exercise.update_prov_tests(exercise, new_prov_tests)) + }; + let reset_nth_slide = (~settings: CoreSettings.t, n, slides): list(Editor.t) => { let (_, init_editors, _) = Init.startup.scratch; let data = List.nth(init_editors, n); diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index 0db2b43815..e0a77ae29b 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -61,19 +61,17 @@ let apply = (model, action, ~schedule_action): Model.t => { } ) { | Ok(model) => - let ed = Editors.get_editor(model.editors); + let new_prov_tests = + Editors.obtain_new_prov_tests( + model.editors, + model.results, + ~settings=model.settings.core, + ); let updated_model = model.settings.instructor_mode ? { ...model, - editors: - Editors.put_editor_action( - ed, - model.editors, - model.results, - ~settings=model.settings.core, - model.settings.instructor_mode, - ), + editors: Editors.update_prov_tests(model.editors, new_prov_tests), } : model; updated_model; diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 6d6e13ef38..a2c8699ed0 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -488,13 +488,7 @@ let ui_state_update = let apply = (model: Model.t, update: t, ~schedule_action): Result.t(Model.t) => { let perform_action = (model: Model.t, a: Action.t): Result.t(Model.t) => { switch ( - Editors.perform_action( - ~settings=model.settings.core, - model.editors, - a, - model.results, - model.settings.instructor_mode, - ) + Editors.perform_action(~settings=model.settings.core, model.editors, a) ) { | Error(err) => Error(err) | Ok(editors) => Ok({...model, editors}) From 5dd2f0f840eec6b2d79fe1f55b9384627e0d5712 Mon Sep 17 00:00:00 2001 From: facundoy Date: Mon, 2 Dec 2024 18:20:06 -0500 Subject: [PATCH 72/77] Correctly merged dev-branch Main with PR Main --- src/haz3lweb/Main.re | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/haz3lweb/Main.re b/src/haz3lweb/Main.re index d6c92d0927..bf79046762 100644 --- a/src/haz3lweb/Main.re +++ b/src/haz3lweb/Main.re @@ -37,13 +37,27 @@ let apply = (model, action, ~schedule_action, ~schedule_autosave): Model.t => { | (UpdateAction.PerformAction(Move(_)), true) => model | (action, _) => if (UpdateAction.is_edit(action)) { - last_edit_action := JsUtil.timestamp(); - edit_action_applied := true; + schedule_autosave( + BonsaiUtil.Alarm.Action.SetAlarm( + Core.Time_ns.add( + Core.Time_ns.now(), + Core.Time_ns.Span.of_sec(1.0), + ), + ), + ); + } else { + schedule_autosave( + BonsaiUtil.Alarm.Action.SnoozeAlarm( + Core.Time_ns.add( + Core.Time_ns.now(), + Core.Time_ns.Span.of_sec(1.0), + ), + ), + ); }; if (Update.should_scroll_to_caret(action)) { scroll_to_caret := true; }; - last_edit_action := JsUtil.timestamp(); switch ( try({ let new_model = Update.apply(model, action, ~schedule_action); From c2ab67e45ea3b6a587da1b6f1e7a6a8c3e173460 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 9 Dec 2024 17:54:44 -0500 Subject: [PATCH 73/77] added placeholders for title, module name, and prompt. Should be useful for newly created exercises --- src/haz3lweb/view/ExerciseMode.re | 56 ++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 097a5de944..841483e1be 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -82,6 +82,7 @@ let view = }; let title_view = { + let title_placeholder = eds.title == "" ? "Exercise Title" : eds.title; Cell.simple_cell_view([ div( ~attrs=[Attr.class_("title-cell")], @@ -116,7 +117,15 @@ let view = : div( ~attrs=[Attr.class_("title-edit")], [ - text(eds.title), + div( + ~attrs=[ + Attr.classes([ + "title-text", + eds.title == "" ? "title-placeholder" : "", + ]), + ], + [text(title_placeholder)], + ), div( ~attrs=[Attr.class_("edit-icon")], [ @@ -146,6 +155,8 @@ let view = }; let module_name_view = { + let module_placeholder = + eds.module_name == "" ? "Exercise Module Name" : eds.module_name; settings.instructor_mode ? Cell.narrative_cell([ div( @@ -183,7 +194,14 @@ let view = ~attrs=[Attr.class_("module-name-text")], [ text("Module name: "), - text(eds.module_name), + div( + ~attrs=[ + Attr.classes([ + eds.module_name == "" ? "module-placeholder" : "", + ]), + ], + [text(module_placeholder)], + ), div( ~attrs=[Attr.class_("edit-icon")], [ @@ -213,16 +231,9 @@ let view = }; let prompt_view = { + let prompt_placeholder = eds.prompt == "" ? "Exercise Prompt" : eds.prompt; let (msg, _) = - ExplainThis.mk_translation(~inject=Some(inject), eds.prompt); - let new_msg = - msg - @ [ - div( - ~attrs=[Attr.class_("edit-icon")], - [Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)))], - ), - ]; + ExplainThis.mk_translation(~inject=Some(inject), prompt_placeholder); Cell.narrative_cell([ div( ~attrs=[Attr.class_("cell-prompt")], @@ -253,7 +264,28 @@ let view = ), ], ) - : div(~attrs=[Attr.class_("prompt-content")], new_msg) + : div( + ~attrs=[Attr.class_("prompt-edit")], + [ + div( + ~attrs=[ + Attr.classes([ + "prompt-content", + eds.prompt == "" ? "prompt-placeholder" : "", + ]), + ], + msg, + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject(Set(EditingPrompt)) + ), + ], + ), + ], + ) : div(~attrs=[Attr.class_("prompt-content")], msg), ], ), From 2266895f081993d0a5e7261b9921e5ef549daf68 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 9 Dec 2024 17:56:21 -0500 Subject: [PATCH 74/77] added placeholders for title, module name, and prompt. Should be useful for newly created exercises --- src/haz3lweb/www/style/cell.css | 68 +++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index e4c5849e0e..ec0d1b53a1 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -62,7 +62,13 @@ color: var(--BR4); } -.title-edit .edit-icon { +.title-cell .title-placeholder { + font-style: italic; + color: var(--BR4); + opacity: 0.5; +} + +.edit-icon { margin-left: 0.5em; cursor: pointer; fill: #7a6219; @@ -81,10 +87,6 @@ animation: wobble 0.6s ease 0s 1 normal forwards; } -.cell-prompt { - padding: 1em; -} - .wrong-impl-cell-caption { flex-grow: 1; display: flex; @@ -137,6 +139,46 @@ .cell-module-name { padding: 1em; + display: flex; + align-items: center; + gap: 0.5em; +} + +.module-name-text { + display: flex; + align-items: center; + gap: 0.5em; +} + +.module-name-edit { + display: flex; + align-items: center; + gap: 0.5em; +} + +.module-name-edit label { + white-space: nowrap; +} + +.module-name-edit input { + margin: 0 0.5em; +} + +.module-name-edit .edit-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5em; +} + +.module-placeholder { + display: inline; + font-style: italic; + color: var(--BR4); + opacity: 0.5; +} + +.cell-prompt { + padding: 1em; } .prompt-edit .prompt-text { @@ -150,11 +192,23 @@ } .cell-prompt .prompt-edit { - padding: 1em; - font-size: 1rem; + font-size: 1.5rem; + font-weight: bold; color: var(--light-text-color); + flex-grow: 1; + display: flex; + align-items: center; } +.cell-prompt .prompt-placeholder { + font-style: italic; + color: var(--BR4); + opacity: 0.5; +} + +.prompt-content { + font-size: 1rem; +} .edit-icon { margin-left: 0.5em; From 509238384e350fcdea87438c23e20e34fea17325 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 10 Dec 2024 15:44:13 -0500 Subject: [PATCH 75/77] changed the placeholders to more common placeholder names --- src/haz3lweb/view/ExerciseMode.re | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 841483e1be..141aab6a39 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -82,7 +82,7 @@ let view = }; let title_view = { - let title_placeholder = eds.title == "" ? "Exercise Title" : eds.title; + let title_placeholder = eds.title == "" ? "Untitled Exercise" : eds.title; Cell.simple_cell_view([ div( ~attrs=[Attr.class_("title-cell")], @@ -156,7 +156,7 @@ let view = let module_name_view = { let module_placeholder = - eds.module_name == "" ? "Exercise Module Name" : eds.module_name; + eds.module_name == "" ? "Unnamed Module" : eds.module_name; settings.instructor_mode ? Cell.narrative_cell([ div( @@ -231,7 +231,7 @@ let view = }; let prompt_view = { - let prompt_placeholder = eds.prompt == "" ? "Exercise Prompt" : eds.prompt; + let prompt_placeholder = eds.prompt == "" ? "Empty Prompt" : eds.prompt; let (msg, _) = ExplainThis.mk_translation(~inject=Some(inject), prompt_placeholder); Cell.narrative_cell([ From dd717ae5417e07f6041077b65b1b8b563713ae91 Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 15 Dec 2024 15:19:54 -0500 Subject: [PATCH 76/77] Fixed a bug where double line breaks in the markdown translation was not being handled properly --- src/haz3lweb/view/ExplainThis.re | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/haz3lweb/view/ExplainThis.re b/src/haz3lweb/view/ExplainThis.re index 3ab1065785..4d6953ce11 100644 --- a/src/haz3lweb/view/ExplainThis.re +++ b/src/haz3lweb/view/ExplainThis.re @@ -185,7 +185,9 @@ let mk_translation = (~inject, text: string): (list(Node.t), ColorSteps.t) => { List.fold_left( ((msg, mapping), elem) => { switch (elem) { - | Omd.Paragraph(_, d) => translate_inline(d, msg, mapping, ~inject) + | Omd.Paragraph(_, d) => + let (n, _) = translate_inline(d, [], mapping, ~inject); + (List.append(msg, [Node.p(n)]), mapping); | Omd.List(_, _, _, items) => let (bullets, mapping) = List.fold_left( From be6a5710fe1c5af6f3575cb60b03dde4762adcce Mon Sep 17 00:00:00 2001 From: facundoy Date: Sun, 15 Dec 2024 15:57:29 -0500 Subject: [PATCH 77/77] Fixed styling issues --- src/haz3lweb/view/ExerciseMode.re | 2 +- src/haz3lweb/www/style/cell.css | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 141aab6a39..a856f6f051 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -277,7 +277,7 @@ let view = msg, ), div( - ~attrs=[Attr.class_("edit-icon")], + ~attrs=[Attr.class_("edit-pencil")], [ Widgets.button(Icons.pencil, _ => inject(Set(EditingPrompt)) diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index ec0d1b53a1..35aa18e12b 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -200,6 +200,14 @@ align-items: center; } +.cell-prompt .prompt-edit .edit-pencil { + align-self: top; +} + +.cell-prompt .prompt-edit .edit-icon { + align-self: flex-start; +} + .cell-prompt .prompt-placeholder { font-style: italic; color: var(--BR4); @@ -210,15 +218,17 @@ font-size: 1rem; } -.edit-icon { +.edit-icon, +.edit-pencil { margin-left: 0.5em; cursor: pointer; fill: #7a6219; - display: inline-flex; + display: inline; vertical-align: top; } -.edit-icon:hover { +.edit-icon:hover, +.edit-pencil:hover { animation: wobble 0.6s ease 0s 1 normal forwards; }