diff --git a/src/haz3lweb/exercises/Exercise.re b/src/haz3lweb/exercises/Exercise.re index 851859e90f..3df249833b 100644 --- a/src/haz3lweb/exercises/Exercise.re +++ b/src/haz3lweb/exercises/Exercise.re @@ -429,13 +429,15 @@ let update_test_val_rep = ({eds}: state, new_test_num: int, new_dist: int) => { }, point_distribution: { ...eds.point_distribution, - test_validation: new_dist, + test_validation: new_dist < 0 ? 0 : new_dist, }, }, }; let update_mut_test_rep = ({eds}: state, new_dist: int, new_hints: list(string)) => { + print_endline("New Point Dist for Mut Grading is: "); + print_endline(string_of_int(new_dist)); let updated_bugs = List.mapi( (i, bug) => { @@ -447,14 +449,15 @@ let update_mut_test_rep = }, eds.hidden_bugs, ); - + print_endline("New Point Dist for Mut Grading is: "); + print_endline(string_of_int(new_dist)); { eds: { ...eds, hidden_bugs: updated_bugs, point_distribution: { ...eds.point_distribution, - mutation_testing: new_dist, + mutation_testing: new_dist < 0 ? 0 : new_dist, }, }, }; @@ -471,12 +474,29 @@ let update_impl_grd_rep = }, point_distribution: { ...eds.point_distribution, - impl_grading: new_dist, + impl_grading: new_dist < 0 ? 0 : new_dist, }, }, }; }; +let update_syntax_rep = ({eds}: state, new_hints: list(string)) => { + eds: { + ...eds, + syntax_tests: + List.mapi( + (i, (_, predicate)) => { + let new_hint = List.nth_opt(new_hints, i); + switch (new_hint) { + | Some(hint) => (hint, predicate) + | None => ("No hint provided", predicate) + }; + }, + eds.syntax_tests, + ), + }, +}; + let update_module_name = ({eds}: state, new_module_name: string) => { eds: { ...eds, diff --git a/src/haz3lweb/exercises/Grading.re b/src/haz3lweb/exercises/Grading.re index 99230668bd..6dc74f492e 100644 --- a/src/haz3lweb/exercises/Grading.re +++ b/src/haz3lweb/exercises/Grading.re @@ -162,6 +162,7 @@ module TestValidationReport = { Attr.type_("number"), Attr.class_("point-num-input"), Attr.id("test-required-input"), + Attr.create("min", "0"), Attr.value(string_of_int(max_tests)), Attr.on_focus(_ => signal_textbox_active), ], @@ -371,6 +372,7 @@ module MutationTestingReport = { Attr.class_("point-num-input"), Attr.id("point-max-input"), Attr.value(string_of_int(max_points)), + Attr.create("min", "0"), Attr.on_focus(_ => select_textbox), ], (), @@ -531,6 +533,7 @@ module MutationTestingReport = { Attr.classes(["test-hint", "test-instance"]), Attr.id("hint-input-" ++ string_of_int(i)), Attr.value(hint), + Attr.create("min", "0"), Attr.on_focus(_ => select_textbox), ], (), @@ -713,38 +716,91 @@ module SyntaxReport = { }; }; - let individual_report = (i: int, hint: string, status: bool) => { + let individual_report = + ( + i: int, + hint: string, + status: bool, + ~editing_syntax_rep, + ~globals: Globals.t, + ~select_textbox, + ) => { let result_string = status ? "Pass" : "Indet"; - div( - ~attrs=[Attr.classes(["test-report"])], - [ - div( - ~attrs=[Attr.classes(["test-id", "Test" ++ result_string])], - [text(string_of_int(i + 1))], - ), - ] - @ [ - div( - ~attrs=[ - Attr.classes(["test-hint", "test-instance", result_string]), - ], - [text(hint)], - ), - ], - ); + if (globals.settings.instructor_mode && editing_syntax_rep) { + div( + ~attrs=[Attr.classes(["test-report"])], + [ + div( + ~attrs=[Attr.classes(["test-id", "Test" ++ result_string])], + [text(string_of_int(i + 1))], + ), + ] + @ [ + input( + ~attrs=[ + Attr.classes(["test-hint", "test-instance"]), + Attr.id("syntax-hint-input-" ++ string_of_int(i)), + Attr.value(hint), + Attr.create("min", "0"), + Attr.on_focus(_ => select_textbox), + ], + (), + ), + ], + ); + } else { + div( + ~attrs=[Attr.classes(["test-report"])], + [ + div( + ~attrs=[Attr.classes(["test-id", "Test" ++ result_string])], + [text(string_of_int(i + 1))], + ), + ] + @ [ + div( + ~attrs=[ + Attr.classes(["test-hint", "test-instance", result_string]), + ], + [text(hint)], + ), + ], + ); + }; }; - let individual_reports = (hinted_results: list((bool, string))) => { + let individual_reports = + ( + hinted_results: list((bool, string)), + ~editing_syntax_rep, + ~globals, + ~select_textbox, + ) => { div( hinted_results |> List.mapi((i, (status, hint)) => - individual_report(i, hint, status) + individual_report( + i, + hint, + status, + ~editing_syntax_rep, + ~globals, + ~select_textbox, + ) ), ); }; - let view = (syntax_report: t) => { + let view = + ( + ~globals: Globals.t, + ~editing_syntax_rep, + ~inject_set_editing_syntax_rep, + ~inject_update_syntax_rep, + ~select_textbox, + syntax_report: t, + ) => { CellCommon.panel( ~classes=["test-panel"], [ @@ -753,7 +809,12 @@ module SyntaxReport = { ~rest= ": Does your implementation satisfy the syntactic requirements?", ), - individual_reports(syntax_report.hinted_results), + individual_reports( + syntax_report.hinted_results, + ~editing_syntax_rep, + ~globals, + ~select_textbox, + ), ], ~footer= Some( @@ -763,12 +824,69 @@ module SyntaxReport = { [ div( ~attrs=[Attr.class_("test-text")], - [ - percentage_view(syntax_report.percentage), - text( - " of the Implementation Validation points will be earned", - ), - ], + globals.settings.instructor_mode + ? editing_syntax_rep + ? [ + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button( + Icons.confirm, + _ => { + let new_hints = + List.init( + List.length( + syntax_report.hinted_results, + ), + i => + Obj.magic( + Js_of_ocaml.Js.some( + JsUtil.get_elem_by_id( + "syntax-hint-input-" + ++ string_of_int(i), + ), + ), + )##.value + ); + + let update_events = [ + inject_set_editing_syntax_rep, + inject_update_syntax_rep(new_hints), + ]; + Virtual_dom.Vdom.Effect.Many(update_events); + }, + ), + ], + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.cancel, _ => + inject_set_editing_syntax_rep + ), + ], + ), + ] + : [ + percentage_view(syntax_report.percentage), + text( + " of the Implementation Validation points will be earned", + ), + div( + ~attrs=[Attr.class_("edit-icon")], + [ + Widgets.button(Icons.pencil, _ => + inject_set_editing_syntax_rep + ), + ], + ), + ] + : [ + percentage_view(syntax_report.percentage), + text( + " of the Implementation Validation points will be earned", + ), + ], ), ], ), @@ -908,6 +1026,7 @@ module ImplGradingReport = { Attr.classes(["test-hint", "test-instance"]), Attr.id("impl-hint-input-" ++ string_of_int(i)), Attr.value(hint), + Attr.create("min", "0"), Attr.on_focus(_ => select_textbox), ], (), @@ -1031,6 +1150,7 @@ module ImplGradingReport = { Attr.class_("point-num-input"), Attr.id("point-max-input"), Attr.value(string_of_int(max_points)), + Attr.create("min", "0"), Attr.on_focus(_ => select_textbox), ], (), diff --git a/src/haz3lweb/view/ExerciseMode.re b/src/haz3lweb/view/ExerciseMode.re index 5bb307e6ff..9f0efa7256 100644 --- a/src/haz3lweb/view/ExerciseMode.re +++ b/src/haz3lweb/view/ExerciseMode.re @@ -16,6 +16,7 @@ module Model = { editing_mut_test_rep: bool, editing_impl_grd_rep: bool, editing_module_name: bool, + editing_syntax_rep: bool, }; let editing_flags_false = { @@ -25,6 +26,7 @@ module Model = { editing_mut_test_rep: false, editing_impl_grd_rep: false, editing_module_name: false, + editing_syntax_rep: false, }; [@deriving (show({with_path: false}), sexp, yojson)] @@ -79,6 +81,7 @@ module Update = { | EditingMutTestRep | EditingImplGrdRep | EditingModuleName + | EditingSyntaxRep | UpdateTitle(string) | AddBuggyImplementation | DeleteBuggyImplementation(int) @@ -86,6 +89,7 @@ module Update = { | UpdateTestValRep(int, int) | UpdateMutTestRep(int, list(string)) | UpdateImplGrdRep(int, list(string)) + | UpdateSyntaxRep(list(string)) | UpdateModuleName(string); [@deriving (show({with_path: false}), sexp, yojson)] @@ -147,6 +151,14 @@ module Update = { editing_module_name: !model.editing_flags.editing_module_name, }, }) + | EditingSyntaxRep => + Updated.return_quiet({ + ...model, + editing_flags: { + ...model.editing_flags, + editing_syntax_rep: !model.editing_flags.editing_syntax_rep, + }, + }) | UpdateTitle(title) => Updated.return_quiet( { @@ -201,6 +213,12 @@ module Update = { ). eds, }) + | UpdateSyntaxRep(new_hints) => + Updated.return({ + ...model, + editors: + Exercise.update_syntax_rep({eds: model.editors}, new_hints).eds, + }) | UpdateImplGrdRep(test_num, new_hints) => Updated.return({ ...model, @@ -959,7 +977,18 @@ module View = { }; let syntax_grading_view = - Always(Grading.SyntaxReport.view(grading_report.syntax_report)); + Always( + Grading.SyntaxReport.view( + ~globals, + ~editing_syntax_rep=editing_flags.editing_syntax_rep, + ~inject_set_editing_syntax_rep= + inject(Instructor(EditingSyntaxRep)), + ~inject_update_syntax_rep= + hints => inject(Instructor(UpdateSyntaxRep(hints))), + ~select_textbox=signal(MakeActive(TextBox)), + grading_report.syntax_report, + ), + ); let impl_validation_view = Always( diff --git a/src/haz3lweb/www/style/cell.css b/src/haz3lweb/www/style/cell.css index 8d235cedde..7f6f0da563 100644 --- a/src/haz3lweb/www/style/cell.css +++ b/src/haz3lweb/www/style/cell.css @@ -235,6 +235,29 @@ font-size: 1rem; } +.syntax-tests-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.syntax-test-item { + display: flex; + align-items: center; + gap: 1rem; +} + +.test-checkbox { + display: flex; + align-items: center; +} + +.test-checkbox input[type="checkbox"] { + width: 1.2rem; + height: 1.2rem; + cursor: pointer; +} + .edit-icon, .edit-pencil { margin-left: 0.5em;