Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Exercise Development Mode: Title Editor #928

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/haz3lweb/Log.re
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ let is_action_logged: Update.t => bool =
| ResetSlide
| ToggleMode
| SwitchSlide(_)
| SwitchTextEditor
Copy link
Member

Choose a reason for hiding this comment

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

Should we unify this with SwitchEditor since the focus can only be in one place at a time?

| UpdateTitle(_)
Copy link
Member

Choose a reason for hiding this comment

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

Let's group all Exercise related actions into an ExerciseAction.t type that is injected into Action.t at once, to avoid having too many cases?

| UpdatePrompt(_)
| SwitchEditor(_)
| PerformAction(_)
| FailedInput(_)
Expand Down
68 changes: 49 additions & 19 deletions src/haz3lweb/SchoolExercise.re
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
open Virtual_dom.Vdom;
open Sexplib.Std;
open Haz3lcore;
open Editor;

[@deriving (show({with_path: false}), sexp, yojson)]
type wrong_impl('code) = {
Expand Down Expand Up @@ -39,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,
Expand All @@ -63,6 +62,7 @@ let find_key_opt = (key, specs: list(p('code))) => {

[@deriving (show({with_path: false}), sexp, yojson)]
type pos =
| Title
| Prelude
| CorrectImpl
| YourTestsValidation
Expand Down Expand Up @@ -125,6 +125,7 @@ type persistent_state = (pos, Id.t, list((pos, PersistentZipper.t)));
let editor_of_state: state => Editor.t =
({pos, eds, _}) =>
switch (pos) {
| Title => eds.prelude
| Prelude => eds.prelude
| CorrectImpl => eds.correct_impl
| YourTestsValidation => eds.your_tests.tests
Expand All @@ -141,6 +142,13 @@ 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,
},
}
| Prelude => {
...state,
eds: {
Expand Down Expand Up @@ -224,33 +232,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");
Expand All @@ -262,6 +272,24 @@ 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 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.")
Expand Down Expand Up @@ -459,6 +487,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
Expand Down Expand Up @@ -759,6 +788,7 @@ let focus = (state: state, stitched_dynamics: stitched(DynamicsItem.t)) => {

let (focal_zipper, focal_info_map) =
switch (pos) {
| 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 => (
Expand Down Expand Up @@ -848,7 +878,7 @@ let blank_spec =
title,
version: 1,
module_name,
prompt: Node.text("TODO: prompt"),
prompt: "TODO: prompt",
point_distribution,
prelude,
correct_impl,
Expand Down
28 changes: 28 additions & 0 deletions src/haz3lweb/Update.re
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ let reevaluate_post_update =
| FinishImportAll(_)
| FinishImportScratchpad(_)
| ResetSlide
| SwitchTextEditor
| UpdateTitle(_)
| UpdatePrompt(_)
| SwitchEditor(_)
| SwitchSlide(_)
| ToggleMode
Expand Down Expand Up @@ -297,6 +300,31 @@ 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)});
}

| 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
Expand Down
3 changes: 3 additions & 0 deletions src/haz3lweb/UpdateAction.re
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type t =
| Save
| ToggleMode
| SwitchSlide(int)
| SwitchTextEditor
| UpdateTitle(string)
| UpdatePrompt(string)
| SwitchEditor(int)
| SetFontMetrics(FontMetrics.t)
| SetLogoFontMetrics(FontMetrics.t)
Expand Down
6 changes: 3 additions & 3 deletions src/haz3lweb/exercises/Ex_OddlyRecursive.ml
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down
8 changes: 4 additions & 4 deletions src/haz3lweb/exercises/Ex_RecursiveFibonacci.ml
Original file line number Diff line number Diff line change
@@ -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 =
Expand All @@ -15,7 +15,7 @@ let exercise : SchoolExercise.spec =
backpack = [];
relatives =
{
siblings = ([ Grout { id = 0; shape = Convex } ], []);
siblings = ([ Grout { id = 1; shape = Convex } ], []);
ancestors = [];
};
caret = Outer;
Expand Down
32 changes: 32 additions & 0 deletions src/haz3lweb/view/Cell.re
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 12 additions & 2 deletions src/haz3lweb/view/Page.re
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ 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;
pos == Title ? [] : handlers(~inject, ~model);
| _ => handlers(~inject, ~model)
};
};

div(
~attr=
Attr.many(
Expand Down Expand Up @@ -280,8 +290,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"),
Expand Down
61 changes: 56 additions & 5 deletions src/haz3lweb/view/SchoolMode.re
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,63 @@ let view =
~color_highlighting,
);
};
let mousedown_handler = (~inject) => {
Virtual_dom.Vdom.Effect.Many(
List.map(inject, Update.[Mousedown, Update.SwitchTextEditor]),
);
};

let title_view = Cell.title_cell(eds.title);
let input_handler = (~inject, title) => {
Virtual_dom.Vdom.Effect.Many(
List.map(inject, [Update.UpdateTitle(title)]),
);
};

let prompt_view =
Cell.narrative_cell(
div(~attr=Attr.class_("cell-prompt"), [eds.prompt]),
let input_handler_prompt = (~inject, title) => {
Virtual_dom.Vdom.Effect.Many(
List.map(inject, [Update.UpdatePrompt(title)]),
);
};

let title_view =
settings.instructor_mode
? [
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 mdstring_to_node = s => {
let (node, _) = LangDoc.mk_translation(~inject=None, s, false);
node;
};

let prompt_view =
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(
Expand Down Expand Up @@ -342,7 +392,8 @@ let view =

div(
~attr=Attr.classes(["editor", "column"]),
[title_view, prompt_view]
title_view
@ [prompt_view]
@ render_cells(
settings,
[
Expand Down
Loading