From 49cf3f96b3084e184b88aef504579aa10c7b6c57 Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Tue, 11 Oct 2022 01:16:38 -0400 Subject: [PATCH 1/6] Enable title edit --- src/haz3lweb/SchoolExercise.re | 116 ++++++++++++++---- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 30 ++++- .../exercises/Ex_RecursiveFibonacci.ml | 32 ++++- src/haz3lweb/view/SchoolMode.re | 29 ++++- 4 files changed, 179 insertions(+), 28 deletions(-) diff --git a/src/haz3lweb/SchoolExercise.re b/src/haz3lweb/SchoolExercise.re index aa3d1c684d..80237d4ade 100644 --- a/src/haz3lweb/SchoolExercise.re +++ b/src/haz3lweb/SchoolExercise.re @@ -1,6 +1,8 @@ open Virtual_dom.Vdom; open Sexplib.Std; open Haz3lcore; +open Zipper; +open Editor; [@deriving (show({with_path: false}), sexp, yojson)] type wrong_impl('code) = { @@ -36,7 +38,7 @@ let validate_point_distribution = [@deriving (show({with_path: false}), sexp, yojson)] type p('code) = { next_id: Id.t, - title: string, + title: 'code, version: int, module_name: string, prompt: @@ -50,11 +52,25 @@ type p('code) = { hidden_tests: hidden_tests('code), }; +let get_title_from_zipper = (title: Zipper.t): string => { + let (seg1, seg2) = title.relatives.siblings; + let seg_to_str = p => + switch (p) { + | Base.Tile(tile) => List.hd(tile.label) + | _ => " " + }; + seg1 @ seg2 |> List.map(seg_to_str) |> String.concat(""); +}; + [@deriving (show({with_path: false}), sexp, yojson)] type key = (string, int); -let key_of = p => { - (p.title, p.version); +let key_of = (p: p('code)) => { + (get_title_from_zipper(p.title), p.version); +}; + +let key_of_editor = (p: p('code)) => { + (get_title_from_zipper(p.title.state.zipper), p.version); }; let find_key_opt = (key, specs: list(p('code))) => { @@ -63,6 +79,7 @@ let find_key_opt = (key, specs: list(p('code))) => { [@deriving (show({with_path: false}), sexp, yojson)] type pos = + | Title | Prelude | CorrectImpl | YourTestsValidation @@ -80,7 +97,7 @@ type transitionary_spec = p(CodeString.t); let map = (p: p('a), f: 'a => 'b): p('b) => { { next_id: p.next_id, - title: p.title, + title: f(p.title), version: p.version, module_name: p.module_name, prompt: p.prompt, @@ -117,7 +134,7 @@ type state = { eds, }; -let key_of_state = ({eds, _}) => key_of(eds); +let key_of_state = ({eds, _}) => key_of_editor(eds); [@deriving (show({with_path: false}), sexp, yojson)] type persistent_state = (pos, Id.t, list((pos, PersistentZipper.t))); @@ -125,6 +142,7 @@ type persistent_state = (pos, Id.t, list((pos, PersistentZipper.t))); let editor_of_state: state => Editor.t = ({pos, eds, _}) => switch (pos) { + | Title => eds.title | Prelude => eds.prelude | CorrectImpl => eds.correct_impl | YourTestsValidation => eds.your_tests.tests @@ -141,6 +159,14 @@ let id_of_state = ({eds, _}: state): Id.t => { let put_editor_and_id = ({pos, eds, _} as state: state, next_id, editor: Editor.t) => switch (pos) { + | Title => { + ...state, + eds: { + ...eds, + next_id, + title: editor, + }, + } | Prelude => { ...state, eds: { @@ -205,6 +231,7 @@ let put_editor_and_id = let editors = ({eds, _}: state) => [ + eds.title, eds.prelude, eds.correct_impl, eds.your_tests.tests, @@ -215,7 +242,14 @@ let editors = ({eds, _}: state) => @ [eds.hidden_tests.tests]; let editor_positions = ({eds, _}: state) => - [Prelude, CorrectImpl, YourTestsTesting, YourTestsValidation, YourImpl] + [ + Title, + Prelude, + CorrectImpl, + YourTestsTesting, + YourTestsValidation, + YourImpl, + ] @ List.mapi((i, _) => HiddenBugs(i), eds.hidden_bugs) @ [HiddenTests]; @@ -224,33 +258,35 @@ let positioned_editors = state => let idx_of_pos = (pos, p: p('code)) => switch (pos) { - | Prelude => 0 - | CorrectImpl => 1 - | YourTestsTesting => 2 - | YourTestsValidation => 3 - | YourImpl => 4 + | Title => 0 + | Prelude => 1 + | CorrectImpl => 2 + | YourTestsTesting => 3 + | YourTestsValidation => 4 + | YourImpl => 5 | HiddenBugs(i) => if (i < List.length(p.hidden_bugs)) { - 5 + i; + 6 + i; } else { failwith("invalid hidden bug index"); } - | HiddenTests => 5 + List.length(p.hidden_bugs) + | HiddenTests => 6 + List.length(p.hidden_bugs) }; let pos_of_idx = (p: p('code), idx: int) => switch (idx) { - | 0 => Prelude - | 1 => CorrectImpl - | 2 => YourTestsTesting - | 3 => YourTestsValidation - | 4 => YourImpl + | 0 => Title + | 1 => Prelude + | 2 => CorrectImpl + | 3 => YourTestsTesting + | 4 => YourTestsValidation + | 5 => YourImpl | _ => if (idx < 0) { failwith("negative idx"); - } else if (idx < 5 + List.length(p.hidden_bugs)) { - HiddenBugs(idx - 5); - } else if (idx == 5 + List.length(p.hidden_bugs)) { + } else if (idx < 6 + List.length(p.hidden_bugs)) { + HiddenBugs(idx - 6); + } else if (idx == 6 + List.length(p.hidden_bugs)) { HiddenTests; } else { failwith("element idx"); @@ -287,6 +323,7 @@ let transition: transitionary_spec => spec = }, ) => { let id = 0; + let (id, title) = zipper_of_code(id, title); let (id, prelude) = zipper_of_code(id, prelude); let (id, correct_impl) = zipper_of_code(id, correct_impl); let (id, your_tests) = { @@ -346,6 +383,7 @@ let eds_of_spec: spec => eds = hidden_tests, }, ) => { + let title = editor_of_serialization(title); let prelude = editor_of_serialization(prelude); let correct_impl = editor_of_serialization(correct_impl); let your_tests = { @@ -459,6 +497,7 @@ let set_instructor_mode = ({eds, _} as state: state, new_mode: bool) => { let visible_in = (pos, ~instructor_mode) => { switch (pos) { + | Title => instructor_mode | Prelude => instructor_mode | CorrectImpl => instructor_mode | YourTestsValidation => true @@ -501,6 +540,7 @@ let unpersist_state = (id, editor_of_serialization(default)); }; let id = next_id; + let (id, title) = lookup(id, Title, spec.title); let (id, prelude) = lookup(id, Prelude, spec.prelude); let (id, correct_impl) = lookup(id, CorrectImpl, spec.correct_impl); let (id, your_tests_tests) = @@ -523,7 +563,7 @@ let unpersist_state = pos, eds: { next_id: id, - title: spec.title, + title, version: spec.version, module_name: spec.module_name, prompt: spec.prompt, @@ -759,6 +799,7 @@ let focus = (state: state, stitched_dynamics: stitched(DynamicsItem.t)) => { let (focal_zipper, focal_info_map) = switch (pos) { + | Title => (eds.title.state.zipper, instructor.info_map) | Prelude => (eds.prelude.state.zipper, instructor.info_map) | CorrectImpl => (eds.correct_impl.state.zipper, instructor.info_map) | YourTestsValidation => ( @@ -819,6 +860,34 @@ let export_transitionary_module = (module_name, {eds, _}: state) => { data; }; +let put_title = (title: string, id: int): Zipper.t => { + selection: { + focus: Left, + content: [], + }, + backpack: [], + relatives: { + siblings: ( + [ + Tile({ + id, + label: [title], + mold: { + out: Exp, + in_: [], + nibs: ({shape: Convex, sort: Exp}, {shape: Convex, sort: Exp}), + }, + shards: [0], + children: [], + }), + ], + [], + ), + ancestors: [], + }, + caret: Outer, +}; + let blank_spec = ( ~title, @@ -828,7 +897,8 @@ let blank_spec = ~provided_tests, ~num_wrong_impls, ) => { - let id = 0; + let title = put_title(title, 0); + let id = 1; let (id, prelude) = Zipper.next_blank(id); let (id, correct_impl) = Zipper.next_blank(id); let (id, your_tests_tests) = Zipper.next_blank(id); diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index 9bbac700bb..6f9bda2900 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -3,7 +3,35 @@ let prompt = Ex_OddlyRecursive_prompt.prompt let exercise : SchoolExercise.spec = { next_id = 5134; - title = "Oddly Recursive"; + title = + { + selection = { focus = Left; content = [] }; + backpack = []; + relatives = + { + siblings = + ( [ + Tile + { + id = 5201; + label = [ "Oddly Recursive" ]; + mold = + { + out = Exp; + in_ = []; + nibs = + ( { shape = Convex; sort = Exp }, + { shape = Convex; sort = Exp } ); + }; + shards = [ 0 ]; + children = []; + }; + ], + [] ); + ancestors = []; + }; + caret = Outer; + }; version = 1; module_name = "Ex_OddlyRecursive"; prompt; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index 4166348a0f..8a29509010 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -3,7 +3,35 @@ let prompt = Ex_RecursiveFibonacci_prompt.prompt let exercise : SchoolExercise.spec = { next_id = 813; - title = "Recursive Fibonacci"; + title = + { + selection = { focus = Left; content = [] }; + backpack = []; + relatives = + { + siblings = + ( [ + Tile + { + id = 5135; + label = [ "Recursive Fibonacci" ]; + mold = + { + out = Exp; + in_ = []; + nibs = + ( { shape = Convex; sort = Exp }, + { shape = Convex; sort = Exp } ); + }; + shards = [ 0 ]; + children = []; + }; + ], + [] ); + ancestors = []; + }; + caret = Outer; + }; version = 1; module_name = "Ex_RecursiveFibonacci"; prompt; @@ -15,7 +43,7 @@ let exercise : SchoolExercise.spec = backpack = []; relatives = { - siblings = ([ Grout { id = 0; shape = Convex } ], []); + siblings = ([ Grout { id = 1; shape = Convex } ], []); ancestors = []; }; caret = Outer; diff --git a/src/haz3lweb/view/SchoolMode.re b/src/haz3lweb/view/SchoolMode.re index 8cfdf5f843..191ce954da 100644 --- a/src/haz3lweb/view/SchoolMode.re +++ b/src/haz3lweb/view/SchoolMode.re @@ -67,7 +67,31 @@ let view = ); }; - let title_view = Cell.title_cell(eds.title); + let title_view = + settings.instructor_mode + ? render_cells( + settings, + [ + Always( + editor_view( + Title, + ~selected=pos == Title, + ~code_id="title", + ~caption=Cell.bolded_caption("Title"), + ~info_map=user_tests.info_map, // TODO this is wrong for top-level let types + ~test_results= + ModelResult.unwrap_test_results(user_tests.simple_result), + ~footer=None, + eds.title, + ), + ), + ], + ) + : [ + Cell.title_cell( + SchoolExercise.get_title_from_zipper(eds.title.state.zipper), + ), + ]; let prompt_view = Cell.narrative_cell( @@ -308,7 +332,8 @@ let view = div( ~attr=Attr.classes(["editor", "column"]), - [title_view, prompt_view] + title_view + @ [prompt_view] @ render_cells( settings, [ From f80f75f368d01adc559fe90d2f8dfcab301d183b Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Thu, 13 Oct 2022 13:23:39 -0400 Subject: [PATCH 2/6] fix --- src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 2 +- src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index 6f9bda2900..828d288fb8 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -14,7 +14,7 @@ let exercise : SchoolExercise.spec = Tile { id = 5201; - label = [ "Oddly Recursive" ]; + label = [ "OddlyRecursive" ]; mold = { out = Exp; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index 8a29509010..d94aee567b 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -14,7 +14,7 @@ let exercise : SchoolExercise.spec = Tile { id = 5135; - label = [ "Recursive Fibonacci" ]; + label = [ "RecursiveFibonacci" ]; mold = { out = Exp; From 48284d286740b3396a77f766829c63e836e1b74c Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Tue, 8 Nov 2022 14:43:12 -0500 Subject: [PATCH 3/6] Add title editor --- src/haz3lweb/Log.re | 2 + src/haz3lweb/SchoolExercise.re | 86 +++++-------------- src/haz3lweb/Update.re | 19 ++++ src/haz3lweb/UpdateAction.re | 2 + src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 30 +------ .../exercises/Ex_RecursiveFibonacci.ml | 30 +------ src/haz3lweb/view/Cell.re | 32 +++++++ src/haz3lweb/view/Page.re | 20 ++++- src/haz3lweb/view/SchoolMode.re | 45 +++++----- src/haz3lweb/www/style.css | 6 ++ 10 files changed, 124 insertions(+), 148 deletions(-) diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index 7c18926ce8..837e0c9076 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -36,6 +36,8 @@ let is_action_logged: Update.t => bool = | ResetSlide | ToggleMode | SwitchSlide(_) + | SwitchTextEditor + | UpdateTitle(_) | SwitchEditor(_) | PerformAction(_) | FailedInput(_) diff --git a/src/haz3lweb/SchoolExercise.re b/src/haz3lweb/SchoolExercise.re index 80237d4ade..e898a126b2 100644 --- a/src/haz3lweb/SchoolExercise.re +++ b/src/haz3lweb/SchoolExercise.re @@ -1,7 +1,6 @@ open Virtual_dom.Vdom; open Sexplib.Std; open Haz3lcore; -open Zipper; open Editor; [@deriving (show({with_path: false}), sexp, yojson)] @@ -38,7 +37,7 @@ let validate_point_distribution = [@deriving (show({with_path: false}), sexp, yojson)] type p('code) = { next_id: Id.t, - title: 'code, + title: string, version: int, module_name: string, prompt: @@ -52,25 +51,11 @@ type p('code) = { hidden_tests: hidden_tests('code), }; -let get_title_from_zipper = (title: Zipper.t): string => { - let (seg1, seg2) = title.relatives.siblings; - let seg_to_str = p => - switch (p) { - | Base.Tile(tile) => List.hd(tile.label) - | _ => " " - }; - seg1 @ seg2 |> List.map(seg_to_str) |> String.concat(""); -}; - [@deriving (show({with_path: false}), sexp, yojson)] type key = (string, int); -let key_of = (p: p('code)) => { - (get_title_from_zipper(p.title), p.version); -}; - -let key_of_editor = (p: p('code)) => { - (get_title_from_zipper(p.title.state.zipper), p.version); +let key_of = p => { + (p.title, p.version); }; let find_key_opt = (key, specs: list(p('code))) => { @@ -97,7 +82,7 @@ type transitionary_spec = p(CodeString.t); let map = (p: p('a), f: 'a => 'b): p('b) => { { next_id: p.next_id, - title: f(p.title), + title: p.title, version: p.version, module_name: p.module_name, prompt: p.prompt, @@ -134,7 +119,7 @@ type state = { eds, }; -let key_of_state = ({eds, _}) => key_of_editor(eds); +let key_of_state = ({eds, _}) => key_of(eds); [@deriving (show({with_path: false}), sexp, yojson)] type persistent_state = (pos, Id.t, list((pos, PersistentZipper.t))); @@ -142,7 +127,7 @@ type persistent_state = (pos, Id.t, list((pos, PersistentZipper.t))); let editor_of_state: state => Editor.t = ({pos, eds, _}) => switch (pos) { - | Title => eds.title + | Title => eds.prelude | Prelude => eds.prelude | CorrectImpl => eds.correct_impl | YourTestsValidation => eds.your_tests.tests @@ -164,7 +149,6 @@ let put_editor_and_id = eds: { ...eds, next_id, - title: editor, }, } | Prelude => { @@ -231,7 +215,6 @@ let put_editor_and_id = let editors = ({eds, _}: state) => [ - eds.title, eds.prelude, eds.correct_impl, eds.your_tests.tests, @@ -242,14 +225,7 @@ let editors = ({eds, _}: state) => @ [eds.hidden_tests.tests]; let editor_positions = ({eds, _}: state) => - [ - Title, - Prelude, - CorrectImpl, - YourTestsTesting, - YourTestsValidation, - YourImpl, - ] + [Prelude, CorrectImpl, YourTestsTesting, YourTestsValidation, YourImpl] @ List.mapi((i, _) => HiddenBugs(i), eds.hidden_bugs) @ [HiddenTests]; @@ -298,6 +274,16 @@ let switch_editor = (idx: int, {eds, _}) => { eds, }; +let switch_text_editor = ({eds, _}) => {pos: Title, eds}; + +let update_title = ({eds, pos}, title) => { + pos, + eds: { + ...eds, + title, + }, +}; + let zipper_of_code = (id, code) => { switch (Printer.zipper_of_string(id, code)) { | None => failwith("Transition failed.") @@ -323,7 +309,6 @@ let transition: transitionary_spec => spec = }, ) => { let id = 0; - let (id, title) = zipper_of_code(id, title); let (id, prelude) = zipper_of_code(id, prelude); let (id, correct_impl) = zipper_of_code(id, correct_impl); let (id, your_tests) = { @@ -383,7 +368,6 @@ let eds_of_spec: spec => eds = hidden_tests, }, ) => { - let title = editor_of_serialization(title); let prelude = editor_of_serialization(prelude); let correct_impl = editor_of_serialization(correct_impl); let your_tests = { @@ -540,7 +524,6 @@ let unpersist_state = (id, editor_of_serialization(default)); }; let id = next_id; - let (id, title) = lookup(id, Title, spec.title); let (id, prelude) = lookup(id, Prelude, spec.prelude); let (id, correct_impl) = lookup(id, CorrectImpl, spec.correct_impl); let (id, your_tests_tests) = @@ -563,7 +546,7 @@ let unpersist_state = pos, eds: { next_id: id, - title, + title: spec.title, version: spec.version, module_name: spec.module_name, prompt: spec.prompt, @@ -799,7 +782,7 @@ let focus = (state: state, stitched_dynamics: stitched(DynamicsItem.t)) => { let (focal_zipper, focal_info_map) = switch (pos) { - | Title => (eds.title.state.zipper, instructor.info_map) + | Title => (eds.prelude.state.zipper, instructor.info_map) | Prelude => (eds.prelude.state.zipper, instructor.info_map) | CorrectImpl => (eds.correct_impl.state.zipper, instructor.info_map) | YourTestsValidation => ( @@ -860,34 +843,6 @@ let export_transitionary_module = (module_name, {eds, _}: state) => { data; }; -let put_title = (title: string, id: int): Zipper.t => { - selection: { - focus: Left, - content: [], - }, - backpack: [], - relatives: { - siblings: ( - [ - Tile({ - id, - label: [title], - mold: { - out: Exp, - in_: [], - nibs: ({shape: Convex, sort: Exp}, {shape: Convex, sort: Exp}), - }, - shards: [0], - children: [], - }), - ], - [], - ), - ancestors: [], - }, - caret: Outer, -}; - let blank_spec = ( ~title, @@ -897,8 +852,7 @@ let blank_spec = ~provided_tests, ~num_wrong_impls, ) => { - let title = put_title(title, 0); - let id = 1; + let id = 0; let (id, prelude) = Zipper.next_blank(id); let (id, correct_impl) = Zipper.next_blank(id); let (id, your_tests_tests) = Zipper.next_blank(id); diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index ae493916d1..2af807857c 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -160,6 +160,8 @@ let reevaluate_post_update = | FinishImportAll(_) | FinishImportScratchpad(_) | ResetSlide + | SwitchTextEditor + | UpdateTitle(_) | SwitchEditor(_) | SwitchSlide(_) | ToggleMode @@ -297,6 +299,23 @@ let apply = Ok({...model, editors: School(n, specs, exercise)}); } } + + | SwitchTextEditor => + switch (model.editors) { + | Scratch(_) => assert(false) + | School(m, specs, exercise) => + let exercise = SchoolExercise.switch_text_editor(exercise); + Ok({...model, editors: School(m, specs, exercise)}); + } + + | UpdateTitle(title) => + switch (model.editors) { + | Scratch(_) => assert(false) + | School(m, specs, exercise) => + let exercise = SchoolExercise.update_title(exercise, title); + Ok({...model, editors: School(m, specs, exercise)}); + } + | SwitchEditor(n) => switch (model.editors) { | Scratch(_) => Error(FailedToSwitch) // one editor per scratch diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index a20c9bfee8..afaa73fb32 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -27,6 +27,8 @@ type t = | Save | ToggleMode | SwitchSlide(int) + | SwitchTextEditor + | UpdateTitle(string) | SwitchEditor(int) | SetFontMetrics(FontMetrics.t) | SetLogoFontMetrics(FontMetrics.t) diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index 828d288fb8..9bbac700bb 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -3,35 +3,7 @@ let prompt = Ex_OddlyRecursive_prompt.prompt let exercise : SchoolExercise.spec = { next_id = 5134; - title = - { - selection = { focus = Left; content = [] }; - backpack = []; - relatives = - { - siblings = - ( [ - Tile - { - id = 5201; - label = [ "OddlyRecursive" ]; - mold = - { - out = Exp; - in_ = []; - nibs = - ( { shape = Convex; sort = Exp }, - { shape = Convex; sort = Exp } ); - }; - shards = [ 0 ]; - children = []; - }; - ], - [] ); - ancestors = []; - }; - caret = Outer; - }; + title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; prompt; diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index d94aee567b..ed5ff34c3a 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -3,35 +3,7 @@ let prompt = Ex_RecursiveFibonacci_prompt.prompt let exercise : SchoolExercise.spec = { next_id = 813; - title = - { - selection = { focus = Left; content = [] }; - backpack = []; - relatives = - { - siblings = - ( [ - Tile - { - id = 5135; - label = [ "RecursiveFibonacci" ]; - mold = - { - out = Exp; - in_ = []; - nibs = - ( { shape = Convex; sort = Exp }, - { shape = Convex; sort = Exp } ); - }; - shards = [ 0 ]; - children = []; - }; - ], - [] ); - ancestors = []; - }; - caret = Outer; - }; + title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; prompt; diff --git a/src/haz3lweb/view/Cell.re b/src/haz3lweb/view/Cell.re index 96f1de4183..b713205aec 100644 --- a/src/haz3lweb/view/Cell.re +++ b/src/haz3lweb/view/Cell.re @@ -122,6 +122,38 @@ let code_cell_view = ); }; +let text_cell_view = (~attrs, ~selected, ~caption, ~text) => + Node.div( + ~attr=attrs, + [ + Node.div( + ~attr=Attr.class_("cell-container"), + [ + Node.div( + ~attr= + Attr.many([ + Attr.classes( + ["cell-item", "cell"] + @ (selected ? ["selected"] : ["deselected"]), + ), + ]), + [ + bolded_caption(caption), + Node.textarea( + ~attr= + Attr.many([ + Attr.id("textarea-container"), + Attr.classes(["textarea-container"]), + ]), + [Node.text(text)], + ), + ], + ), + ], + ), + ], + ); + let test_status_icon_view = (~font_metrics, insts, ms: Measured.Shards.t): option(Node.t) => switch (ms) { diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 1d6e0f660f..004b19f6a4 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -250,6 +250,22 @@ let get_selection = (model: Model.t): string => let view = (~inject, ~handlers, model: Model.t) => { let main_ui = main_ui_view(~inject, model); + + let additional_attrs = { + switch (model.editors) { + | School(_, _, exercise) => + let SchoolExercise.{pos, _} = exercise; + if (pos == Title) { + print_endline("additional_attrs"); + []; + } else { + print_endline("no additional_attrs"); + handlers(~inject, ~model); + }; + | _ => handlers(~inject, ~model) + }; + }; + div( ~attr= Attr.many( @@ -280,8 +296,8 @@ let view = (~inject, ~handlers, model: Model.t) => { Dom.preventDefault(evt); inject(UpdateAction.Paste(pasted_text)); }), - ...handlers(~inject, ~model), - ], + ] + @ additional_attrs, ), [ FontSpecimen.view("font-specimen"), diff --git a/src/haz3lweb/view/SchoolMode.re b/src/haz3lweb/view/SchoolMode.re index 6ee53b97f4..d6447d36c8 100644 --- a/src/haz3lweb/view/SchoolMode.re +++ b/src/haz3lweb/view/SchoolMode.re @@ -99,32 +99,33 @@ let view = ~color_highlighting, ); }; + let mousedown_handler = (~inject) => { + Virtual_dom.Vdom.Effect.Many( + List.map(inject, Update.[Mousedown, Update.SwitchTextEditor]), + ); + }; + + let input_handler = (~inject, title) => { + Virtual_dom.Vdom.Effect.Many( + List.map(inject, [Update.UpdateTitle(title)]), + ); + }; let title_view = settings.instructor_mode - ? render_cells( - settings, - [ - Always( - editor_view( - Title, - ~selected=pos == Title, - ~code_id="title", - ~caption=Cell.bolded_caption("Title"), - ~info_map=user_tests.info_map, // TODO this is wrong for top-level let types - ~test_results= - ModelResult.unwrap_test_results(user_tests.simple_result), - ~footer=None, - eds.title, - ), - ), - ], - ) - : [ - Cell.title_cell( - SchoolExercise.get_title_from_zipper(eds.title.state.zipper), + ? [ + Cell.text_cell_view( + ~attrs= + Attr.many([ + Attr.on_mousedown(_ => mousedown_handler(~inject)), + Attr.on_input(_ => input_handler(~inject)), + ]), + ~selected=pos == Title, + ~caption="Title", + ~text=eds.title, ), - ]; + ] + : [Cell.title_cell(eds.title)]; let prompt_view = Cell.narrative_cell( diff --git a/src/haz3lweb/www/style.css b/src/haz3lweb/www/style.css index c60cd27236..e73b8feb72 100644 --- a/src/haz3lweb/www/style.css +++ b/src/haz3lweb/www/style.css @@ -473,6 +473,12 @@ body { z-index: 9999; } +.textarea-container { + background-color: transparent; + padding-left: 1em; + padding-right: 1em; +} + .code-container { position: relative; } From a8d4c0fddb1ea9609f6ccf4f3ee6497f520a9963 Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Tue, 8 Nov 2022 14:43:36 -0500 Subject: [PATCH 4/6] Add prompt editor --- src/haz3lweb/Log.re | 1 + src/haz3lweb/SchoolExercise.re | 14 ++++++--- src/haz3lweb/Update.re | 9 ++++++ src/haz3lweb/UpdateAction.re | 1 + src/haz3lweb/exercises/Ex_OddlyRecursive.ml | 6 ++-- .../exercises/Ex_RecursiveFibonacci.ml | 6 ++-- src/haz3lweb/view/Page.re | 8 +---- src/haz3lweb/view/SchoolMode.re | 31 +++++++++++++++++-- 8 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index 837e0c9076..8f92cb2771 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -38,6 +38,7 @@ let is_action_logged: Update.t => bool = | SwitchSlide(_) | SwitchTextEditor | UpdateTitle(_) + | UpdatePrompt(_) | SwitchEditor(_) | PerformAction(_) | FailedInput(_) diff --git a/src/haz3lweb/SchoolExercise.re b/src/haz3lweb/SchoolExercise.re index e898a126b2..970d54f51d 100644 --- a/src/haz3lweb/SchoolExercise.re +++ b/src/haz3lweb/SchoolExercise.re @@ -1,4 +1,3 @@ -open Virtual_dom.Vdom; open Sexplib.Std; open Haz3lcore; open Editor; @@ -40,8 +39,7 @@ type p('code) = { title: string, version: int, module_name: string, - prompt: - [@printer (fmt, _) => Format.pp_print_string(fmt, "prompt")] [@opaque] Node.t, + prompt: string, point_distribution, prelude: 'code, correct_impl: 'code, @@ -284,6 +282,14 @@ let update_title = ({eds, pos}, title) => { }, }; +let update_prompt = ({eds, pos}, prompt) => { + pos, + eds: { + ...eds, + prompt, + }, +}; + let zipper_of_code = (id, code) => { switch (Printer.zipper_of_string(id, code)) { | None => failwith("Transition failed.") @@ -872,7 +878,7 @@ let blank_spec = title, version: 1, module_name, - prompt: Node.text("TODO: prompt"), + prompt: "TODO: prompt", point_distribution, prelude, correct_impl, diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 2af807857c..68b8fa048e 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -162,6 +162,7 @@ let reevaluate_post_update = | ResetSlide | SwitchTextEditor | UpdateTitle(_) + | UpdatePrompt(_) | SwitchEditor(_) | SwitchSlide(_) | ToggleMode @@ -316,6 +317,14 @@ let apply = Ok({...model, editors: School(m, specs, exercise)}); } + | UpdatePrompt(prompt) => + switch (model.editors) { + | Scratch(_) => assert(false) + | School(m, specs, exercise) => + let exercise = SchoolExercise.update_prompt(exercise, prompt); + Ok({...model, editors: School(m, specs, exercise)}); + } + | SwitchEditor(n) => switch (model.editors) { | Scratch(_) => Error(FailedToSwitch) // one editor per scratch diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index afaa73fb32..4e41cd988e 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -29,6 +29,7 @@ type t = | SwitchSlide(int) | SwitchTextEditor | UpdateTitle(string) + | UpdatePrompt(string) | SwitchEditor(int) | SetFontMetrics(FontMetrics.t) | SetLogoFontMetrics(FontMetrics.t) diff --git a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml index 9bbac700bb..e0382c5734 100644 --- a/src/haz3lweb/exercises/Ex_OddlyRecursive.ml +++ b/src/haz3lweb/exercises/Ex_OddlyRecursive.ml @@ -1,12 +1,12 @@ -let prompt = Ex_OddlyRecursive_prompt.prompt - let exercise : SchoolExercise.spec = { next_id = 5134; title = "Oddly Recursive"; version = 1; module_name = "Ex_OddlyRecursive"; - prompt; + prompt = + "Write a recursive function that determines whether the given integer is \ + odd. "; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml index ed5ff34c3a..700c11fe84 100644 --- a/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml +++ b/src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml @@ -1,12 +1,12 @@ -let prompt = Ex_RecursiveFibonacci_prompt.prompt - let exercise : SchoolExercise.spec = { next_id = 813; title = "Recursive Fibonacci"; version = 1; module_name = "Ex_RecursiveFibonacci"; - prompt; + prompt = + "Write tests cases for, and then implement, a function, that recursively \ + determines the nth fibonacci number."; point_distribution = { test_validation = 1; mutation_testing = 1; impl_grading = 2 }; prelude = diff --git a/src/haz3lweb/view/Page.re b/src/haz3lweb/view/Page.re index 004b19f6a4..cb5b20acc4 100644 --- a/src/haz3lweb/view/Page.re +++ b/src/haz3lweb/view/Page.re @@ -255,13 +255,7 @@ let view = (~inject, ~handlers, model: Model.t) => { switch (model.editors) { | School(_, _, exercise) => let SchoolExercise.{pos, _} = exercise; - if (pos == Title) { - print_endline("additional_attrs"); - []; - } else { - print_endline("no additional_attrs"); - handlers(~inject, ~model); - }; + pos == Title ? [] : handlers(~inject, ~model); | _ => handlers(~inject, ~model) }; }; diff --git a/src/haz3lweb/view/SchoolMode.re b/src/haz3lweb/view/SchoolMode.re index d6447d36c8..f934e01519 100644 --- a/src/haz3lweb/view/SchoolMode.re +++ b/src/haz3lweb/view/SchoolMode.re @@ -111,6 +111,12 @@ let view = ); }; + let input_handler_prompt = (~inject, title) => { + Virtual_dom.Vdom.Effect.Many( + List.map(inject, [Update.UpdatePrompt(title)]), + ); + }; + let title_view = settings.instructor_mode ? [ @@ -127,10 +133,29 @@ let view = ] : [Cell.title_cell(eds.title)]; + let mdstring_to_node = s => { + let (node, _) = LangDoc.mk_translation(~inject=None, s, false); + node; + }; + let prompt_view = - Cell.narrative_cell( - div(~attr=Attr.class_("cell-prompt"), [eds.prompt]), - ); + settings.instructor_mode + ? Cell.text_cell_view( + ~attrs= + Attr.many([ + Attr.on_mousedown(_ => mousedown_handler(~inject)), + Attr.on_input(_ => input_handler_prompt(~inject)), + ]), + ~selected=pos == Title, + ~caption="Prompt", + ~text=eds.prompt, + ) + : Cell.narrative_cell( + div( + ~attr=Attr.class_("cell-prompt"), + [div(mdstring_to_node(eds.prompt))], + ), + ); let prelude_view = Always( From 3149c3c996944cf53bf2470a77822fe0131ab729 Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Mon, 5 Dec 2022 13:38:06 -0500 Subject: [PATCH 5/6] Combine SwitchTextEditor to SwitchEditor --- src/haz3lweb/Log.re | 1 - src/haz3lweb/SchoolExercise.re | 2 -- src/haz3lweb/Update.re | 9 --------- src/haz3lweb/UpdateAction.re | 1 - src/haz3lweb/view/SchoolMode.re | 2 +- 5 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/haz3lweb/Log.re b/src/haz3lweb/Log.re index 8f92cb2771..5a51911ba8 100644 --- a/src/haz3lweb/Log.re +++ b/src/haz3lweb/Log.re @@ -36,7 +36,6 @@ let is_action_logged: Update.t => bool = | ResetSlide | ToggleMode | SwitchSlide(_) - | SwitchTextEditor | UpdateTitle(_) | UpdatePrompt(_) | SwitchEditor(_) diff --git a/src/haz3lweb/SchoolExercise.re b/src/haz3lweb/SchoolExercise.re index 970d54f51d..97006119b7 100644 --- a/src/haz3lweb/SchoolExercise.re +++ b/src/haz3lweb/SchoolExercise.re @@ -272,8 +272,6 @@ let switch_editor = (idx: int, {eds, _}) => { eds, }; -let switch_text_editor = ({eds, _}) => {pos: Title, eds}; - let update_title = ({eds, pos}, title) => { pos, eds: { diff --git a/src/haz3lweb/Update.re b/src/haz3lweb/Update.re index 0d1259654e..118481af92 100644 --- a/src/haz3lweb/Update.re +++ b/src/haz3lweb/Update.re @@ -160,7 +160,6 @@ let reevaluate_post_update = | FinishImportAll(_) | FinishImportScratchpad(_) | ResetSlide - | SwitchTextEditor | UpdateTitle(_) | UpdatePrompt(_) | SwitchEditor(_) @@ -301,14 +300,6 @@ let apply = } } - | SwitchTextEditor => - switch (model.editors) { - | Scratch(_) => assert(false) - | School(m, specs, exercise) => - let exercise = SchoolExercise.switch_text_editor(exercise); - Ok({...model, editors: School(m, specs, exercise)}); - } - | UpdateTitle(title) => switch (model.editors) { | Scratch(_) => assert(false) diff --git a/src/haz3lweb/UpdateAction.re b/src/haz3lweb/UpdateAction.re index 4e41cd988e..d05725c0c4 100644 --- a/src/haz3lweb/UpdateAction.re +++ b/src/haz3lweb/UpdateAction.re @@ -27,7 +27,6 @@ type t = | Save | ToggleMode | SwitchSlide(int) - | SwitchTextEditor | UpdateTitle(string) | UpdatePrompt(string) | SwitchEditor(int) diff --git a/src/haz3lweb/view/SchoolMode.re b/src/haz3lweb/view/SchoolMode.re index f934e01519..7dd751460d 100644 --- a/src/haz3lweb/view/SchoolMode.re +++ b/src/haz3lweb/view/SchoolMode.re @@ -101,7 +101,7 @@ let view = }; let mousedown_handler = (~inject) => { Virtual_dom.Vdom.Effect.Many( - List.map(inject, Update.[Mousedown, Update.SwitchTextEditor]), + List.map(inject, Update.[Mousedown, Update.SwitchEditor(0)]), ); }; From 1946d693d1b6df03b20779d56240de660c0dd923 Mon Sep 17 00:00:00 2001 From: Wu Jinyi Date: Mon, 5 Dec 2022 13:42:45 -0500 Subject: [PATCH 6/6] Update INSTRUCTORS.md --- INSTRUCTORS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/INSTRUCTORS.md b/INSTRUCTORS.md index 5a4b7c9ef6..d2b7170168 100644 --- a/INSTRUCTORS.md +++ b/INSTRUCTORS.md @@ -10,8 +10,6 @@ Make sure the module_name argument matches your module name. Use the .ml extensi 4. When satisfied, you can click the Export Exercise Module button in the top bar, which will generate a replacement for your .ml file that you can drop in (blowing away the code in the blank template is ok). -5. You will then have to create a prompt file, _prompt.re. See examples for what goes there. - Once you're done, you can compile and run again and your exercise should be in the state you exported. You can keep doing this cycle to tweak the exercise until it is ready.