From 7396d11583b1f555937461997cc68602d505d38d Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Tue, 3 Dec 2024 16:35:20 -0800 Subject: [PATCH 01/28] Add headings for "Common Settings" and "Answers" sections. Move editor components to appropriate sections under headings. --- .../perseus-editor/src/components/heading.tsx | 1 + .../src/widgets/numeric-input-editor.tsx | 77 ++++++++++++------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/packages/perseus-editor/src/components/heading.tsx b/packages/perseus-editor/src/components/heading.tsx index 864031b8dc..9618ed4140 100644 --- a/packages/perseus-editor/src/components/heading.tsx +++ b/packages/perseus-editor/src/components/heading.tsx @@ -51,6 +51,7 @@ const styles = StyleSheet.create({ marginInline: -10, backgroundColor: color.offBlack8, padding: spacing.xSmall_8, + width: "calc(100% + 20px)", }, heading: { flexDirection: "row", diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index cc3928e509..bc00b909b7 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -5,8 +5,10 @@ import { EditorJsonify, Util, PerseusI18nContext, - iconTrash, + iconTrash, LockedLabelType, } from "@khanacademy/perseus"; +import Heading from "../components/heading"; +import Button from "@khanacademy/wonder-blocks-button"; import {Checkbox} from "@khanacademy/wonder-blocks-form"; import * as React from "react"; import _ from "underscore"; @@ -15,6 +17,7 @@ import Editor from "../editor"; import {iconGear} from "../styles/icon-paths"; import type {APIOptionsWithDefaults} from "@khanacademy/perseus"; +import {getDefaultFigureForType} from "./interactive-graph-editor/locked-figures/util"; type ChangeFn = typeof Changeable.change; @@ -100,6 +103,8 @@ type Props = PerseusNumericInputWidgetOptions & { type State = { lastStatus: string; showOptions: boolean[]; + showSettings: boolean; + showAnswers: boolean; }; class NumericInputEditor extends React.Component { @@ -122,6 +127,8 @@ class NumericInputEditor extends React.Component { this.state = { lastStatus: "wrong", showOptions: _.map(this.props.answers, () => false), + showSettings: true, + showAnswers: true, }; } @@ -135,6 +142,15 @@ class NumericInputEditor extends React.Component { this.setState({showOptions: showOptions}); }; + onToggleAccordion = (accordionName: string) => { + return () => { + const toggleName = `show${accordionName}`; + const newState = {[toggleName]: !this.state[toggleName]}; + // @ts-expect-error + this.setState(newState); + } + } + onTrashAnswer = (choiceIndex) => { if (choiceIndex >= 0 && choiceIndex < this.props.answers.length) { const answers = this.props.answers.slice(0); @@ -397,24 +413,6 @@ class NumericInputEditor extends React.Component { ); - const addAnswerButton = ( - - ); - const instructions = { wrong: "(address the mistake/misconception)", ungraded: "(explain in detail to avoid confusion)", @@ -602,16 +600,37 @@ class NumericInputEditor extends React.Component { return (
-
User input
-
- Message shown to user on attempt -
- {generateInputAnswerEditors()} - {addAnswerButton} - {inputSize} - {rightAlign} - {coefficientCheck} - {labelText} + + {this.state.showSettings && inputSize} + {this.state.showSettings && rightAlign} + {this.state.showSettings && coefficientCheck} + {this.state.showSettings && labelText} + + {this.state.showAnswers && ( + <> +
User input
+
+ Message shown to user on attempt +
+ {generateInputAnswerEditors()} + + + )}
); } From b88706db9473e4c0846aa42189ae26d19767644c Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Wed, 4 Dec 2024 16:05:39 -0800 Subject: [PATCH 02/28] Individual answers moved to their own accordion. --- .../src/styles/perseus-editor.less | 5 + .../src/widgets/numeric-input-editor.tsx | 334 ++++++++++-------- 2 files changed, 184 insertions(+), 155 deletions(-) diff --git a/packages/perseus-editor/src/styles/perseus-editor.less b/packages/perseus-editor/src/styles/perseus-editor.less index a7f12136be..375ee8042d 100644 --- a/packages/perseus-editor/src/styles/perseus-editor.less +++ b/packages/perseus-editor/src/styles/perseus-editor.less @@ -224,6 +224,11 @@ .categorizer-container { overflow-x: scroll; } + + .section-accordion { + display: flex; + flex-direction: row; + } } .perseus-widget-editor-title-id > svg { diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index bc00b909b7..790d91e39e 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -5,19 +5,23 @@ import { EditorJsonify, Util, PerseusI18nContext, - iconTrash, LockedLabelType, + iconTrash, } from "@khanacademy/perseus"; -import Heading from "../components/heading"; import Button from "@khanacademy/wonder-blocks-button"; +import {View} from "@khanacademy/wonder-blocks-core"; import {Checkbox} from "@khanacademy/wonder-blocks-form"; +import {Strut} from "@khanacademy/wonder-blocks-layout"; +import {spacing} from "@khanacademy/wonder-blocks-tokens"; +import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; import * as React from "react"; import _ from "underscore"; +import Heading from "../components/heading"; +import PerseusEditorAccordion from "../components/perseus-editor-accordion"; import Editor from "../editor"; import {iconGear} from "../styles/icon-paths"; import type {APIOptionsWithDefaults} from "@khanacademy/perseus"; -import {getDefaultFigureForType} from "./interactive-graph-editor/locked-figures/util"; type ChangeFn = typeof Changeable.change; @@ -103,6 +107,7 @@ type Props = PerseusNumericInputWidgetOptions & { type State = { lastStatus: string; showOptions: boolean[]; + showAnswerDetails: boolean[]; showSettings: boolean; showAnswers: boolean; }; @@ -127,6 +132,7 @@ class NumericInputEditor extends React.Component { this.state = { lastStatus: "wrong", showOptions: _.map(this.props.answers, () => false), + showAnswerDetails: _.map(this.props.answers, () => true), showSettings: true, showAnswers: true, }; @@ -142,14 +148,20 @@ class NumericInputEditor extends React.Component { this.setState({showOptions: showOptions}); }; - onToggleAccordion = (accordionName: string) => { + onToggleAnswers = (answerIndex) => { + const showAnswerDetails = this.state.showAnswerDetails.slice(); + showAnswerDetails[answerIndex] = !showAnswerDetails[answerIndex]; + this.setState({showAnswerDetails: showAnswerDetails}); + }; + + onToggleHeading = (accordionName: string) => { return () => { const toggleName = `show${accordionName}`; const newState = {[toggleName]: !this.state[toggleName]}; // @ts-expect-error this.setState(newState); - } - } + }; + }; onTrashAnswer = (choiceIndex) => { if (choiceIndex >= 0 && choiceIndex < this.props.answers.length) { @@ -211,6 +223,8 @@ class NumericInputEditor extends React.Component { addAnswer = () => { const lastAnswer: any = initAnswer(this.state.lastStatus); const answers = this.props.answers.concat(lastAnswer); + const showAnswerDetails = this.state.showAnswerDetails.concat(true); + this.setState({showAnswerDetails: showAnswerDetails}); this.props.onChange({answers: answers}); }; @@ -443,157 +457,170 @@ class NumericInputEditor extends React.Component { ); return (
-
{ + this.onToggleAnswers(i); + }} + header={ +
+ Answer Option + +
} > - { - // NOTE(charlie): The mobile web expression - // editor relies on this automatic answer - // form resolution for determining when to - // show the Pi symbol. If we get rid of it, - // we should also disable Pi for - // NumericInput and require problems that - // use Pi to build on Expression. - // Alternatively, we could store answers - // as plaintext and parse them to determine - // whether or not to reveal Pi on the - // keypad (right now, answers are stored as - // resolved values, like '0.125' rather - // than '1/8'). - let forms; - if (format === "pi") { - forms = ["pi"]; - } else if (format === "mixed") { - forms = ["proper", "mixed"]; - } else if ( - format === "proper" || - format === "improper" - ) { - forms = ["proper", "improper"]; - } - this.updateAnswer(i, { - value: firstNumericalParse( - newValue, - this.context.strings, - ), - answerForms: forms, - }); - }} - onChange={(newValue) => { - this.updateAnswer(i, { - value: firstNumericalParse( - newValue, - this.context.strings, - ), - }); - }} - /> - {answer.strict && ( -
- ≡ -
- )} - {answer.simplify !== "required" && - answer.status === "correct" && ( -
+ { + // NOTE(charlie): The mobile web expression + // editor relies on this automatic answer + // form resolution for determining when to + // show the Pi symbol. If we get rid of it, + // we should also disable Pi for + // NumericInput and require problems that + // use Pi to build on Expression. + // Alternatively, we could store answers + // as plaintext and parse them to determine + // whether or not to reveal Pi on the + // keypad (right now, answers are stored as + // resolved values, like '0.125' rather + // than '1/8'). + let forms; + if (format === "pi") { + forms = ["pi"]; + } else if (format === "mixed") { + forms = ["proper", "mixed"]; + } else if ( + format === "proper" || + format === "improper" + ) { + forms = ["proper", "improper"]; } - title="accepts unsimplified answers" + this.updateAnswer(i, { + value: firstNumericalParse( + newValue, + this.context.strings, + ), + answerForms: forms, + }); + }} + onChange={(newValue) => { + this.updateAnswer(i, { + value: firstNumericalParse( + newValue, + this.context.strings, + ), + }); + }} + /> + {answer.strict && ( +
- ‰ + ≡
)} - {answer.maxError ? ( -
-
- ± + {answer.simplify !== "required" && + answer.status === "correct" && ( +
+ ‰ +
+ )} + {answer.maxError ? ( +
+
+ ± +
+
- -
- ) : null} - -
- {editor} -
- {this.state.showOptions[i] && ( -
- {maxError(i)} - {answer.status === "correct" && - unsimplifiedAnswers(i)} - {suggestedAnswerTypes(i)} + ) : null} + - )} +
+ {editor} +
+ {this.state.showOptions[i] && ( +
+ {maxError(i)} + {answer.status === "correct" && + unsimplifiedAnswers(i)} + {suggestedAnswerTypes(i)} +
+ )} +
); }); @@ -604,7 +631,7 @@ class NumericInputEditor extends React.Component { title="Common Settings" isCollapsible={true} isOpen={this.state.showSettings} - onToggle={this.onToggleAccordion("Settings")} + onToggle={this.onToggleHeading("Settings")} /> {this.state.showSettings && inputSize} {this.state.showSettings && rightAlign} @@ -614,7 +641,7 @@ class NumericInputEditor extends React.Component { title="Answers" isCollapsible={true} isOpen={this.state.showAnswers} - onToggle={this.onToggleAccordion("Answers")} + onToggle={this.onToggleHeading("Answers")} /> {this.state.showAnswers && ( <> @@ -623,10 +650,7 @@ class NumericInputEditor extends React.Component { Message shown to user on attempt
{generateInputAnswerEditors()} - From c8ac23e710f30cea2c862eaa3beeb35570dde8f1 Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Wed, 4 Dec 2024 16:49:52 -0800 Subject: [PATCH 03/28] Move delete button to bottom of individual answer panel. --- .../src/styles/perseus-editor.less | 5 ++ .../src/widgets/numeric-input-editor.tsx | 58 +++++++++++++------ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/packages/perseus-editor/src/styles/perseus-editor.less b/packages/perseus-editor/src/styles/perseus-editor.less index 375ee8042d..21f89f30fd 100644 --- a/packages/perseus-editor/src/styles/perseus-editor.less +++ b/packages/perseus-editor/src/styles/perseus-editor.less @@ -229,6 +229,11 @@ display: flex; flex-direction: row; } + + .delete-item-button { + align-self: center; + padding-right: 0.5em; + } } .perseus-widget-editor-title-id > svg { diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 790d91e39e..13f1e6fe4b 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -8,11 +8,11 @@ import { iconTrash, } from "@khanacademy/perseus"; import Button from "@khanacademy/wonder-blocks-button"; -import {View} from "@khanacademy/wonder-blocks-core"; import {Checkbox} from "@khanacademy/wonder-blocks-form"; import {Strut} from "@khanacademy/wonder-blocks-layout"; import {spacing} from "@khanacademy/wonder-blocks-tokens"; import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; +import trashIcon from "@phosphor-icons/core/bold/trash-bold.svg"; import * as React from "react"; import _ from "underscore"; @@ -455,6 +455,17 @@ class NumericInputEditor extends React.Component { }} /> ); + const statusProper = + answer.status.charAt(0).toUpperCase() + + answer.status.slice(1); + const answerRangeText = answer.maxError + ? `± ${answer.maxError}` + : ""; + const answerHeading = + answer.value === null + ? "New Answer" + : `${statusProper} answer: ${answer.value} ${answerRangeText}`; + return (
{ }} header={
- Answer Option + {answerHeading}
} @@ -576,22 +587,22 @@ class NumericInputEditor extends React.Component { > {answer.status} - { - // preventDefault ensures that href="#" - // doesn't scroll to the top of the page - e.preventDefault(); - this.onTrashAnswer(i); - }} - onKeyDown={(e) => - this.onSpace(e, this.onTrashAnswer) - } - > - - + {/* {*/} + {/* // preventDefault ensures that href="#"*/} + {/* // doesn't scroll to the top of the page*/} + {/* e.preventDefault();*/} + {/* this.onTrashAnswer(i);*/} + {/* }}*/} + {/* onKeyDown={(e) =>*/} + {/* this.onSpace(e, this.onTrashAnswer)*/} + {/* }*/} + {/*>*/} + {/* */} + {/**/} { {suggestedAnswerTypes(i)}
)} +
); From 4af63e03f294dbab5f8130959fa7f39e6d855385 Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Wed, 4 Dec 2024 16:57:10 -0800 Subject: [PATCH 04/28] Move settings labels to individual answer sections. --- .../src/widgets/numeric-input-editor.tsx | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 13f1e6fe4b..486e59c3c6 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -480,6 +480,7 @@ class NumericInputEditor extends React.Component {
} > +
User input
{ > {answer.status} - {/* {*/} - {/* // preventDefault ensures that href="#"*/} - {/* // doesn't scroll to the top of the page*/} - {/* e.preventDefault();*/} - {/* this.onTrashAnswer(i);*/} - {/* }}*/} - {/* onKeyDown={(e) =>*/} - {/* this.onSpace(e, this.onTrashAnswer)*/} - {/* }*/} - {/*>*/} - {/* */} - {/**/} {
+
+ Message shown to user on attempt +
{editor}
{this.state.showOptions[i] && (
- {maxError(i)} + {maxError(i)} {answer.status === "correct" && unsimplifiedAnswers(i)} {suggestedAnswerTypes(i)} @@ -667,10 +655,6 @@ class NumericInputEditor extends React.Component { /> {this.state.showAnswers && ( <> -
User input
-
- Message shown to user on attempt -
{generateInputAnswerEditors()}
{this.state.showOptions[i] && (
- {maxError(i)} + {maxError(i)} {answer.status === "correct" && unsimplifiedAnswers(i)} {suggestedAnswerTypes(i)} From 3a03430b9a143181ffcb03c87835881c486bf66b Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Thu, 5 Dec 2024 10:01:05 -0800 Subject: [PATCH 06/28] Changeset --- .changeset/nice-turkeys-dress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-turkeys-dress.md diff --git a/.changeset/nice-turkeys-dress.md b/.changeset/nice-turkeys-dress.md new file mode 100644 index 0000000000..a21090ca25 --- /dev/null +++ b/.changeset/nice-turkeys-dress.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus-editor": minor +--- + +[Numeric Input] - Adjust editor to organize settings more logically From b7abe6e46a404fbeebe5498543cb6137d9612efe Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Thu, 5 Dec 2024 10:14:06 -0800 Subject: [PATCH 07/28] Remove TS comment (no good way to resolve). --- packages/perseus-editor/src/widgets/numeric-input-editor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 0ae5f32230..33457f63e8 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -157,7 +157,6 @@ class NumericInputEditor extends React.Component { return () => { const toggleName = `show${accordionName}`; const newState = {[toggleName]: !this.state[toggleName]}; - // @ts-expect-error this.setState(newState); }; }; From 17bad8261cb27ad5cc101565e937ea47160650dd Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Thu, 5 Dec 2024 12:18:37 -0800 Subject: [PATCH 08/28] Adjust for Typescript issue. --- packages/perseus-editor/src/widgets/numeric-input-editor.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 33457f63e8..742cab7e5f 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -156,7 +156,8 @@ class NumericInputEditor extends React.Component { onToggleHeading = (accordionName: string) => { return () => { const toggleName = `show${accordionName}`; - const newState = {[toggleName]: !this.state[toggleName]}; + const newState = {...this.state}; + newState[toggleName] === !newState[toggleName]; this.setState(newState); }; }; @@ -639,7 +640,7 @@ class NumericInputEditor extends React.Component { return (
Date: Fri, 6 Dec 2024 10:22:25 -0800 Subject: [PATCH 09/28] Remove toggle button for additional answer options, and always show those options. Update unit tests accordingly. --- .../__tests__/numeric-input-editor.test.tsx | 27 ----------------- .../src/widgets/numeric-input-editor.tsx | 30 ++++--------------- 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx index 84f4722133..c9e17f17fb 100644 --- a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx +++ b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx @@ -102,7 +102,6 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click(screen.getByLabelText("Toggle options")); await userEvent.click( screen.getByRole("checkbox", { name: "Strictly match only these formats", @@ -143,20 +142,6 @@ describe("numeric-input-editor", () => { ); }); - it("should be possible to toggle options", async () => { - render( {}} />, { - wrapper: RenderStateRoot, - }); - - await userEvent.click( - screen.getByRole("link", {name: "Toggle options"}), - ); - - expect( - screen.getByText("Unsimplified answers are"), - ).toBeInTheDocument(); - }); - it("should be possible to set unsimplified answers to ungraded", async () => { const onChangeMock = jest.fn(); @@ -164,9 +149,6 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click( - screen.getByRole("link", {name: "Toggle options"}), - ); await userEvent.click(screen.getByRole("button", {name: "ungraded"})); expect(onChangeMock).toBeCalledWith( @@ -185,9 +167,6 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click( - screen.getByRole("link", {name: "Toggle options"}), - ); await userEvent.click(screen.getByRole("button", {name: "accepted"})); expect(onChangeMock).toBeCalledWith( @@ -206,9 +185,6 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click( - screen.getByRole("link", {name: "Toggle options"}), - ); await userEvent.click(screen.getByRole("button", {name: "wrong"})); expect(onChangeMock).toBeCalledWith( @@ -237,9 +213,6 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click( - screen.getByRole("link", {name: "Toggle options"}), - ); await userEvent.click(screen.getByTitle(name)); expect(onChangeMock).toBeCalledWith( diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 742cab7e5f..ea2c8d3bdc 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -590,22 +590,6 @@ class NumericInputEditor extends React.Component { > {answer.status} - { - // preventDefault ensures that href="#" - // doesn't scroll to the top of the page - e.preventDefault(); - this.onToggleOptions(i); - }} - onKeyDown={(e) => - this.onSpace(e, this.onToggleOptions) - } - > - -
@@ -613,14 +597,12 @@ class NumericInputEditor extends React.Component {
{editor}
- {this.state.showOptions[i] && ( -
- {maxError(i)} - {answer.status === "correct" && - unsimplifiedAnswers(i)} - {suggestedAnswerTypes(i)} -
- )} +
+ {maxError(i)} + {answer.status === "correct" && + unsimplifiedAnswers(i)} + {suggestedAnswerTypes(i)} +
{
- { this.onEvaluationChange(i, "correct"); }} > Correct - - + { this.onEvaluationChange(i, "wrong"); }} > Wrong - - + { this.onEvaluationChange(i, "ungraded"); }} > Ungraded - +
{unsimplifiedAnswers(i)}
From 1bd7f221a49a94e2a8f6b0e8adb4eb637e0313db Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Fri, 13 Dec 2024 16:24:25 -0800 Subject: [PATCH 16/28] Debugging. --- packages/perseus-editor/src/editor-page.tsx | 1 + packages/perseus-editor/src/editor.tsx | 1 + .../src/styles/perseus-editor.less | 5 + .../src/widgets/numeric-input-editor.tsx | 128 ++++++++---------- packages/perseus/src/renderer.tsx | 2 +- .../widgets/numeric-input/numeric-input.tsx | 3 +- 6 files changed, 68 insertions(+), 72 deletions(-) diff --git a/packages/perseus-editor/src/editor-page.tsx b/packages/perseus-editor/src/editor-page.tsx index 2f6c1bd684..4347a9af2d 100644 --- a/packages/perseus-editor/src/editor-page.tsx +++ b/packages/perseus-editor/src/editor-page.tsx @@ -208,6 +208,7 @@ class EditorPage extends React.Component { handleChange: ChangeHandler = (toChange, cb, silent) => { const newProps = _(this.props).pick("question", "hints", "answerArea"); _(newProps).extend(toChange); + console.log(`Editor Page - newProps: `, newProps); this.props.onChange(newProps, cb, silent); }; diff --git a/packages/perseus-editor/src/editor.tsx b/packages/perseus-editor/src/editor.tsx index 003f30bbaf..9aeec8c99c 100644 --- a/packages/perseus-editor/src/editor.tsx +++ b/packages/perseus-editor/src/editor.tsx @@ -281,6 +281,7 @@ class Editor extends React.Component { ) => { const widgets = Object.assign({}, this.props.widgets); widgets[id] = Object.assign({}, widgets[id], newWidgetInfo); + console.log(`Editor - Widgets[id]: `, widgets[id]); this.props.onChange({widgets}, cb, silent); }; diff --git a/packages/perseus-editor/src/styles/perseus-editor.less b/packages/perseus-editor/src/styles/perseus-editor.less index ae0047ebe8..eb76014c7e 100644 --- a/packages/perseus-editor/src/styles/perseus-editor.less +++ b/packages/perseus-editor/src/styles/perseus-editor.less @@ -564,6 +564,11 @@ .perseus-textarea-underlay { margin-bottom: 26px; } + textarea { + background-color: #ffffff; + border: 1px solid rgba(33, 36, 44, 0.5); + border-radius: 4px; + } } .input-answer-editor-value, diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index dcb0eae9ae..d431a345bf 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -7,7 +7,6 @@ import { PerseusI18nContext, } from "@khanacademy/perseus"; import Button from "@khanacademy/wonder-blocks-button"; -import {Checkbox} from "@khanacademy/wonder-blocks-form"; import Pill from "@khanacademy/wonder-blocks-pill"; import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; import trashIcon from "@phosphor-icons/core/bold/trash-bold.svg"; @@ -22,8 +21,7 @@ import type {APIOptionsWithDefaults} from "@khanacademy/perseus"; type ChangeFn = typeof Changeable.change; -const {ButtonGroup, InfoTip, MultiButtonGroup, NumberInput, TextInput} = - components; +const {InfoTip, NumberInput, TextInput} = components; const {firstNumericalParse} = Util; // NOTE(john): Copied from perseus-types.d.ts in the Perseus package. @@ -97,7 +95,6 @@ type Props = PerseusNumericInputWidgetOptions & { type State = { lastStatus: string; - showOptions: boolean[]; showAnswerDetails: boolean[]; showSettings: boolean; showAnswers: boolean; @@ -122,7 +119,6 @@ class NumericInputEditor extends React.Component { super(props); this.state = { lastStatus: "wrong", - showOptions: _.map(this.props.answers, () => false), showAnswerDetails: _.map(this.props.answers, () => true), showSettings: true, showAnswers: true, @@ -133,12 +129,6 @@ class NumericInputEditor extends React.Component { return Changeable.change.apply(this, args); }; - onToggleOptions = (choiceIndex) => { - const showOptions = this.state.showOptions.slice(); - showOptions[choiceIndex] = !showOptions[choiceIndex]; - this.setState({showOptions: showOptions}); - }; - onToggleAnswers = (answerIndex: number) => { const showAnswerDetails = this.state.showAnswerDetails.slice(); showAnswerDetails[answerIndex] = !showAnswerDetails[answerIndex]; @@ -149,12 +139,15 @@ class NumericInputEditor extends React.Component { let answerForms: string[] = this.props.answers[answerIndex]["answerForms"] ?? []; const formSelected = answerForms.includes(answerForm); + console.log(`Answer Forms: `, answerForms); + console.log(`Form to toggle: `, answerForm); + console.log(`Is Selected: `, formSelected); if (!formSelected) { answerForms.push(answerForm); } else { answerForms = answerForms.filter((form) => form !== answerForm); } - this.updateAnswer(answerIndex, {answerForms}); + this.updateAnswer(answerIndex, {answerForms: [...answerForms]}); }; onToggleHeading = (accordionName: string) => { @@ -201,7 +194,9 @@ class NumericInputEditor extends React.Component { }; updateAnswer = (choiceIndex, update) => { + console.log(`updateAnswer - choiceIndex, update`, choiceIndex, update); if (!_.isObject(update)) { + console.log(`updateAnswer - !_.isObject(update)`); return _.partial( (choiceIndex, key, value) => { const update: Record = {}; @@ -227,6 +222,7 @@ class NumericInputEditor extends React.Component { } answers[choiceIndex] = _.extend({}, answers[choiceIndex], update); + console.log(`Calling 'props.onChange' with: `, answers); this.props.onChange({answers: answers}); }; @@ -268,36 +264,36 @@ class NumericInputEditor extends React.Component { render() { const answers = this.props.answers; + console.log(`Top-Level Answers: `, this.props.answers.map(a=> a.answerForms?.join(":")).join("|")); const SettingOption = (props: { kind: "accent" | "transparent"; role?: "radio" | "checkbox"; + ariaLabel?: string; onClick: () => void; children: any; }): React.ReactElement => { - const {kind, onClick, children} = props; + const {kind, onClick, ariaLabel, children} = props; const style = { marginRight: "8px", marginTop: "4px", }; const role = props.role ?? "radio"; - return ( - - {children} - - ); + const pillProps = { + "aria-label": ariaLabel, + kind: kind, + size: "medium", + role: role, + style: style, + onClick: onClick, + }; + return {children}; }; const RadioOption = (props: { answerIndex: number; answerProperty: string; - value: string; + value: string | boolean; onClick?: () => void; children: any; }): React.ReactElement => { @@ -319,26 +315,6 @@ class NumericInputEditor extends React.Component { ); }; - const FormatOption = (props: { - answerIndex: number; - format: MathFormat; - children: any; - }): React.ReactElement => { - const {answerIndex, format, children} = props; - const isSelected = - answers[answerIndex]["answerForms"]?.includes(format); - const kind = isSelected ? "accent" : "transparent"; - const onClick = () => { - this.onToggleAnswerForm(answerIndex, format); - }; - - return ( - - {children} - - ); - }; - const unsimplifiedAnswers = (i: any) => (
@@ -424,33 +400,45 @@ class NumericInputEditor extends React.Component {


- - 6 - - - 0.75 - - - ⅗ - - - ⁷⁄₄ - - - 1¾ - - - π - + {answerFormButtons.map((format) => { + const isSelected = answers[i]["answerForms"]?.includes( + format.value, + ); + const kind = isSelected ? "accent" : "transparent"; + const onClick = () => { + this.onToggleAnswerForm(i, format.value); + }; + + return ( + + {format.content} + + ); + })}
- { - this.updateAnswer.bind(this, i)({strict: value}); - }} - /> + +
+ + Suggested + + + Required +
); diff --git a/packages/perseus/src/renderer.tsx b/packages/perseus/src/renderer.tsx index 80312ba144..e643967032 100644 --- a/packages/perseus/src/renderer.tsx +++ b/packages/perseus/src/renderer.tsx @@ -576,7 +576,7 @@ class Renderer Widgets.getTracking(widgetInfo && widgetInfo.type), ); } - +console.log(`Renderer - Widget Props: `, widgetProps); return { ...widgetProps, widgetId: widgetId, diff --git a/packages/perseus/src/widgets/numeric-input/numeric-input.tsx b/packages/perseus/src/widgets/numeric-input/numeric-input.tsx index bd8bf29dc6..6a7f698e3b 100644 --- a/packages/perseus/src/widgets/numeric-input/numeric-input.tsx +++ b/packages/perseus/src/widgets/numeric-input/numeric-input.tsx @@ -145,7 +145,7 @@ export class NumericInput // Ensure no duplicate tooltip text from simplified and unsimplified // versions of the same format examples = _.uniq(examples); - +console.log(`Examples: `, examples); return [this.context.strings.yourAnswer].concat(examples); } @@ -227,6 +227,7 @@ export class NumericInput }; render(): React.ReactNode { + console.log(`Widget Top-Level Render: `, this.props.answerForms); let labelText = this.props.labelText; if (labelText == null || labelText === "") { labelText = this.context.strings.yourAnswerLabel; From 5c62fb0cc27c2cc578013a24ef61c1c772f809eb Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Fri, 13 Dec 2024 17:30:42 -0800 Subject: [PATCH 17/28] Remove debugging. Group unsimplified options via fieldset Update unit tests accordingly. --- packages/perseus-editor/src/editor-page.tsx | 1 - packages/perseus-editor/src/editor.tsx | 1 - .../__tests__/numeric-input-editor.test.tsx | 24 ++++++++++++----- .../src/widgets/numeric-input-editor.tsx | 26 ++++++++----------- packages/perseus/src/renderer.tsx | 2 +- .../widgets/numeric-input/numeric-input.tsx | 3 +-- 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/perseus-editor/src/editor-page.tsx b/packages/perseus-editor/src/editor-page.tsx index 4347a9af2d..2f6c1bd684 100644 --- a/packages/perseus-editor/src/editor-page.tsx +++ b/packages/perseus-editor/src/editor-page.tsx @@ -208,7 +208,6 @@ class EditorPage extends React.Component { handleChange: ChangeHandler = (toChange, cb, silent) => { const newProps = _(this.props).pick("question", "hints", "answerArea"); _(newProps).extend(toChange); - console.log(`Editor Page - newProps: `, newProps); this.props.onChange(newProps, cb, silent); }; diff --git a/packages/perseus-editor/src/editor.tsx b/packages/perseus-editor/src/editor.tsx index 9aeec8c99c..003f30bbaf 100644 --- a/packages/perseus-editor/src/editor.tsx +++ b/packages/perseus-editor/src/editor.tsx @@ -281,7 +281,6 @@ class Editor extends React.Component { ) => { const widgets = Object.assign({}, this.props.widgets); widgets[id] = Object.assign({}, widgets[id], newWidgetInfo); - console.log(`Editor - Widgets[id]: `, widgets[id]); this.props.onChange({widgets}, cb, silent); }; diff --git a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx index c9e17f17fb..90ac324db9 100644 --- a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx +++ b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx @@ -1,6 +1,6 @@ import {Dependencies} from "@khanacademy/perseus"; import {RenderStateRoot} from "@khanacademy/wonder-blocks-core"; -import {render, screen, waitFor} from "@testing-library/react"; +import {render, screen, waitFor, within} from "@testing-library/react"; import {userEvent as userEventLib} from "@testing-library/user-event"; import * as React from "react"; @@ -131,7 +131,7 @@ describe("numeric-input-editor", () => { }); const input = screen.getByRole("textbox", { - name: "Aria label", + name: "aria label", }); await userEvent.type(input, "a"); @@ -149,7 +149,11 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click(screen.getByRole("button", {name: "ungraded"})); + await userEvent.click( + within( + screen.getByRole("group", {name: /^Unsimplified answers are/}), + ).getByRole("radio", {name: "Ungraded"}), + ); expect(onChangeMock).toBeCalledWith( expect.objectContaining({ @@ -167,7 +171,11 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click(screen.getByRole("button", {name: "accepted"})); + await userEvent.click( + within( + screen.getByRole("group", {name: /^Unsimplified answers are/}), + ).getByRole("radio", {name: "Accepted"}), + ); expect(onChangeMock).toBeCalledWith( expect.objectContaining({ @@ -185,7 +193,11 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click(screen.getByRole("button", {name: "wrong"})); + await userEvent.click( + within( + screen.getByRole("group", {name: /^Unsimplified answers are/}), + ).getByRole("radio", {name: "Wrong"}), + ); expect(onChangeMock).toBeCalledWith( expect.objectContaining({ @@ -213,7 +225,7 @@ describe("numeric-input-editor", () => { wrapper: RenderStateRoot, }); - await userEvent.click(screen.getByTitle(name)); + await userEvent.click(screen.getByRole("checkbox", {name: name})); expect(onChangeMock).toBeCalledWith( expect.objectContaining({ diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index d431a345bf..7c0d01e6a3 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -137,17 +137,14 @@ class NumericInputEditor extends React.Component { onToggleAnswerForm = (answerIndex: number, answerForm) => { let answerForms: string[] = - this.props.answers[answerIndex]["answerForms"] ?? []; + [...this.props.answers[answerIndex]["answerForms"]] ?? []; const formSelected = answerForms.includes(answerForm); - console.log(`Answer Forms: `, answerForms); - console.log(`Form to toggle: `, answerForm); - console.log(`Is Selected: `, formSelected); if (!formSelected) { answerForms.push(answerForm); } else { answerForms = answerForms.filter((form) => form !== answerForm); } - this.updateAnswer(answerIndex, {answerForms: [...answerForms]}); + this.updateAnswer(answerIndex, "answerForms")(answerForms); }; onToggleHeading = (accordionName: string) => { @@ -194,9 +191,7 @@ class NumericInputEditor extends React.Component { }; updateAnswer = (choiceIndex, update) => { - console.log(`updateAnswer - choiceIndex, update`, choiceIndex, update); if (!_.isObject(update)) { - console.log(`updateAnswer - !_.isObject(update)`); return _.partial( (choiceIndex, key, value) => { const update: Record = {}; @@ -222,7 +217,6 @@ class NumericInputEditor extends React.Component { } answers[choiceIndex] = _.extend({}, answers[choiceIndex], update); - console.log(`Calling 'props.onChange' with: `, answers); this.props.onChange({answers: answers}); }; @@ -264,7 +258,6 @@ class NumericInputEditor extends React.Component { render() { const answers = this.props.answers; - console.log(`Top-Level Answers: `, this.props.answers.map(a=> a.answerForms?.join(":")).join("|")); const SettingOption = (props: { kind: "accent" | "transparent"; @@ -316,13 +309,16 @@ class NumericInputEditor extends React.Component { }; const unsimplifiedAnswers = (i: any) => ( -
- +
{answers[i]["status"] !== "correct" && ( - irrelevant for this status + <> + Unsimplified answers are + irrelevant for this status + )} {answers[i]["status"] === "correct" && ( - <> + + Unsimplified answers are

Normally select "ungraded". This will @@ -363,9 +359,9 @@ class NumericInputEditor extends React.Component { > Wrong - + )} -

+ ); const suggestedAnswerTypes = (i: any) => ( diff --git a/packages/perseus/src/renderer.tsx b/packages/perseus/src/renderer.tsx index e643967032..80312ba144 100644 --- a/packages/perseus/src/renderer.tsx +++ b/packages/perseus/src/renderer.tsx @@ -576,7 +576,7 @@ class Renderer Widgets.getTracking(widgetInfo && widgetInfo.type), ); } -console.log(`Renderer - Widget Props: `, widgetProps); + return { ...widgetProps, widgetId: widgetId, diff --git a/packages/perseus/src/widgets/numeric-input/numeric-input.tsx b/packages/perseus/src/widgets/numeric-input/numeric-input.tsx index 6a7f698e3b..bd8bf29dc6 100644 --- a/packages/perseus/src/widgets/numeric-input/numeric-input.tsx +++ b/packages/perseus/src/widgets/numeric-input/numeric-input.tsx @@ -145,7 +145,7 @@ export class NumericInput // Ensure no duplicate tooltip text from simplified and unsimplified // versions of the same format examples = _.uniq(examples); -console.log(`Examples: `, examples); + return [this.context.strings.yourAnswer].concat(examples); } @@ -227,7 +227,6 @@ console.log(`Examples: `, examples); }; render(): React.ReactNode { - console.log(`Widget Top-Level Render: `, this.props.answerForms); let labelText = this.props.labelText; if (labelText == null || labelText === "") { labelText = this.context.strings.yourAnswerLabel; From 1b2674d8047b54de0fafbbfb5dca58d7143da9b3 Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Fri, 13 Dec 2024 20:11:55 -0800 Subject: [PATCH 18/28] Add animation to accordion panels. --- .../components/perseus-editor-accordion.tsx | 4 ++- .../src/styles/perseus-editor.less | 34 ++++++++++++++++--- .../src/widgets/numeric-input-editor.tsx | 23 ++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/perseus-editor/src/components/perseus-editor-accordion.tsx b/packages/perseus-editor/src/components/perseus-editor-accordion.tsx index 16fc3ccf8d..45038c0f27 100644 --- a/packages/perseus-editor/src/components/perseus-editor-accordion.tsx +++ b/packages/perseus-editor/src/components/perseus-editor-accordion.tsx @@ -7,6 +7,7 @@ import * as React from "react"; import type {StyleType} from "@khanacademy/wonder-blocks-core"; type Props = { + animated?: boolean; children: React.ReactNode | React.ReactNode[]; header: string | React.ReactElement; expanded?: boolean; @@ -16,7 +17,7 @@ type Props = { }; const PerseusEditorAccordion = (props: Props) => { - const {children, header, expanded, containerStyle, panelStyle, onToggle} = + const {animated, children, header, expanded, containerStyle, panelStyle, onToggle} = props; return ( @@ -27,6 +28,7 @@ const PerseusEditorAccordion = (props: Props) => { className="perseus-editor-accordion" > { onToggleAnswerForm = (answerIndex: number, answerForm) => { let answerForms: string[] = - [...this.props.answers[answerIndex]["answerForms"]] ?? []; + [...(this.props.answers[answerIndex]["answerForms"] ?? [])]; const formSelected = answerForms.includes(answerForm); if (!formSelected) { answerForms.push(answerForm); @@ -613,6 +613,7 @@ class NumericInputEditor extends React.Component { return (
{ this.onToggleAnswers(i); @@ -747,24 +748,28 @@ class NumericInputEditor extends React.Component { isOpen={this.state.showSettings} onToggle={this.onToggleHeading("Settings")} /> - {this.state.showSettings && inputSize} - {this.state.showSettings && rightAlign} - {this.state.showSettings && coefficientCheck} - {this.state.showSettings && labelText} +
+
+ {inputSize} + {rightAlign} + {coefficientCheck} + {labelText} +
+
- {this.state.showAnswers && ( - <> +
+
{generateInputAnswerEditors()} - - )} +
+
); } From f4eeaea2a941e1520f61ba12ffed4e4d93c589aa Mon Sep 17 00:00:00 2001 From: Mark Fitzgerald Date: Mon, 16 Dec 2024 12:27:29 -0800 Subject: [PATCH 19/28] Change option labels to fieldset/legend to improve accessibility and aid with unit tests. Update unit tests accordingly. --- .../components/perseus-editor-accordion.tsx | 11 +- .../src/styles/perseus-editor.less | 13 +++ .../__tests__/numeric-input-editor.test.tsx | 25 +++-- .../src/widgets/numeric-input-editor.tsx | 102 ++++++++++-------- 4 files changed, 97 insertions(+), 54 deletions(-) diff --git a/packages/perseus-editor/src/components/perseus-editor-accordion.tsx b/packages/perseus-editor/src/components/perseus-editor-accordion.tsx index 45038c0f27..63ea6747a4 100644 --- a/packages/perseus-editor/src/components/perseus-editor-accordion.tsx +++ b/packages/perseus-editor/src/components/perseus-editor-accordion.tsx @@ -17,8 +17,15 @@ type Props = { }; const PerseusEditorAccordion = (props: Props) => { - const {animated, children, header, expanded, containerStyle, panelStyle, onToggle} = - props; + const { + animated, + children, + header, + expanded, + containerStyle, + panelStyle, + onToggle, + } = props; return ( elements, so going old-school here */ + line-height: 24px; /* for alignment with items in same line (like pills or buttons) */ + padding-inline-end: 0.5em; + } + + .tooltip-for-legend { + display: inline-block; + line-height: 24px; + } } // Are any widgets capable of overflowing in the editor interface? @@ -264,6 +275,8 @@ .perseus-editor-accordion-content { overflow: hidden; + margin: 0 -1px; /* allows focus ring on accordion to show */ + padding: 0 1px; } } diff --git a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx index 90ac324db9..771ebb7cd9 100644 --- a/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx +++ b/packages/perseus-editor/src/widgets/__tests__/numeric-input-editor.test.tsx @@ -41,7 +41,10 @@ describe("numeric-input-editor", () => { }); await userEvent.click( - screen.getByRole("button", {name: "Normal (80px)"}), + within(screen.getByRole("group", {name: /^Width/})).getByRole( + "radio", + {name: "Normal (80px)"}, + ), ); expect(onChangeMock).toBeCalledWith( @@ -58,7 +61,10 @@ describe("numeric-input-editor", () => { }); await userEvent.click( - screen.getByRole("button", {name: "Small (40px)"}), + within(screen.getByRole("group", {name: /^Width/})).getByRole( + "radio", + {name: "Small (40px)"}, + ), ); expect(onChangeMock).toBeCalledWith( @@ -75,7 +81,10 @@ describe("numeric-input-editor", () => { }); await userEvent.click( - screen.getByRole("checkbox", {name: "Right alignment"}), + within(screen.getByRole("group", {name: /^Alignment/})).getByRole( + "radio", + {name: "Right"}, + ), ); expect(onChangeMock).toBeCalledWith({rightAlign: true}); @@ -89,7 +98,9 @@ describe("numeric-input-editor", () => { }); await userEvent.click( - screen.getByRole("checkbox", {name: "Coefficient"}), + within( + screen.getByRole("group", {name: /^Number style/}), + ).getByRole("radio", {name: "Coefficient"}), ); expect(onChangeMock).toBeCalledWith({coefficient: true}); @@ -103,9 +114,9 @@ describe("numeric-input-editor", () => { }); await userEvent.click( - screen.getByRole("checkbox", { - name: "Strictly match only these formats", - }), + within( + screen.getByRole("group", {name: /^Answer formats are/}), + ).getByRole("radio", {name: "Required"}), ); expect(onChangeMock).toBeCalledWith({ diff --git a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx index 0cddccf25e..201f7e1944 100644 --- a/packages/perseus-editor/src/widgets/numeric-input-editor.tsx +++ b/packages/perseus-editor/src/widgets/numeric-input-editor.tsx @@ -136,8 +136,9 @@ class NumericInputEditor extends React.Component { }; onToggleAnswerForm = (answerIndex: number, answerForm) => { - let answerForms: string[] = - [...(this.props.answers[answerIndex]["answerForms"] ?? [])]; + let answerForms: string[] = [ + ...(this.props.answers[answerIndex]["answerForms"] ?? []), + ]; const formSelected = answerForms.includes(answerForm); if (!formSelected) { answerForms.push(answerForm); @@ -269,7 +270,6 @@ class NumericInputEditor extends React.Component { const {kind, onClick, ariaLabel, children} = props; const style = { marginRight: "8px", - marginTop: "4px", }; const role = props.role ?? "radio"; const pillProps = { @@ -312,31 +312,38 @@ class NumericInputEditor extends React.Component {
{answers[i]["status"] !== "correct" && ( <> - Unsimplified answers are - irrelevant for this status + + Unsimplified answers are irrelevant for this status + )} {answers[i]["status"] === "correct" && ( - - Unsimplified answers are - -

- Normally select "ungraded". This will - user a message saying the answer is correct but - simplified. The user will then have to simplify - re-enter, but will not be penalized. (5th grade - after) -

-

- Select "accepted" only if the user is - expected to know how to simplify fractions yet. - (Anything prior to 5th grade) -

-

- Select "wrong" only if we are - specifically assessing the ability to simplify. -

-
+ <> + + Unsimplified answers are + + + +

+ Normally select "ungraded". This + will give the user a message saying the + answer is correct but not simplified. The + user will then have to simplify it and + re-enter, but will not be penalized. (5th + grade and after) +

+

+ Select "accepted" only if the user + is not expected to know how to simplify + fractions yet. (Anything prior to 5th grade) +

+

+ Select "wrong" only if we + are specifically assessing the ability to + simplify. +

+
+

{ answerProperty="simplify" value="enforced" > - Wrong + Wrong -
+ )}
); @@ -418,9 +425,8 @@ class NumericInputEditor extends React.Component { ); })}
-
- -
+
+ Answer formats are: { > Required -
+ ); const inputSize = ( -
- +
+ Width: { area is too narrow to fit them.

-
+ ); const rightAlign = ( -
- +
+ Alignment: { > Right -
+ ); const labelText = ( @@ -534,8 +540,8 @@ class NumericInputEditor extends React.Component { ); const coefficientCheck = ( -
- +
+ Number style: { for -1 and an empty string to mean 1.

-
+ ); const instructions = { @@ -685,8 +691,10 @@ class NumericInputEditor extends React.Component { onChange={this.updateAnswer(i, "maxError")} />
-
- +
+ + Status: + { > Ungraded -
+ {unsimplifiedAnswers(i)}
Message shown to user in article: @@ -748,7 +756,9 @@ class NumericInputEditor extends React.Component { isOpen={this.state.showSettings} onToggle={this.onToggleHeading("Settings")} /> -
+
{inputSize} {rightAlign} @@ -762,7 +772,9 @@ class NumericInputEditor extends React.Component { isOpen={this.state.showAnswers} onToggle={this.onToggleHeading("Answers")} /> -
+
{generateInputAnswerEditors()}