@khanacademy/perseus-editor 4.1.5 → 4.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +9 -6
- package/.babelrc.js +0 -8
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -1155
- package/src/README.md +0 -7
- package/src/__stories__/editor-page.stories.tsx +0 -65
- package/src/__stories__/editor.stories.tsx +0 -134
- package/src/__stories__/item-editor.stories.tsx +0 -41
- package/src/__stories__/tex-error-view.stories.tsx +0 -26
- package/src/__testdata__/input-number.testdata.ts +0 -27
- package/src/__tests__/editor.test.tsx +0 -142
- package/src/__tests__/hint-editor.test.tsx +0 -83
- package/src/__tests__/i18n.test.ts +0 -501
- package/src/__tests__/integration.test.ts +0 -5
- package/src/__tests__/item-extras-editor.test.tsx +0 -85
- package/src/__tests__/traversal.test.ts +0 -377
- package/src/all-editors.test.ts +0 -16
- package/src/all-editors.ts +0 -83
- package/src/all-widgets.ts +0 -7
- package/src/article-editor.tsx +0 -438
- package/src/components/__stories__/blur-input.stories.tsx +0 -22
- package/src/components/__stories__/section-control-button.stories.tsx +0 -26
- package/src/components/__tests__/blur-input.test.tsx +0 -67
- package/src/components/blur-input.tsx +0 -69
- package/src/components/checkbox.tsx +0 -194
- package/src/components/device-framer.tsx +0 -90
- package/src/components/drag-target.tsx +0 -87
- package/src/components/dropdown-option.tsx +0 -311
- package/src/components/form-wrapped-text-field.tsx +0 -245
- package/src/components/graph-points-count-selector.tsx +0 -31
- package/src/components/graph-settings.tsx +0 -627
- package/src/components/graph-type-selector.tsx +0 -31
- package/src/components/hover-behavior.ts +0 -177
- package/src/components/json-editor.tsx +0 -121
- package/src/components/link.ts +0 -173
- package/src/components/section-control-button.tsx +0 -53
- package/src/components/segment-count-selector.tsx +0 -27
- package/src/components/simple-button.tsx +0 -85
- package/src/components/sortable.tsx +0 -187
- package/src/components/util.ts +0 -38
- package/src/components/viewport-resizer.tsx +0 -72
- package/src/components/widget-editor.tsx +0 -222
- package/src/components/widget-select.tsx +0 -48
- package/src/diffs/__stories__/perseus-diff-wrapper.tsx +0 -11
- package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -855
- package/src/diffs/__stories__/tags-diff.stories.tsx +0 -36
- package/src/diffs/__stories__/text-diff.stories.tsx +0 -35
- package/src/diffs/__tests__/split-diff.test.ts +0 -33
- package/src/diffs/__tests__/string-array-diff.test.ts +0 -73
- package/src/diffs/__tests__/structured-item-diff.test.ts +0 -433
- package/src/diffs/__tests__/widget-diff-performer.test.ts +0 -43
- package/src/diffs/article-diff.tsx +0 -69
- package/src/diffs/item-diff.tsx +0 -76
- package/src/diffs/renderer-diff.tsx +0 -137
- package/src/diffs/split-diff.ts +0 -31
- package/src/diffs/string-array-diff.ts +0 -74
- package/src/diffs/structured-item-diff.tsx +0 -275
- package/src/diffs/tags-diff.tsx +0 -49
- package/src/diffs/text-diff.tsx +0 -193
- package/src/diffs/widget-diff-performer.ts +0 -69
- package/src/diffs/widget-diff.tsx +0 -290
- package/src/editor-page.tsx +0 -325
- package/src/editor.tsx +0 -1099
- package/src/hint-editor.tsx +0 -469
- package/src/i18n.ts +0 -140
- package/src/iframe-content-renderer.tsx +0 -188
- package/src/index.ts +0 -30
- package/src/item-editor.tsx +0 -158
- package/src/item-extras-editor.tsx +0 -230
- package/src/katex-mhchem/index.js +0 -73
- package/src/katex-mhchem/parser.js +0 -2659
- package/src/multirenderer-editor.tsx +0 -1061
- package/src/stateful-article-editor.tsx +0 -173
- package/src/stateful-editor-page.tsx +0 -88
- package/src/styles/perseus-editor.less +0 -1321
- package/src/testing-widgets.ts +0 -11
- package/src/tex-error-view.tsx +0 -90
- package/src/util/points.test.ts +0 -24
- package/src/util/points.ts +0 -13
- package/src/util/register-all-widgets-and-editors-for-testing.test.ts +0 -17
- package/src/util/register-all-widgets-and-editors-for-testing.ts +0 -17
- package/src/version.ts +0 -10
- package/src/widgets/__stories__/categorizer-editor.stories.tsx +0 -24
- package/src/widgets/__stories__/definition-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/dropdown-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/explanation-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/expression-editor.stories.tsx +0 -122
- package/src/widgets/__stories__/image-editor.stories.tsx +0 -72
- package/src/widgets/__stories__/input-number-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/interaction-editor.stories.tsx +0 -37
- package/src/widgets/__stories__/label-image-editor.stories.tsx +0 -99
- package/src/widgets/__stories__/matcher-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/number-line-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/numeric-input-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/radio-editor.stories.tsx +0 -116
- package/src/widgets/__stories__/sorter-editor.stories.tsx +0 -18
- package/src/widgets/__tests__/categorizer-editor.test.tsx +0 -51
- package/src/widgets/__tests__/definition-editor.test.tsx +0 -41
- package/src/widgets/__tests__/dropdown-editor.test.tsx +0 -57
- package/src/widgets/__tests__/explanation-editor.test.tsx +0 -59
- package/src/widgets/__tests__/expression-editor.test.tsx +0 -403
- package/src/widgets/__tests__/input-number-editor.test.tsx +0 -128
- package/src/widgets/__tests__/matcher-editor.test.tsx +0 -47
- package/src/widgets/__tests__/number-line-editor.test.tsx +0 -144
- package/src/widgets/__tests__/numeric-input-editor.test.tsx +0 -177
- package/src/widgets/__tests__/radio-editor.test.tsx +0 -118
- package/src/widgets/__tests__/sorter-editor.test.tsx +0 -55
- package/src/widgets/categorizer-editor.tsx +0 -104
- package/src/widgets/cs-program-editor.tsx +0 -256
- package/src/widgets/definition-editor.tsx +0 -77
- package/src/widgets/deprecated-standin-editor.tsx +0 -29
- package/src/widgets/dropdown-editor.tsx +0 -211
- package/src/widgets/example-graphie-widget-editor.tsx +0 -70
- package/src/widgets/example-graphie-widget.tsx +0 -172
- package/src/widgets/example-widget-editor.tsx +0 -62
- package/src/widgets/example-widget.tsx +0 -154
- package/src/widgets/explanation-editor.tsx +0 -92
- package/src/widgets/expression-editor.tsx +0 -557
- package/src/widgets/graded-group-editor.tsx +0 -161
- package/src/widgets/graded-group-set-editor.tsx +0 -104
- package/src/widgets/grapher-editor.tsx +0 -155
- package/src/widgets/group-editor.tsx +0 -83
- package/src/widgets/iframe-editor.tsx +0 -202
- package/src/widgets/image-editor.tsx +0 -367
- package/src/widgets/input-number-editor.tsx +0 -288
- package/src/widgets/interaction-editor/constraint-editor.tsx +0 -134
- package/src/widgets/interaction-editor/function-editor.tsx +0 -108
- package/src/widgets/interaction-editor/index.ts +0 -3
- package/src/widgets/interaction-editor/interaction-editor.tsx +0 -749
- package/src/widgets/interaction-editor/label-editor.tsx +0 -87
- package/src/widgets/interaction-editor/line-editor.tsx +0 -125
- package/src/widgets/interaction-editor/mathquill-input.tsx +0 -81
- package/src/widgets/interaction-editor/movable-line-editor.tsx +0 -121
- package/src/widgets/interaction-editor/movable-point-editor.tsx +0 -89
- package/src/widgets/interaction-editor/parametric-editor.tsx +0 -117
- package/src/widgets/interaction-editor/point-editor.tsx +0 -72
- package/src/widgets/interaction-editor/rectangle-editor.tsx +0 -96
- package/src/widgets/interactive-graph-editor.tsx +0 -571
- package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -82
- package/src/widgets/label-image/__stories__/behavior.stories.tsx +0 -31
- package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -75
- package/src/widgets/label-image/__stories__/question-markers.stories.tsx +0 -107
- package/src/widgets/label-image/__stories__/select-image.stories.tsx +0 -59
- package/src/widgets/label-image/answer-choices.tsx +0 -231
- package/src/widgets/label-image/behavior.tsx +0 -122
- package/src/widgets/label-image/marker.tsx +0 -290
- package/src/widgets/label-image/question-markers.tsx +0 -176
- package/src/widgets/label-image/select-image.tsx +0 -81
- package/src/widgets/label-image-editor.tsx +0 -252
- package/src/widgets/lights-puzzle-editor.tsx +0 -278
- package/src/widgets/matcher-editor.tsx +0 -149
- package/src/widgets/matrix-editor.tsx +0 -156
- package/src/widgets/measurer-editor.tsx +0 -253
- package/src/widgets/molecule-editor.tsx +0 -64
- package/src/widgets/number-line-editor.tsx +0 -512
- package/src/widgets/numeric-input-editor.tsx +0 -536
- package/src/widgets/orderer-editor.tsx +0 -172
- package/src/widgets/passage-editor.tsx +0 -117
- package/src/widgets/passage-ref-editor.tsx +0 -80
- package/src/widgets/passage-ref-target-editor.tsx +0 -49
- package/src/widgets/plotter-editor.tsx +0 -542
- package/src/widgets/radio/editor.tsx +0 -398
- package/src/widgets/reaction-diagram-editor.tsx +0 -131
- package/src/widgets/sequence-editor.tsx +0 -205
- package/src/widgets/simple-markdown-tester-editor.tsx +0 -86
- package/src/widgets/simple-markdown-tester.tsx +0 -92
- package/src/widgets/simulator-editor.tsx +0 -146
- package/src/widgets/sorter-editor.tsx +0 -103
- package/src/widgets/table-editor.tsx +0 -164
- package/src/widgets/unit-editor.tsx +0 -299
- package/src/widgets/video-editor.tsx +0 -73
- package/tsconfig-build.json +0 -22
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/sort-comp */
|
|
2
|
-
import * as KAS from "@khanacademy/kas";
|
|
3
|
-
import {
|
|
4
|
-
components,
|
|
5
|
-
Changeable,
|
|
6
|
-
Dependencies,
|
|
7
|
-
Expression,
|
|
8
|
-
PerseusExpressionAnswerFormConsidered,
|
|
9
|
-
} from "@khanacademy/perseus";
|
|
10
|
-
import {isTruthy} from "@khanacademy/wonder-stuff-core";
|
|
11
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
12
|
-
import lens from "hubble";
|
|
13
|
-
import * as React from "react";
|
|
14
|
-
import _ from "underscore";
|
|
15
|
-
|
|
16
|
-
import SortableArea from "../components/sortable";
|
|
17
|
-
|
|
18
|
-
import type {PerseusExpressionWidgetOptions} from "@khanacademy/perseus";
|
|
19
|
-
|
|
20
|
-
const {InfoTip, PropCheckBox} = components;
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
widgetId?: any;
|
|
24
|
-
value?: string;
|
|
25
|
-
} & Omit<PerseusExpressionWidgetOptions, "buttonsVisible"> &
|
|
26
|
-
Changeable.ChangeableProps;
|
|
27
|
-
|
|
28
|
-
// types for iterables
|
|
29
|
-
type AnswerForm = PerseusExpressionWidgetOptions["answerForms"][number];
|
|
30
|
-
type LegacyButtonSet = PerseusExpressionWidgetOptions["buttonSets"][number];
|
|
31
|
-
|
|
32
|
-
type DefaultProps = {
|
|
33
|
-
answerForms: Props["answerForms"];
|
|
34
|
-
times: Props["times"];
|
|
35
|
-
buttonSets: Props["buttonSets"];
|
|
36
|
-
functions: Props["functions"];
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const buttonSetsList: any = [
|
|
40
|
-
"basic",
|
|
41
|
-
"trig",
|
|
42
|
-
"prealgebra",
|
|
43
|
-
"logarithms",
|
|
44
|
-
"basic relations",
|
|
45
|
-
"advanced relations",
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const parseAnswerKey = ({key}: AnswerForm): number => {
|
|
49
|
-
// We don't throw here because there is data stored in some
|
|
50
|
-
// exercises/articles where the answer forms don't have a key. If we throw,
|
|
51
|
-
// it blocks content editors from loading the page at all.
|
|
52
|
-
// TODO(Jeremy): find a way to handle these answer forms that are missing
|
|
53
|
-
// keys more gracefully.
|
|
54
|
-
return Number.parseInt(key ?? "");
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Pick a key that isn't currently used by an answer in answerForms
|
|
58
|
-
const _makeNewKey = (answerForms: ReadonlyArray<AnswerForm>) => {
|
|
59
|
-
// first note all the currently used keys in an array, used like a map :3
|
|
60
|
-
// note that this automatically updates the array's length property to
|
|
61
|
-
// be one past the largest key.
|
|
62
|
-
const usedKeys: Array<boolean> = [];
|
|
63
|
-
answerForms.forEach((ans) => {
|
|
64
|
-
usedKeys[parseAnswerKey(ans)] = true;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// then scan through the array to find the first unused (undefined) key
|
|
68
|
-
for (let i = 0; i < usedKeys.length; i++) {
|
|
69
|
-
if (!usedKeys[i]) {
|
|
70
|
-
return i;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// if we didn't find a key, make one bigger than all the other keys,
|
|
75
|
-
// since that's how the length property is defined to work on arrays
|
|
76
|
-
return usedKeys.length;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
class ExpressionEditor extends React.Component<Props> {
|
|
80
|
-
static widgetName = "expression" as const;
|
|
81
|
-
|
|
82
|
-
static defaultProps: DefaultProps = {
|
|
83
|
-
answerForms: [],
|
|
84
|
-
times: false,
|
|
85
|
-
buttonSets: ["basic"],
|
|
86
|
-
functions: ["f", "g", "h"],
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
change(...args) {
|
|
90
|
-
return Changeable.change.apply(this, args);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
render(): React.ReactNode {
|
|
94
|
-
const answerOptions = this.props.answerForms
|
|
95
|
-
.map((ans: AnswerForm) => {
|
|
96
|
-
const key = parseAnswerKey(ans);
|
|
97
|
-
|
|
98
|
-
const expressionProps = {
|
|
99
|
-
// note we're using
|
|
100
|
-
// *this.props*.{times,functions,buttonSets} since each
|
|
101
|
-
// answer area has the same settings for those
|
|
102
|
-
times: this.props.times,
|
|
103
|
-
functions: this.props.functions,
|
|
104
|
-
buttonSets: this.props.buttonSets,
|
|
105
|
-
|
|
106
|
-
buttonsVisible: "focused",
|
|
107
|
-
form: ans.form,
|
|
108
|
-
simplify: ans.simplify,
|
|
109
|
-
value: ans.value,
|
|
110
|
-
|
|
111
|
-
onChange: (props) => this.updateForm(key, props),
|
|
112
|
-
trackInteraction: () => {},
|
|
113
|
-
|
|
114
|
-
widgetId: this.props.widgetId + "-" + ans.key,
|
|
115
|
-
} as const;
|
|
116
|
-
|
|
117
|
-
return lens(ans)
|
|
118
|
-
.merge([], {
|
|
119
|
-
draggable: true,
|
|
120
|
-
onChange: (props) =>
|
|
121
|
-
this.updateForm(
|
|
122
|
-
Number.parseInt(ans.key ?? ""),
|
|
123
|
-
props,
|
|
124
|
-
),
|
|
125
|
-
onDelete: () => this.handleRemoveForm(key),
|
|
126
|
-
expressionProps: expressionProps,
|
|
127
|
-
})
|
|
128
|
-
.freeze();
|
|
129
|
-
})
|
|
130
|
-
.map((obj) => <AnswerOption key={obj.key} {...obj} />);
|
|
131
|
-
|
|
132
|
-
const sortable = (
|
|
133
|
-
<SortableArea
|
|
134
|
-
components={answerOptions}
|
|
135
|
-
onReorder={this.handleReorder}
|
|
136
|
-
className="answer-options-list"
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// checkboxes to choose which sets of input buttons are shown
|
|
141
|
-
const buttonSetChoices = buttonSetsList.map((name) => {
|
|
142
|
-
// The first one gets special cased to always be checked, disabled,
|
|
143
|
-
// and float left.
|
|
144
|
-
const isFirst = name === "basic";
|
|
145
|
-
const checked =
|
|
146
|
-
this.props.buttonSets.includes(
|
|
147
|
-
name as PerseusExpressionWidgetOptions["buttonSets"][number],
|
|
148
|
-
) || isFirst;
|
|
149
|
-
return (
|
|
150
|
-
<label className="button-set-label" key={name}>
|
|
151
|
-
<input
|
|
152
|
-
type="checkbox"
|
|
153
|
-
checked={checked}
|
|
154
|
-
disabled={isFirst}
|
|
155
|
-
onChange={() => this.handleButtonSet(name)}
|
|
156
|
-
/>
|
|
157
|
-
{name}
|
|
158
|
-
</label>
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const {TeX} = Dependencies.getDependencies(); // OldExpression only
|
|
163
|
-
|
|
164
|
-
buttonSetChoices.unshift(
|
|
165
|
-
<label className="button-set-label" key="show-div">
|
|
166
|
-
<input type="checkbox" onChange={this.handleToggleDiv} />
|
|
167
|
-
<span className="show-div-button">
|
|
168
|
-
show <TeX>\div</TeX> button
|
|
169
|
-
</span>
|
|
170
|
-
</label>,
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<div className="perseus-widget-expression-editor">
|
|
175
|
-
<h3 className="expression-editor-h3">Global Options</h3>
|
|
176
|
-
|
|
177
|
-
<div>
|
|
178
|
-
<PropCheckBox
|
|
179
|
-
times={this.props.times}
|
|
180
|
-
onChange={this.props.onChange}
|
|
181
|
-
labelAlignment="right"
|
|
182
|
-
label="Use × for rendering multiplication instead of a
|
|
183
|
-
center dot."
|
|
184
|
-
/>
|
|
185
|
-
<InfoTip>
|
|
186
|
-
<p>
|
|
187
|
-
For pre-algebra problems this option displays
|
|
188
|
-
multiplication as \times instead of \cdot in both
|
|
189
|
-
the rendered output and the acceptable formats
|
|
190
|
-
examples.
|
|
191
|
-
</p>
|
|
192
|
-
</InfoTip>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<div>
|
|
196
|
-
<label>
|
|
197
|
-
{"Function variables: "}
|
|
198
|
-
<input
|
|
199
|
-
type="text"
|
|
200
|
-
defaultValue={this.props.functions.join(" ")}
|
|
201
|
-
onChange={this.handleFunctions}
|
|
202
|
-
/>
|
|
203
|
-
</label>
|
|
204
|
-
<InfoTip>
|
|
205
|
-
<p>
|
|
206
|
-
Single-letter variables listed here will be
|
|
207
|
-
interpreted as functions. This let us know that f(x)
|
|
208
|
-
means "f of x" and not "f times x".
|
|
209
|
-
</p>
|
|
210
|
-
</InfoTip>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<div>
|
|
214
|
-
<div>Button sets:</div>
|
|
215
|
-
{buttonSetChoices}
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<h3 className="expression-editor-h3">Answers</h3>
|
|
219
|
-
|
|
220
|
-
<p style={{margin: "4px 0"}}>
|
|
221
|
-
student responses area matched against these from top to
|
|
222
|
-
bottom
|
|
223
|
-
</p>
|
|
224
|
-
|
|
225
|
-
{sortable}
|
|
226
|
-
|
|
227
|
-
<div>
|
|
228
|
-
<button
|
|
229
|
-
className="simple-button orange"
|
|
230
|
-
style={{fontSize: 13}}
|
|
231
|
-
onClick={this.newAnswer}
|
|
232
|
-
type="button"
|
|
233
|
-
>
|
|
234
|
-
Add new answer
|
|
235
|
-
</button>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
serialize: () => any = () => {
|
|
242
|
-
const formSerializables = [
|
|
243
|
-
"value",
|
|
244
|
-
"form",
|
|
245
|
-
"simplify",
|
|
246
|
-
"considered",
|
|
247
|
-
// it's a little weird to serialize the react key, but saves some
|
|
248
|
-
// effort reconstructing them when this item is loaded later.
|
|
249
|
-
"key",
|
|
250
|
-
];
|
|
251
|
-
const serializables = [
|
|
252
|
-
"answerForms",
|
|
253
|
-
"buttonSets",
|
|
254
|
-
"functions",
|
|
255
|
-
"times",
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
const answerForms = this.props.answerForms.map((form) => {
|
|
259
|
-
return _(form).pick(formSerializables);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
return lens(this.props)
|
|
263
|
-
.set(["answerForms"], answerForms)
|
|
264
|
-
.mod([], (props) => _(props).pick(serializables))
|
|
265
|
-
.freeze();
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
getSaveWarnings: () => any = () => {
|
|
269
|
-
const issues: Array<any | string> = [];
|
|
270
|
-
|
|
271
|
-
if (this.props.answerForms.length === 0) {
|
|
272
|
-
issues.push("No answers specified");
|
|
273
|
-
} else {
|
|
274
|
-
const hasCorrect = this.props.answerForms.some((form) => {
|
|
275
|
-
return form.considered === "correct";
|
|
276
|
-
});
|
|
277
|
-
if (!hasCorrect) {
|
|
278
|
-
issues.push("No correct answer specified");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
_(this.props.answerForms).each((form, ix) => {
|
|
282
|
-
if (this.props.value === "") {
|
|
283
|
-
issues.push(`Answer ${ix + 1} is empty`);
|
|
284
|
-
} else {
|
|
285
|
-
// note we're not using icu for content creators
|
|
286
|
-
const expression = KAS.parse(form.value, {
|
|
287
|
-
functions: this.props.functions,
|
|
288
|
-
});
|
|
289
|
-
if (!expression.parsed) {
|
|
290
|
-
issues.push(`Couldn't parse ${form.value}`);
|
|
291
|
-
} else if (
|
|
292
|
-
form.simplify &&
|
|
293
|
-
!expression.expr.isSimplified()
|
|
294
|
-
) {
|
|
295
|
-
issues.push(
|
|
296
|
-
`${form.value} isn't simplified, but is required" +
|
|
297
|
-
" to be`,
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// TODO(joel) - warn about:
|
|
304
|
-
// - unreachable answers (how??)
|
|
305
|
-
// - specific answers following unspecific answers
|
|
306
|
-
// - incorrect answers as the final form
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return issues;
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
_newEmptyAnswerForm: () => any = () => {
|
|
313
|
-
return {
|
|
314
|
-
considered: "correct",
|
|
315
|
-
form: false,
|
|
316
|
-
|
|
317
|
-
// note: the key means "n-th form created" - not "form in
|
|
318
|
-
// position n" and will stay the same for the life of this form
|
|
319
|
-
key: _makeNewKey(this.props.answerForms),
|
|
320
|
-
|
|
321
|
-
simplify: false,
|
|
322
|
-
value: "",
|
|
323
|
-
};
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
newAnswer: () => void = () => {
|
|
327
|
-
const answerForms = this.props.answerForms.slice();
|
|
328
|
-
answerForms.push(this._newEmptyAnswerForm());
|
|
329
|
-
this.change({answerForms});
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
handleRemoveForm: (answerKey: number) => void = (i) => {
|
|
333
|
-
const answerForms = this.props.answerForms.slice();
|
|
334
|
-
answerForms.splice(i, 1);
|
|
335
|
-
this.change({answerForms});
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
// called when the options (including the expression itself) to an answer
|
|
339
|
-
// form change
|
|
340
|
-
updateForm: (i: number, props: any) => void = (i, props) => {
|
|
341
|
-
const answerForms = lens(this.props.answerForms)
|
|
342
|
-
.merge([i], props)
|
|
343
|
-
.freeze();
|
|
344
|
-
|
|
345
|
-
this.change({answerForms});
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
handleReorder: (components: any) => void = (components) => {
|
|
349
|
-
const answerForms = components.map((component) => {
|
|
350
|
-
const form = _(component.props).pick(
|
|
351
|
-
"considered",
|
|
352
|
-
"form",
|
|
353
|
-
"simplify",
|
|
354
|
-
"value",
|
|
355
|
-
);
|
|
356
|
-
// @ts-expect-error - TS2339 - Property 'key' does not exist on type 'Pick<any, "form" | "value" | "simplify" | "considered">'.
|
|
357
|
-
form.key = component.key;
|
|
358
|
-
return form;
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
this.change({answerForms});
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// called when the selected buttonset changes
|
|
365
|
-
handleButtonSet: (changingName: string) => void = (changingName) => {
|
|
366
|
-
const buttonSetNames = buttonSetsList;
|
|
367
|
-
|
|
368
|
-
// Filter to preserve order - using .union and .difference would always
|
|
369
|
-
// move the last added button set to the end.
|
|
370
|
-
const buttonSets = buttonSetNames.filter((set) => {
|
|
371
|
-
return (
|
|
372
|
-
this.props.buttonSets.includes(set) !== (set === changingName)
|
|
373
|
-
);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
this.props.onChange({buttonSets});
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
handleToggleDiv: () => void = () => {
|
|
380
|
-
// We always want buttonSets to contain exactly one of "basic" and
|
|
381
|
-
// "basic+div". Toggle between the two of them.
|
|
382
|
-
// If someone can think of a more elegant formulation of this (there
|
|
383
|
-
// must be one!) feel free to change it.
|
|
384
|
-
let keep: LegacyButtonSet | undefined;
|
|
385
|
-
let remove: LegacyButtonSet | undefined;
|
|
386
|
-
if (this.props.buttonSets.includes("basic+div")) {
|
|
387
|
-
keep = "basic";
|
|
388
|
-
remove = "basic+div";
|
|
389
|
-
} else {
|
|
390
|
-
keep = "basic+div";
|
|
391
|
-
remove = "basic";
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const buttonSets = this.props.buttonSets
|
|
395
|
-
.filter((set) => set !== remove)
|
|
396
|
-
.concat(keep);
|
|
397
|
-
|
|
398
|
-
this.change("buttonSets", buttonSets);
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
// called when the correct answer changes
|
|
402
|
-
handleTexInsert: (arg1: string) => void = (str) => {
|
|
403
|
-
// eslint-disable-next-line react/no-string-refs
|
|
404
|
-
// @ts-expect-error - TS2339 - Property 'insert' does not exist on type 'ReactInstance'.
|
|
405
|
-
this.refs.expression.insert(str);
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
// called when the function variables change
|
|
409
|
-
handleFunctions: (arg1: React.ChangeEvent<HTMLInputElement>) => void = (
|
|
410
|
-
e,
|
|
411
|
-
) => {
|
|
412
|
-
const newProps: Record<string, any> = {};
|
|
413
|
-
newProps.functions = e.target.value.split(/[ ,]+/).filter(isTruthy);
|
|
414
|
-
this.props.onChange(newProps);
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Find the next element in arr after val, wrapping around to the first.
|
|
419
|
-
const findNextIn = function (arr: ReadonlyArray<string>, val: any) {
|
|
420
|
-
let ix = arr.indexOf(val);
|
|
421
|
-
ix = (ix + 1) % arr.length;
|
|
422
|
-
return arr[ix];
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
type AnswerOptionProps = {
|
|
426
|
-
considered: typeof PerseusExpressionAnswerFormConsidered[number];
|
|
427
|
-
expressionProps: any;
|
|
428
|
-
|
|
429
|
-
// Must the answer have the same form as this answer.
|
|
430
|
-
form: boolean;
|
|
431
|
-
|
|
432
|
-
// Must the answer be simplified.
|
|
433
|
-
simplify: boolean;
|
|
434
|
-
|
|
435
|
-
onDelete: () => void;
|
|
436
|
-
} & Changeable.ChangeableProps;
|
|
437
|
-
|
|
438
|
-
type AnswerOptionState = {
|
|
439
|
-
deleteFocused: boolean;
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
class AnswerOption extends React.Component<
|
|
443
|
-
AnswerOptionProps,
|
|
444
|
-
AnswerOptionState
|
|
445
|
-
> {
|
|
446
|
-
state = {deleteFocused: false};
|
|
447
|
-
|
|
448
|
-
handleDeleteBlur = () => {
|
|
449
|
-
this.setState({deleteFocused: false});
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
change = (...args) => {
|
|
453
|
-
return Changeable.change.apply(this, args);
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
render(): React.ReactNode {
|
|
457
|
-
let removeButton: React.ReactNode | null = null;
|
|
458
|
-
if (this.state.deleteFocused) {
|
|
459
|
-
removeButton = (
|
|
460
|
-
<button
|
|
461
|
-
type="button"
|
|
462
|
-
className="simple-button orange"
|
|
463
|
-
onClick={this.handleImSure}
|
|
464
|
-
onBlur={this.handleDeleteBlur}
|
|
465
|
-
>
|
|
466
|
-
I'm sure!
|
|
467
|
-
</button>
|
|
468
|
-
);
|
|
469
|
-
} else {
|
|
470
|
-
removeButton = (
|
|
471
|
-
<button
|
|
472
|
-
type="button"
|
|
473
|
-
className="simple-button orange"
|
|
474
|
-
onClick={this.handleDelete}
|
|
475
|
-
>
|
|
476
|
-
Delete
|
|
477
|
-
</button>
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return (
|
|
482
|
-
<div className="expression-answer-option">
|
|
483
|
-
<div className="answer-handle" />
|
|
484
|
-
|
|
485
|
-
<div className="answer-body">
|
|
486
|
-
<div className="answer-considered">
|
|
487
|
-
<div
|
|
488
|
-
onClick={this.toggleConsidered}
|
|
489
|
-
className={"answer-status " + this.props.considered}
|
|
490
|
-
>
|
|
491
|
-
{this.props.considered}
|
|
492
|
-
</div>
|
|
493
|
-
|
|
494
|
-
<div className="answer-expression">
|
|
495
|
-
<Expression {...this.props.expressionProps} />
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
|
|
499
|
-
<div className="answer-option">
|
|
500
|
-
<PropCheckBox
|
|
501
|
-
form={this.props.form}
|
|
502
|
-
onChange={this.props.onChange}
|
|
503
|
-
labelAlignment="right"
|
|
504
|
-
label="Answer expression must have the same form."
|
|
505
|
-
/>
|
|
506
|
-
<InfoTip>
|
|
507
|
-
<p>
|
|
508
|
-
The student's answer must be in the same form.
|
|
509
|
-
Commutativity and excess negative signs are
|
|
510
|
-
ignored.
|
|
511
|
-
</p>
|
|
512
|
-
</InfoTip>
|
|
513
|
-
</div>
|
|
514
|
-
|
|
515
|
-
<div className="answer-option">
|
|
516
|
-
<PropCheckBox
|
|
517
|
-
simplify={this.props.simplify}
|
|
518
|
-
onChange={this.props.onChange}
|
|
519
|
-
labelAlignment="right"
|
|
520
|
-
label="Answer expression must be fully expanded and
|
|
521
|
-
simplified."
|
|
522
|
-
/>
|
|
523
|
-
<InfoTip>
|
|
524
|
-
<p>
|
|
525
|
-
The student's answer must be fully expanded and
|
|
526
|
-
simplified. Answering this equation (x^2+2x+1)
|
|
527
|
-
with this factored equation (x+1)^2 will render
|
|
528
|
-
this response "Your answer is not fully expanded
|
|
529
|
-
and simplified."
|
|
530
|
-
</p>
|
|
531
|
-
</InfoTip>
|
|
532
|
-
</div>
|
|
533
|
-
|
|
534
|
-
<div className="remove-container">{removeButton}</div>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
handleImSure = () => {
|
|
541
|
-
this.props.onDelete();
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
handleDelete = () => {
|
|
545
|
-
this.setState({deleteFocused: true});
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
toggleConsidered = () => {
|
|
549
|
-
const newVal = findNextIn(
|
|
550
|
-
PerseusExpressionAnswerFormConsidered,
|
|
551
|
-
this.props.considered,
|
|
552
|
-
);
|
|
553
|
-
this.change({considered: newVal});
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
export default ExpressionEditor;
|