From 99890979bb451530a4078427227cdecc926ced2f Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Thu, 13 Jun 2024 13:14:20 -0400 Subject: [PATCH 01/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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 30c10357656761c8af6d0b491330f78a98282f80 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Mon, 5 Aug 2024 11:58:38 -0400 Subject: [PATCH 19/26] 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 ebdaefb335043ead08bbd35b0faafbe146c86a76 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Tue, 6 Aug 2024 11:44:30 -0400 Subject: [PATCH 20/26] 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 21/26] 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 229b96923a6d2b11091627d81c8b3fcd31995ba4 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Sat, 17 Aug 2024 15:38:18 -0400 Subject: [PATCH 22/26] 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 23/26] 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 24/26] 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 a938192778701b4d9b291d2b263e330d8d3983ec Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Fri, 27 Sep 2024 13:46:28 -0400 Subject: [PATCH 25/26] 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 1b89fd3917d999b4aa5bf2d1bd89df8e90658559 Mon Sep 17 00:00:00 2001 From: russell-rozenbaum Date: Fri, 11 Oct 2024 15:17:02 -0400 Subject: [PATCH 26/26] 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 =