@khanacademy/perseus 49.2.1 → 50.0.0
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/article-renderer.d.ts +2 -6
- package/dist/components/graph.d.ts +1 -1
- package/dist/components/graphie.d.ts +1 -1
- package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +1 -4
- package/dist/components/highlighting/ui/highlighting-ui.d.ts +1 -4
- package/dist/components/i18n-context.d.ts +1 -1
- package/dist/components/input-with-examples.d.ts +1 -4
- package/dist/components/math-input.d.ts +2 -6
- package/dist/components/number-input.d.ts +2 -5
- package/dist/components/svg-image.d.ts +1 -1
- package/dist/es/index.js +547 -1380
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +40 -85
- package/dist/es/strings.js.map +1 -1
- package/dist/hint-renderer.d.ts +1 -4
- package/dist/hints-renderer.d.ts +2 -5
- package/dist/index.d.ts +5 -7
- package/dist/index.js +588 -1407
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +1 -1
- package/dist/renderer.d.ts +2 -5
- package/dist/server-item-renderer.d.ts +3 -7
- package/dist/strings.d.ts +8 -0
- package/dist/strings.js +40 -84
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/util/extract-perseus-data.d.ts +1 -1
- package/dist/util/parse-perseus-json/index.d.ts +33 -4
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +3 -3
- package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +1 -1
- package/dist/util/test-utils.d.ts +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/validation.types.d.ts +9 -3
- package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/prompt-types.d.ts +2 -1
- package/dist/widget-container.d.ts +1 -1
- package/dist/widget-type-utils.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +2 -5
- package/dist/widgets/cs-program/cs-program.d.ts +1 -1
- package/dist/widgets/definition/definition.d.ts +2 -5
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -4
- package/dist/widgets/dropdown/dropdown.d.ts +2 -5
- package/dist/widgets/explanation/explanation.d.ts +2 -5
- package/dist/widgets/expression/expression.d.ts +5 -8
- package/dist/widgets/graded-group/graded-group-answer-bar.d.ts +1 -4
- package/dist/widgets/graded-group/graded-group.d.ts +2 -5
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
- package/dist/widgets/grapher/grapher.d.ts +2 -2
- package/dist/widgets/group/group.d.ts +2 -5
- package/dist/widgets/iframe/iframe.d.ts +1 -1
- package/dist/widgets/image/image.d.ts +2 -5
- package/dist/widgets/input-number/input-number.d.ts +2 -6
- package/dist/widgets/interaction/interaction.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +4 -6
- package/dist/widgets/interactive-graphs/backgrounds/grid.d.ts +2 -2
- package/dist/widgets/interactive-graphs/backgrounds/legacy-grid.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/circle.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/components/angle-indicators.d.ts +1 -1
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +2 -2
- package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-label.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +1 -1
- package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +2 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
- package/dist/widgets/label-image/label-image.d.ts +2 -5
- package/dist/widgets/label-image/marker.d.ts +1 -4
- package/dist/widgets/matcher/matcher.d.ts +2 -5
- package/dist/widgets/matrix/matrix.d.ts +2 -5
- package/dist/widgets/measurer/measurer.d.ts +1 -1
- package/dist/widgets/mock-widgets/index.d.ts +2 -0
- package/dist/widgets/mock-widgets/mock-asset-loading-widget.d.ts +17 -0
- package/dist/widgets/mock-widgets/mock-widget.d.ts +47 -0
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +5 -0
- package/dist/widgets/molecule/molecule.d.ts +1 -1
- package/dist/widgets/number-line/number-line.d.ts +1 -4
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -6
- package/dist/widgets/orderer/orderer.d.ts +1 -1
- package/dist/widgets/passage/passage.d.ts +2 -5
- package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -5
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -5
- package/dist/widgets/plotter/plotter.d.ts +2 -5
- package/dist/widgets/radio/base-radio.d.ts +1 -1
- package/dist/widgets/radio/radio-component.d.ts +2 -5
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -1
- package/dist/widgets/table/table.d.ts +2 -5
- package/dist/widgets/video/video.d.ts +2 -5
- package/dist/widgets.d.ts +1 -1
- package/package.json +46 -45
- package/dist/interactive2/objective_.d.ts +0 -34
- package/dist/perseus-types.d.ts +0 -980
- package/dist/util/answer-types.d.ts +0 -69
- package/dist/util/math.d.ts +0 -29
package/dist/es/index.js
CHANGED
|
@@ -12,16 +12,17 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
|
|
|
12
12
|
import * as ReactDOM from 'react-dom';
|
|
13
13
|
import ReactDOM__default from 'react-dom';
|
|
14
14
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
15
|
+
import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
|
|
15
16
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
16
17
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
17
18
|
import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
18
19
|
import $ from 'jquery';
|
|
19
20
|
import { v4 } from 'uuid';
|
|
20
|
-
import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
21
|
+
import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
21
22
|
import PropTypes from 'prop-types';
|
|
22
|
-
import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
+
import { point, KhanMath, number as number$1, vector as vector$1, sum as sum$1, line } from '@khanacademy/kmath';
|
|
25
26
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
26
27
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
27
28
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -44,27 +45,196 @@ import cornersOutIcon from '@phosphor-icons/core/regular/corners-out.svg';
|
|
|
44
45
|
import Link from '@khanacademy/wonder-blocks-link';
|
|
45
46
|
|
|
46
47
|
function _extends() {
|
|
47
|
-
_extends = Object.assign
|
|
48
|
-
for (var
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
for (var key in source) {
|
|
52
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
53
|
-
target[key] = source[key];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
48
|
+
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
49
|
+
for (var e = 1; e < arguments.length; e++) {
|
|
50
|
+
var t = arguments[e];
|
|
51
|
+
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return _extends.apply(this, arguments);
|
|
53
|
+
return n;
|
|
54
|
+
}, _extends.apply(null, arguments);
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
/**
|
|
65
58
|
* The translated strings that are used to render Perseus.
|
|
66
59
|
*/
|
|
67
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Untranslated strings used in Perseus. To be used by an external
|
|
63
|
+
* translator to produce translated strings, passed in as `PerseusStrings`.
|
|
64
|
+
* !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
|
|
65
|
+
*/
|
|
66
|
+
const strings = {
|
|
67
|
+
closeKeypad: "close math keypad",
|
|
68
|
+
openKeypad: "open math keypad",
|
|
69
|
+
mathInputBox: "Math input box",
|
|
70
|
+
removeHighlight: "Remove highlight",
|
|
71
|
+
addHighlight: "Add highlight",
|
|
72
|
+
hintPos: "Hint #%(pos)s",
|
|
73
|
+
errorRendering: "Error rendering: %(error)s",
|
|
74
|
+
APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "12 pi or " + "2/3 pi",
|
|
75
|
+
EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
|
|
76
|
+
NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
|
|
77
|
+
MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
|
|
78
|
+
MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
|
|
79
|
+
WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
|
|
80
|
+
WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
|
|
81
|
+
invalidSelection: "Make sure you select something for every row.",
|
|
82
|
+
ERROR_TITLE: "Oops!",
|
|
83
|
+
ERROR_MESSAGE: "Sorry, I don't understand that!",
|
|
84
|
+
hints: "Hints",
|
|
85
|
+
getAnotherHint: "Get another hint",
|
|
86
|
+
deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
|
|
87
|
+
keepTrying: "Keep trying",
|
|
88
|
+
tryAgain: "Try again",
|
|
89
|
+
check: "Check",
|
|
90
|
+
correctExcited: "Correct!",
|
|
91
|
+
nextQuestion: "Next question",
|
|
92
|
+
skipToTitle: "Skip to %(title)s",
|
|
93
|
+
current: "Current",
|
|
94
|
+
correct: "Correct",
|
|
95
|
+
correctSelected: "Correct (selected)",
|
|
96
|
+
correctCrossedOut: "Correct (but you crossed it out)",
|
|
97
|
+
incorrect: "Incorrect",
|
|
98
|
+
incorrectSelected: "Incorrect (selected)",
|
|
99
|
+
hideExplanation: "Hide explanation",
|
|
100
|
+
explain: "Explain",
|
|
101
|
+
INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
|
|
102
|
+
DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
|
|
103
|
+
DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
|
|
104
|
+
integerExample: "an integer, like $6$",
|
|
105
|
+
properExample: "a *proper* fraction, like $1/2$ or $6/10$",
|
|
106
|
+
simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
|
|
107
|
+
improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
|
|
108
|
+
simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
|
|
109
|
+
mixedExample: "a mixed number, like $1\\\\ 3/4$",
|
|
110
|
+
decimalExample: "an *exact* decimal, like $0.75$",
|
|
111
|
+
percentExample: "a percent, like $12.34\\\\%$",
|
|
112
|
+
piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
|
|
113
|
+
yourAnswer: "**Your answer should be** ",
|
|
114
|
+
yourAnswerLabel: "Your answer:",
|
|
115
|
+
addPoints: "Click to add points",
|
|
116
|
+
addVertices: "Click to add vertices",
|
|
117
|
+
tapMultiple: "Tap each dot on the image to select all answers that apply.",
|
|
118
|
+
tapSingle: "Tap each dot on the image to select an answer.",
|
|
119
|
+
clickMultiple: "Click each dot on the image to select all answers that apply.",
|
|
120
|
+
clickSingle: "Click each dot on the image to select an answer.",
|
|
121
|
+
choices: "Choices:",
|
|
122
|
+
answers: {
|
|
123
|
+
one: "%(num)s answer",
|
|
124
|
+
other: "%(num)s answers"
|
|
125
|
+
},
|
|
126
|
+
hideAnswersToggleLabel: "Hide answer choices",
|
|
127
|
+
moves: {
|
|
128
|
+
one: "Moves: %(num)s",
|
|
129
|
+
other: "Moves: %(num)s"
|
|
130
|
+
},
|
|
131
|
+
clickTiles: "Click on the tiles to change the lights.",
|
|
132
|
+
turnOffLights: "You must turn on all of the lights to continue.",
|
|
133
|
+
fillAllCells: "Make sure you fill in all cells in the matrix.",
|
|
134
|
+
molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
|
|
135
|
+
switchDirection: "Switch direction",
|
|
136
|
+
circleOpen: "Make circle open",
|
|
137
|
+
circleFilled: "Make circle filled",
|
|
138
|
+
numDivisions: "Number of divisions:",
|
|
139
|
+
divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
|
|
140
|
+
lineRange: "lines %(lineRange)s",
|
|
141
|
+
lineNumber: "line %(lineNumber)s",
|
|
142
|
+
symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
|
|
143
|
+
symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
|
|
144
|
+
lineLabel: {
|
|
145
|
+
context: "a label next to a reading passage to denote the line number",
|
|
146
|
+
message: "Line"
|
|
147
|
+
},
|
|
148
|
+
beginningPassage: "Beginning of reading passage.",
|
|
149
|
+
beginningFootnotes: "Beginning of reading passage footnotes.",
|
|
150
|
+
endPassage: "End of reading passage.",
|
|
151
|
+
questionMarker: "[Marker for question %(number)s]",
|
|
152
|
+
circleMarker: "[Circle marker %(number)s]",
|
|
153
|
+
sentenceMarker: "[Sentence %(number)s]",
|
|
154
|
+
dragHandles: "Drag handles to make graph",
|
|
155
|
+
tapAddPoints: "Tap to add points",
|
|
156
|
+
false: "False",
|
|
157
|
+
true: "True",
|
|
158
|
+
no: "No",
|
|
159
|
+
yes: "Yes",
|
|
160
|
+
chooseCorrectNum: "Please choose the correct number of answers.",
|
|
161
|
+
notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
|
|
162
|
+
noneOfTheAbove: "None of the above",
|
|
163
|
+
chooseNumAnswers: "Choose %(numCorrect)s answers:",
|
|
164
|
+
chooseAllAnswers: "Choose all answers that apply:",
|
|
165
|
+
chooseOneAnswer: "Choose 1 answer:",
|
|
166
|
+
choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
|
|
167
|
+
choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
|
|
168
|
+
choiceCorrect: "(Choice %(letter)s, Correct)",
|
|
169
|
+
choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
|
|
170
|
+
choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
|
|
171
|
+
choiceIncorrect: "(Choice %(letter)s, Incorrect)",
|
|
172
|
+
choiceChecked: "(Choice %(letter)s, Checked)",
|
|
173
|
+
choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
|
|
174
|
+
choice: "(Choice %(letter)s)",
|
|
175
|
+
crossOut: "Cross out",
|
|
176
|
+
crossOutOption: "Cross out option",
|
|
177
|
+
crossOutChoice: "Cross out Choice %(letter)s",
|
|
178
|
+
bringBack: "Bring back",
|
|
179
|
+
openMenuForChoice: "Open menu for Choice %(letter)s",
|
|
180
|
+
letters: {
|
|
181
|
+
context: "This is a list of single-character labels that will appear in front of multiple-choice options. For instance, a multiple-choice question with three options would display (A) first option (B) second option (C) third option. There must be spaces between each of the different characters. The characters will show up next to options in the order that they are listed here. Most multiple choice questions have 5 or fewer options.",
|
|
182
|
+
message: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
|
|
183
|
+
},
|
|
184
|
+
rightArrow: "Reaction arrow pointing to the right.",
|
|
185
|
+
dontUnderstandUnits: "I couldn't understand those units.",
|
|
186
|
+
checkSigFigs: "Check your significant figures.",
|
|
187
|
+
answerNumericallyIncorrect: "That answer is numerically incorrect.",
|
|
188
|
+
checkUnits: "Check your units.",
|
|
189
|
+
dontUnderstand: "I don't understand that",
|
|
190
|
+
loading: "Loading...",
|
|
191
|
+
videoTranscript: "See video transcript",
|
|
192
|
+
somethingWrong: "Something went wrong.",
|
|
193
|
+
videoWrapper: "Khan Academy video wrapper",
|
|
194
|
+
mathInputTitle: "mathematics keyboard",
|
|
195
|
+
mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
|
|
196
|
+
sin: "sin",
|
|
197
|
+
cos: "cos",
|
|
198
|
+
tan: "tan",
|
|
199
|
+
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
200
|
+
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
201
|
+
selectAnAnswer: "Select an answer",
|
|
202
|
+
// The following strings are used for interactive graph SR descriptions.
|
|
203
|
+
addPoint: "Add Point",
|
|
204
|
+
removePoint: "Remove Point",
|
|
205
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
206
|
+
closePolygon: "Close shape",
|
|
207
|
+
openPolygon: "Re-open shape",
|
|
208
|
+
srInteractiveElements: "Interactive elements: %(elements)s",
|
|
209
|
+
srNoInteractiveElements: "No interactive elements",
|
|
210
|
+
// TODO(LEMS-2660): The following strings are ones that will need
|
|
211
|
+
// translation tickets after all interactive graph SR strings have
|
|
212
|
+
// been finalized. Remove this comment after the tickets have been
|
|
213
|
+
// created.
|
|
214
|
+
srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
|
|
215
|
+
srCircleGraph: "A circle on a coordinate plane.",
|
|
216
|
+
srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
|
|
217
|
+
srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
|
|
218
|
+
srCircleRadius: "Circle radius is %(radius)s.",
|
|
219
|
+
srCircleOuterPoints: "Points on the circle at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, %(point3X)s comma %(point3Y)s, %(point4X)s comma %(point4Y)s.",
|
|
220
|
+
srLinearGraph: "A line on a coordinate plane.",
|
|
221
|
+
srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
222
|
+
srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
|
|
223
|
+
srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
|
|
224
|
+
srLinearGraphSlopeHorizontal: "Its slope is zero.",
|
|
225
|
+
srLinearGraphSlopeVertical: "Its slope is undefined.",
|
|
226
|
+
srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
|
|
227
|
+
srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
|
|
228
|
+
srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
|
|
229
|
+
srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
|
|
230
|
+
srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
231
|
+
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
232
|
+
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
|
+
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
|
+
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s"
|
|
235
|
+
// The above strings are used for interactive graph SR descriptions.
|
|
236
|
+
};
|
|
237
|
+
|
|
68
238
|
/**
|
|
69
239
|
* Mock strings for the Perseus package, to be used for tests and Storybook.
|
|
70
240
|
*/
|
|
@@ -334,6 +504,16 @@ const mockStrings = {
|
|
|
334
504
|
// The above strings are used for interactive graph SR descriptions.
|
|
335
505
|
};
|
|
336
506
|
|
|
507
|
+
({
|
|
508
|
+
[ErrorCodes.MISSING_PERCENT_ERROR]: strings.MISSING_PERCENT_ERROR,
|
|
509
|
+
[ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR]: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
510
|
+
[ErrorCodes.APPROXIMATED_PI_ERROR]: strings.APPROXIMATED_PI_ERROR,
|
|
511
|
+
[ErrorCodes.EXTRA_SYMBOLS_ERROR]: strings.EXTRA_SYMBOLS_ERROR,
|
|
512
|
+
[ErrorCodes.WRONG_CASE_ERROR]: strings.WRONG_CASE_ERROR,
|
|
513
|
+
[ErrorCodes.WRONG_LETTER_ERROR]: strings.WRONG_LETTER_ERROR,
|
|
514
|
+
[ErrorCodes.MULTIPLICATION_SIGN_ERROR]: strings.MULTIPLICATION_SIGN_ERROR
|
|
515
|
+
});
|
|
516
|
+
|
|
337
517
|
/**
|
|
338
518
|
* PerseusI18nContext provides a way to set the strings and locale that are
|
|
339
519
|
* used inside the Perseus package.
|
|
@@ -1312,917 +1492,6 @@ const Log = {
|
|
|
1312
1492
|
}
|
|
1313
1493
|
};
|
|
1314
1494
|
|
|
1315
|
-
const KhanMath = {
|
|
1316
|
-
// Simplify formulas before display
|
|
1317
|
-
cleanMath: function (expr) {
|
|
1318
|
-
return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
|
|
1319
|
-
},
|
|
1320
|
-
// Bound a number by 1e-6 and 1e20 to avoid exponents after toString
|
|
1321
|
-
bound: function (num) {
|
|
1322
|
-
if (num === 0) {
|
|
1323
|
-
return num;
|
|
1324
|
-
}
|
|
1325
|
-
if (num < 0) {
|
|
1326
|
-
return -KhanMath.bound(-num);
|
|
1327
|
-
}
|
|
1328
|
-
return Math.max(1e-6, Math.min(num, 1e20));
|
|
1329
|
-
},
|
|
1330
|
-
factorial: function (x) {
|
|
1331
|
-
if (x <= 1) {
|
|
1332
|
-
return x;
|
|
1333
|
-
}
|
|
1334
|
-
return x * KhanMath.factorial(x - 1);
|
|
1335
|
-
},
|
|
1336
|
-
getGCD: function (a, b) {
|
|
1337
|
-
if (arguments.length > 2) {
|
|
1338
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1339
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1340
|
-
const rest = [].slice.call(arguments, 1);
|
|
1341
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1342
|
-
return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
|
|
1343
|
-
}
|
|
1344
|
-
let mod;
|
|
1345
|
-
a = Math.abs(a);
|
|
1346
|
-
b = Math.abs(b);
|
|
1347
|
-
while (b) {
|
|
1348
|
-
mod = a % b;
|
|
1349
|
-
a = b;
|
|
1350
|
-
b = mod;
|
|
1351
|
-
}
|
|
1352
|
-
return a;
|
|
1353
|
-
},
|
|
1354
|
-
getLCM: function (a, b) {
|
|
1355
|
-
if (arguments.length > 2) {
|
|
1356
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1357
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1358
|
-
const rest = [].slice.call(arguments, 1);
|
|
1359
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1360
|
-
return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
|
|
1361
|
-
}
|
|
1362
|
-
return Math.abs(a * b) / KhanMath.getGCD(a, b);
|
|
1363
|
-
},
|
|
1364
|
-
primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
|
|
1365
|
-
isPrime: function (n) {
|
|
1366
|
-
if (n <= 1) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
if (n < 101) {
|
|
1370
|
-
return !!$.grep(KhanMath.primes, function (p, i) {
|
|
1371
|
-
return Math.abs(p - n) <= 0.5;
|
|
1372
|
-
}).length;
|
|
1373
|
-
}
|
|
1374
|
-
if (n <= 1 || n > 2 && n % 2 === 0) {
|
|
1375
|
-
return false;
|
|
1376
|
-
}
|
|
1377
|
-
for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
|
|
1378
|
-
if (n % i === 0) {
|
|
1379
|
-
return false;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
return true;
|
|
1383
|
-
},
|
|
1384
|
-
// @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
|
|
1385
|
-
getPrimeFactorization: function (number) {
|
|
1386
|
-
if (number === 1) {
|
|
1387
|
-
return [];
|
|
1388
|
-
}
|
|
1389
|
-
if (KhanMath.isPrime(number)) {
|
|
1390
|
-
return [number];
|
|
1391
|
-
}
|
|
1392
|
-
const maxf = Math.sqrt(number);
|
|
1393
|
-
for (let f = 2; f <= maxf; f++) {
|
|
1394
|
-
if (number % f === 0) {
|
|
1395
|
-
return $.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
},
|
|
1399
|
-
// Round a number to the nearest increment
|
|
1400
|
-
// E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
|
|
1401
|
-
// num = 45, return 60.
|
|
1402
|
-
roundToNearest: function (increment, num) {
|
|
1403
|
-
return Math.round(num / increment) * increment;
|
|
1404
|
-
},
|
|
1405
|
-
// Round a number to a certain number of decimal places
|
|
1406
|
-
roundTo: function (precision, num) {
|
|
1407
|
-
const factor = Math.pow(10, precision).toFixed(5);
|
|
1408
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
|
|
1409
|
-
return Math.round((num * factor).toFixed(5)) / factor;
|
|
1410
|
-
},
|
|
1411
|
-
/**
|
|
1412
|
-
* Return a string of num rounded to a fixed precision decimal places,
|
|
1413
|
-
* with an approx symbol if num had to be rounded, and trailing 0s
|
|
1414
|
-
*/
|
|
1415
|
-
toFixedApprox: function (num, precision) {
|
|
1416
|
-
// TODO(aria): Make this locale-dependent
|
|
1417
|
-
const fixedStr = num.toFixed(precision);
|
|
1418
|
-
if (number$1.equal(+fixedStr, num)) {
|
|
1419
|
-
return fixedStr;
|
|
1420
|
-
}
|
|
1421
|
-
return "\\approx " + fixedStr;
|
|
1422
|
-
},
|
|
1423
|
-
/**
|
|
1424
|
-
* Return a string of num rounded to precision decimal places, with an
|
|
1425
|
-
* approx symbol if num had to be rounded, but no trailing 0s if it was
|
|
1426
|
-
* not rounded.
|
|
1427
|
-
*/
|
|
1428
|
-
roundToApprox: function (num, precision) {
|
|
1429
|
-
const fixed = KhanMath.roundTo(precision, num);
|
|
1430
|
-
if (number$1.equal(fixed, num)) {
|
|
1431
|
-
return String(fixed);
|
|
1432
|
-
}
|
|
1433
|
-
return KhanMath.toFixedApprox(num, precision);
|
|
1434
|
-
},
|
|
1435
|
-
// toFraction(4/8) => [1, 2]
|
|
1436
|
-
// toFraction(0.666) => [333, 500]
|
|
1437
|
-
// toFraction(0.666, 0.001) => [2, 3]
|
|
1438
|
-
//
|
|
1439
|
-
// tolerance can't be bigger than 1, sorry
|
|
1440
|
-
toFraction: function (decimal, tolerance) {
|
|
1441
|
-
if (tolerance == null) {
|
|
1442
|
-
tolerance = Math.pow(2, -46);
|
|
1443
|
-
}
|
|
1444
|
-
if (decimal < 0 || decimal > 1) {
|
|
1445
|
-
let fract = decimal % 1;
|
|
1446
|
-
fract += fract < 0 ? 1 : 0;
|
|
1447
|
-
const nd = KhanMath.toFraction(fract, tolerance);
|
|
1448
|
-
nd[0] += Math.round(decimal - fract) * nd[1];
|
|
1449
|
-
return nd;
|
|
1450
|
-
}
|
|
1451
|
-
if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
|
|
1452
|
-
return [Math.round(decimal), 1];
|
|
1453
|
-
}
|
|
1454
|
-
let loN = 0;
|
|
1455
|
-
let loD = 1;
|
|
1456
|
-
let hiN = 1;
|
|
1457
|
-
let hiD = 1;
|
|
1458
|
-
let midN = 1;
|
|
1459
|
-
let midD = 2;
|
|
1460
|
-
|
|
1461
|
-
// eslint-disable-next-line no-constant-condition
|
|
1462
|
-
while (true) {
|
|
1463
|
-
if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
|
|
1464
|
-
return [midN, midD];
|
|
1465
|
-
}
|
|
1466
|
-
if (midN / midD < decimal) {
|
|
1467
|
-
loN = midN;
|
|
1468
|
-
loD = midD;
|
|
1469
|
-
} else {
|
|
1470
|
-
hiN = midN;
|
|
1471
|
-
hiD = midD;
|
|
1472
|
-
}
|
|
1473
|
-
midN = loN + hiN;
|
|
1474
|
-
midD = loD + hiD;
|
|
1475
|
-
}
|
|
1476
|
-
},
|
|
1477
|
-
// Returns the format (string) of a given numeric string
|
|
1478
|
-
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
1479
|
-
// That is, it is not necessarily true that interpreted input are numeric
|
|
1480
|
-
getNumericFormat: function (text) {
|
|
1481
|
-
text = $.trim(text);
|
|
1482
|
-
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
1483
|
-
if (text.match(/^[+-]?\d+$/)) {
|
|
1484
|
-
return "integer";
|
|
1485
|
-
}
|
|
1486
|
-
if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
|
|
1487
|
-
return "mixed";
|
|
1488
|
-
}
|
|
1489
|
-
const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
|
|
1490
|
-
if (fraction) {
|
|
1491
|
-
return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
|
|
1492
|
-
}
|
|
1493
|
-
if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
|
|
1494
|
-
return "decimal";
|
|
1495
|
-
}
|
|
1496
|
-
if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
|
|
1497
|
-
return "pi";
|
|
1498
|
-
}
|
|
1499
|
-
return null;
|
|
1500
|
-
},
|
|
1501
|
-
// Returns a string of the number in a specified format
|
|
1502
|
-
toNumericString: function (number, format) {
|
|
1503
|
-
if (number == null) {
|
|
1504
|
-
return "";
|
|
1505
|
-
}
|
|
1506
|
-
if (number === 0) {
|
|
1507
|
-
return "0"; // otherwise it might end up as 0% or 0pi
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
if (format === "percent") {
|
|
1511
|
-
return number * 100 + "%";
|
|
1512
|
-
}
|
|
1513
|
-
if (format === "pi") {
|
|
1514
|
-
const fraction = number$1.toFraction(number / Math.PI);
|
|
1515
|
-
const numerator = Math.abs(fraction[0]);
|
|
1516
|
-
const denominator = fraction[1];
|
|
1517
|
-
if (number$1.isInteger(numerator)) {
|
|
1518
|
-
const sign = number < 0 ? "-" : "";
|
|
1519
|
-
const pi = "\u03C0";
|
|
1520
|
-
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
1524
|
-
const fraction = number$1.toFraction(number);
|
|
1525
|
-
const numerator = Math.abs(fraction[0]);
|
|
1526
|
-
const denominator = fraction[1];
|
|
1527
|
-
const sign = number < 0 ? "-" : "";
|
|
1528
|
-
if (denominator === 1) {
|
|
1529
|
-
return sign + numerator; // for integers, irrational, d > 1000
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
if (format === "mixed") {
|
|
1533
|
-
const modulus = numerator % denominator;
|
|
1534
|
-
const integer = (numerator - modulus) / denominator;
|
|
1535
|
-
return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
|
|
1536
|
-
} // otherwise proper, improper, or fraction
|
|
1537
|
-
return sign + numerator + "/" + denominator;
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
// otherwise (decimal, float, long long)
|
|
1541
|
-
return String(number);
|
|
1542
|
-
}
|
|
1543
|
-
};
|
|
1544
|
-
function sum$1(array) {
|
|
1545
|
-
return array.reduce(add$3, 0);
|
|
1546
|
-
}
|
|
1547
|
-
function add$3(a, b) {
|
|
1548
|
-
return a + b;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
/* eslint-disable no-useless-escape */
|
|
1552
|
-
const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
1553
|
-
|
|
1554
|
-
// TOOD(kevinb): Figure out how this relates to KEScore in
|
|
1555
|
-
// perseus-all-package/types.js and see if there's a way to
|
|
1556
|
-
// unify these types.
|
|
1557
|
-
/**
|
|
1558
|
-
* Answer types
|
|
1559
|
-
*
|
|
1560
|
-
* Utility for creating answerable questions displayed in exercises
|
|
1561
|
-
*
|
|
1562
|
-
* Different answer types produce different kinds of input displays, and do
|
|
1563
|
-
* different kinds of checking on the solutions.
|
|
1564
|
-
*
|
|
1565
|
-
* Each of the objects contain two functions, setup and createValidator.
|
|
1566
|
-
*
|
|
1567
|
-
* The setup function takes a solutionarea and solution, and performs setup
|
|
1568
|
-
* within the solutionarea, and then returns an object which contains:
|
|
1569
|
-
*
|
|
1570
|
-
* answer: a function which, when called, will retrieve the current answer from
|
|
1571
|
-
* the solutionarea, which can then be validated using the validator
|
|
1572
|
-
* function
|
|
1573
|
-
* validator: a function returned from the createValidator function (defined
|
|
1574
|
-
* below)
|
|
1575
|
-
* solution: the correct answer to the problem
|
|
1576
|
-
* showGuess: a function which, when given a guess, shows the guess within the
|
|
1577
|
-
* provided solutionarea
|
|
1578
|
-
* showGuessCustom: a function which displays parts of a guess that are not
|
|
1579
|
-
* within the solutionarea; currently only used for custom
|
|
1580
|
-
* answers
|
|
1581
|
-
*
|
|
1582
|
-
* The createValidator function only takes a solution, and it returns a
|
|
1583
|
-
* function which can be used to validate an answer.
|
|
1584
|
-
*
|
|
1585
|
-
* The resulting validator function returns:
|
|
1586
|
-
* - true: if the answer is fully correct
|
|
1587
|
-
* - false: if the answer is incorrect
|
|
1588
|
-
* - "" (the empty string): if no answer has been provided (e.g. the answer box
|
|
1589
|
-
* is left unfilled)
|
|
1590
|
-
* - a string: if there is some slight error
|
|
1591
|
-
*
|
|
1592
|
-
* In most cases, setup and createValidator don't really need the solution DOM
|
|
1593
|
-
* element so we have setupFunctional and createValidatorFunctional for them
|
|
1594
|
-
* which take only $solution.text() and $solution.data(). This makes it easier
|
|
1595
|
-
* to reuse specific answer types.
|
|
1596
|
-
*
|
|
1597
|
-
* TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
|
|
1598
|
-
*
|
|
1599
|
-
*/
|
|
1600
|
-
const KhanAnswerTypes = {
|
|
1601
|
-
/*
|
|
1602
|
-
* predicate answer type
|
|
1603
|
-
*
|
|
1604
|
-
* performs simple predicate-based checking of a numeric solution, with
|
|
1605
|
-
* different kinds of number formats
|
|
1606
|
-
*
|
|
1607
|
-
* Uses the data-forms option on the solution to choose which number formats
|
|
1608
|
-
* are acceptable. Available data-forms:
|
|
1609
|
-
*
|
|
1610
|
-
* - integer: 3
|
|
1611
|
-
* - proper: 3/5
|
|
1612
|
-
* - improper: 5/3
|
|
1613
|
-
* - pi: 3 pi
|
|
1614
|
-
* - log: log(5)
|
|
1615
|
-
* - percent: 15%
|
|
1616
|
-
* - mixed: 1 1/3
|
|
1617
|
-
* - decimal: 1.7
|
|
1618
|
-
*
|
|
1619
|
-
* The solution should be a predicate of the form:
|
|
1620
|
-
*
|
|
1621
|
-
* function(guess, maxError) {
|
|
1622
|
-
* return abs(guess - 3) < maxError;
|
|
1623
|
-
* }
|
|
1624
|
-
*
|
|
1625
|
-
*/
|
|
1626
|
-
predicate: {
|
|
1627
|
-
defaultForms: "integer, proper, improper, mixed, decimal",
|
|
1628
|
-
createValidatorFunctional: function (predicate, options, strings) {
|
|
1629
|
-
// Extract the options from the given solution object
|
|
1630
|
-
options = _.extend({
|
|
1631
|
-
simplify: "required",
|
|
1632
|
-
ratio: false,
|
|
1633
|
-
forms: KhanAnswerTypes.predicate.defaultForms
|
|
1634
|
-
}, options);
|
|
1635
|
-
let acceptableForms;
|
|
1636
|
-
// this is maintaining backwards compatibility
|
|
1637
|
-
// TODO(merlob) fix all places that depend on this, then delete
|
|
1638
|
-
if (!_.isArray(options.forms)) {
|
|
1639
|
-
acceptableForms = options.forms.split(/\s*,\s*/);
|
|
1640
|
-
} else {
|
|
1641
|
-
acceptableForms = options.forms;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// TODO(jack): remove options.inexact in favor of options.maxError
|
|
1645
|
-
if (options.inexact === undefined) {
|
|
1646
|
-
// If we aren't allowing inexact, ensure that we don't have a
|
|
1647
|
-
// large maxError as well.
|
|
1648
|
-
options.maxError = 0;
|
|
1649
|
-
}
|
|
1650
|
-
// Allow a small tolerance on maxError, to avoid numerical
|
|
1651
|
-
// representation issues (2.3 should be correct for a solution of
|
|
1652
|
-
// 2.45 with maxError=0.15).
|
|
1653
|
-
options.maxError = +options.maxError + MAXERROR_EPSILON;
|
|
1654
|
-
|
|
1655
|
-
// If percent is an acceptable form, make sure it's the last one
|
|
1656
|
-
// in the list so we don't prematurely complain about not having
|
|
1657
|
-
// a percent sign when the user entered the correct answer in a
|
|
1658
|
-
// different form (such as a decimal or fraction)
|
|
1659
|
-
if (_.contains(acceptableForms, "percent")) {
|
|
1660
|
-
acceptableForms = _.without(acceptableForms, "percent");
|
|
1661
|
-
acceptableForms.push("percent");
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
// Take text looking like a fraction, and turn it into a number
|
|
1665
|
-
const fractionTransformer = function fractionTransformer(text) {
|
|
1666
|
-
text = text
|
|
1667
|
-
// Replace unicode minus sign with hyphen
|
|
1668
|
-
.replace(/\u2212/, "-")
|
|
1669
|
-
// Remove space after +, -
|
|
1670
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1671
|
-
// Remove leading/trailing whitespace
|
|
1672
|
-
.replace(/(^\s*)|(\s*$)/gi, "");
|
|
1673
|
-
|
|
1674
|
-
// Extract numerator and denominator
|
|
1675
|
-
const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
|
|
1676
|
-
// Fractions are represented as "-\frac{numerator}{denominator}"
|
|
1677
|
-
// in Mobile device input instead of "numerator/denominator" as
|
|
1678
|
-
// in web-browser.
|
|
1679
|
-
const mobileDeviceMatch = text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);
|
|
1680
|
-
const parsedInt = parseInt(text, 10);
|
|
1681
|
-
if (match || mobileDeviceMatch) {
|
|
1682
|
-
let num;
|
|
1683
|
-
let denom;
|
|
1684
|
-
let simplified = true;
|
|
1685
|
-
if (match) {
|
|
1686
|
-
num = parseFloat(match[1]);
|
|
1687
|
-
denom = parseFloat(match[2]);
|
|
1688
|
-
} else {
|
|
1689
|
-
num = parseFloat(mobileDeviceMatch[2]);
|
|
1690
|
-
if (mobileDeviceMatch[1] === "-") {
|
|
1691
|
-
if (num < 0) {
|
|
1692
|
-
simplified = false;
|
|
1693
|
-
}
|
|
1694
|
-
num = -num;
|
|
1695
|
-
}
|
|
1696
|
-
denom = parseFloat(mobileDeviceMatch[3]);
|
|
1697
|
-
}
|
|
1698
|
-
simplified = simplified && denom > 0 && (options.ratio || denom !== 1) && KhanMath.getGCD(num, denom) === 1;
|
|
1699
|
-
return [{
|
|
1700
|
-
value: num / denom,
|
|
1701
|
-
exact: simplified
|
|
1702
|
-
}];
|
|
1703
|
-
}
|
|
1704
|
-
if (!isNaN(parsedInt) && "" + parsedInt === text) {
|
|
1705
|
-
return [{
|
|
1706
|
-
value: parsedInt,
|
|
1707
|
-
exact: true
|
|
1708
|
-
}];
|
|
1709
|
-
}
|
|
1710
|
-
return [];
|
|
1711
|
-
};
|
|
1712
|
-
|
|
1713
|
-
/*
|
|
1714
|
-
* Different forms of numbers
|
|
1715
|
-
*
|
|
1716
|
-
* Each function returns a list of objects of the form:
|
|
1717
|
-
*
|
|
1718
|
-
* {
|
|
1719
|
-
* value: numerical value,
|
|
1720
|
-
* exact: true/false
|
|
1721
|
-
* }
|
|
1722
|
-
*/
|
|
1723
|
-
const forms = {
|
|
1724
|
-
// integer, which is encompassed by decimal
|
|
1725
|
-
integer: function (text) {
|
|
1726
|
-
// Compare the decimal form to the decimal form rounded to
|
|
1727
|
-
// an integer. Only accept if the user actually entered an
|
|
1728
|
-
// integer.
|
|
1729
|
-
const decimal = forms.decimal(text);
|
|
1730
|
-
const rounded = forms.decimal(text, 1);
|
|
1731
|
-
if (decimal[0].value != null && decimal[0].value === rounded[0].value || decimal[1].value != null && decimal[1].value === rounded[1].value) {
|
|
1732
|
-
return decimal;
|
|
1733
|
-
}
|
|
1734
|
-
return [];
|
|
1735
|
-
},
|
|
1736
|
-
// A proper fraction
|
|
1737
|
-
proper: function (text) {
|
|
1738
|
-
return $.map(fractionTransformer(text), function (o) {
|
|
1739
|
-
// All fractions that are less than 1
|
|
1740
|
-
if (Math.abs(o.value) < 1) {
|
|
1741
|
-
return [o];
|
|
1742
|
-
}
|
|
1743
|
-
return [];
|
|
1744
|
-
});
|
|
1745
|
-
},
|
|
1746
|
-
// an improper fraction
|
|
1747
|
-
improper: function (text) {
|
|
1748
|
-
return $.map(fractionTransformer(text), function (o) {
|
|
1749
|
-
// All fractions that are greater than 1
|
|
1750
|
-
if (Math.abs(o.value) >= 1) {
|
|
1751
|
-
return [o];
|
|
1752
|
-
}
|
|
1753
|
-
return [];
|
|
1754
|
-
});
|
|
1755
|
-
},
|
|
1756
|
-
// pi-like numbers
|
|
1757
|
-
pi: function (text) {
|
|
1758
|
-
let match;
|
|
1759
|
-
let possibilities = [];
|
|
1760
|
-
|
|
1761
|
-
// Replace unicode minus sign with hyphen
|
|
1762
|
-
text = text.replace(/\u2212/, "-");
|
|
1763
|
-
|
|
1764
|
-
// - pi
|
|
1765
|
-
// (Note: we also support \pi (for TeX), p, tau (and \tau,
|
|
1766
|
-
// and t), pau.)
|
|
1767
|
-
if (match = text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1768
|
-
possibilities = [{
|
|
1769
|
-
value: parseFloat(match[1] + "1"),
|
|
1770
|
-
exact: true
|
|
1771
|
-
}];
|
|
1772
|
-
|
|
1773
|
-
// 5 / 6 pi
|
|
1774
|
-
} else if (match = text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1775
|
-
possibilities = fractionTransformer(match[1]);
|
|
1776
|
-
|
|
1777
|
-
// 4 5 / 6 pi
|
|
1778
|
-
} else if (match = text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1779
|
-
const sign = parseFloat(match[1] + "1");
|
|
1780
|
-
const integ = parseFloat(match[2]);
|
|
1781
|
-
const num = parseFloat(match[3]);
|
|
1782
|
-
const denom = parseFloat(match[4]);
|
|
1783
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
1784
|
-
possibilities = [{
|
|
1785
|
-
value: sign * (integ + num / denom),
|
|
1786
|
-
exact: simplified
|
|
1787
|
-
}];
|
|
1788
|
-
|
|
1789
|
-
// 5 pi / 6
|
|
1790
|
-
} else if (match = text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)) {
|
|
1791
|
-
possibilities = fractionTransformer(match[1] + "/" + match[3]);
|
|
1792
|
-
|
|
1793
|
-
// - pi / 4
|
|
1794
|
-
} else if (match = text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)) {
|
|
1795
|
-
possibilities = fractionTransformer(match[1] + "1/" + match[3]);
|
|
1796
|
-
|
|
1797
|
-
// 0
|
|
1798
|
-
} else if (text === "0") {
|
|
1799
|
-
possibilities = [{
|
|
1800
|
-
value: 0,
|
|
1801
|
-
exact: true
|
|
1802
|
-
}];
|
|
1803
|
-
|
|
1804
|
-
// 0.5 pi (fallback)
|
|
1805
|
-
} else if (match = text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1806
|
-
possibilities = forms.decimal(match[1]);
|
|
1807
|
-
} else {
|
|
1808
|
-
possibilities = _.reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/), function (memo, form) {
|
|
1809
|
-
return memo.concat(forms[form](text));
|
|
1810
|
-
}, []);
|
|
1811
|
-
|
|
1812
|
-
// If the answer is a floating point number that's
|
|
1813
|
-
// near a multiple of pi, mark is as being possibly
|
|
1814
|
-
// an approximation of pi. We actually check if
|
|
1815
|
-
// it's a plausible approximation of pi/12, since
|
|
1816
|
-
// sometimes the correct answer is like pi/3 or pi/4.
|
|
1817
|
-
// We also say it's a pi-approximation if it involves
|
|
1818
|
-
// x/7 (since 22/7 is an approximation of pi.)
|
|
1819
|
-
// Never mark an integer as being an approximation
|
|
1820
|
-
// of pi.
|
|
1821
|
-
let approximatesPi = false;
|
|
1822
|
-
const number = parseFloat(text);
|
|
1823
|
-
if (!isNaN(number) && number !== parseInt(text)) {
|
|
1824
|
-
const piMult = Math.PI / 12;
|
|
1825
|
-
const roundedNumber = piMult * Math.round(number / piMult);
|
|
1826
|
-
if (Math.abs(number - roundedNumber) < 0.01) {
|
|
1827
|
-
approximatesPi = true;
|
|
1828
|
-
}
|
|
1829
|
-
} else if (text.match(/\/\s*7/)) {
|
|
1830
|
-
approximatesPi = true;
|
|
1831
|
-
}
|
|
1832
|
-
if (approximatesPi) {
|
|
1833
|
-
_.each(possibilities, function (possibility) {
|
|
1834
|
-
possibility.piApprox = true;
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
return possibilities;
|
|
1838
|
-
}
|
|
1839
|
-
let multiplier = Math.PI;
|
|
1840
|
-
if (text.match(/\\?tau|t|\u03c4/)) {
|
|
1841
|
-
multiplier = Math.PI * 2;
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
// We're taking an early stand along side xkcd in the
|
|
1845
|
-
// inevitable ti vs. pau debate... http://xkcd.com/1292
|
|
1846
|
-
if (text.match(/pau/)) {
|
|
1847
|
-
multiplier = Math.PI * 1.5;
|
|
1848
|
-
}
|
|
1849
|
-
$.each(possibilities, function (ix, possibility) {
|
|
1850
|
-
possibility.value *= multiplier;
|
|
1851
|
-
});
|
|
1852
|
-
return possibilities;
|
|
1853
|
-
},
|
|
1854
|
-
// Converts '' to 1 and '-' to -1 so you can write "[___] x"
|
|
1855
|
-
// and accept sane things
|
|
1856
|
-
coefficient: function (text) {
|
|
1857
|
-
let possibilities = [];
|
|
1858
|
-
|
|
1859
|
-
// Replace unicode minus sign with hyphen
|
|
1860
|
-
text = text.replace(/\u2212/, "-");
|
|
1861
|
-
if (text === "") {
|
|
1862
|
-
possibilities = [{
|
|
1863
|
-
value: 1,
|
|
1864
|
-
exact: true
|
|
1865
|
-
}];
|
|
1866
|
-
} else if (text === "-") {
|
|
1867
|
-
possibilities = [{
|
|
1868
|
-
value: -1,
|
|
1869
|
-
exact: true
|
|
1870
|
-
}];
|
|
1871
|
-
}
|
|
1872
|
-
return possibilities;
|
|
1873
|
-
},
|
|
1874
|
-
// simple log(c) form
|
|
1875
|
-
log: function (text) {
|
|
1876
|
-
let match;
|
|
1877
|
-
let possibilities = [];
|
|
1878
|
-
|
|
1879
|
-
// Replace unicode minus sign with hyphen
|
|
1880
|
-
text = text.replace(/\u2212/, "-");
|
|
1881
|
-
text = text.replace(/[ \(\)]/g, "");
|
|
1882
|
-
if (match = text.match(/^log\s*(\S+)\s*$/i)) {
|
|
1883
|
-
// @ts-expect-error - TS2322 - Type '{ value: number | undefined; exact: boolean; }[]' is not assignable to type 'never[]'.
|
|
1884
|
-
possibilities = forms.decimal(match[1]);
|
|
1885
|
-
} else if (text === "0") {
|
|
1886
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'. | TS2322 - Type 'boolean' is not assignable to type 'never'.
|
|
1887
|
-
possibilities = [{
|
|
1888
|
-
value: 0,
|
|
1889
|
-
exact: true
|
|
1890
|
-
}];
|
|
1891
|
-
}
|
|
1892
|
-
return possibilities;
|
|
1893
|
-
},
|
|
1894
|
-
// Numbers with percent signs
|
|
1895
|
-
percent: function (text) {
|
|
1896
|
-
text = $.trim(text);
|
|
1897
|
-
// store whether or not there is a percent sign
|
|
1898
|
-
let hasPercentSign = false;
|
|
1899
|
-
if (text.indexOf("%") === text.length - 1) {
|
|
1900
|
-
text = $.trim(text.substring(0, text.length - 1));
|
|
1901
|
-
hasPercentSign = true;
|
|
1902
|
-
}
|
|
1903
|
-
const transformed = forms.decimal(text);
|
|
1904
|
-
$.each(transformed, function (ix, t) {
|
|
1905
|
-
t.exact = hasPercentSign;
|
|
1906
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
1907
|
-
t.value = t.value / 100;
|
|
1908
|
-
});
|
|
1909
|
-
return transformed;
|
|
1910
|
-
},
|
|
1911
|
-
// Mixed numbers, like 1 3/4
|
|
1912
|
-
mixed: function (text) {
|
|
1913
|
-
const match = text
|
|
1914
|
-
// Replace unicode minus sign with hyphen
|
|
1915
|
-
.replace(/\u2212/, "-")
|
|
1916
|
-
// Remove space after +, -
|
|
1917
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1918
|
-
// Extract integer, numerator and denominator
|
|
1919
|
-
.match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
|
|
1920
|
-
if (match) {
|
|
1921
|
-
const sign = parseFloat(match[1] + "1");
|
|
1922
|
-
const integ = parseFloat(match[2]);
|
|
1923
|
-
const num = parseFloat(match[3]);
|
|
1924
|
-
const denom = parseFloat(match[4]);
|
|
1925
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
1926
|
-
return [{
|
|
1927
|
-
value: sign * (integ + num / denom),
|
|
1928
|
-
exact: simplified
|
|
1929
|
-
}];
|
|
1930
|
-
}
|
|
1931
|
-
return [];
|
|
1932
|
-
},
|
|
1933
|
-
// Decimal numbers -- compare entered text rounded to
|
|
1934
|
-
// 'precision' Reciprical of the precision against the correct
|
|
1935
|
-
// answer. We round to 1/1e10 by default, which is healthily
|
|
1936
|
-
// less than machine epsilon but should be more than any real
|
|
1937
|
-
// decimal answer would use. (The 'integer' answer type uses
|
|
1938
|
-
// precision == 1.)
|
|
1939
|
-
decimal: function (text, precision = 1e10) {
|
|
1940
|
-
const normal = function normal(text) {
|
|
1941
|
-
text = $.trim(text);
|
|
1942
|
-
const match = text
|
|
1943
|
-
// Replace unicode minus sign with hyphen
|
|
1944
|
-
.replace(/\u2212/, "-")
|
|
1945
|
-
// Remove space after +, -
|
|
1946
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1947
|
-
// Extract integer, numerator and denominator. If
|
|
1948
|
-
// commas or spaces are used, they must be in the
|
|
1949
|
-
// "correct" places
|
|
1950
|
-
.match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);
|
|
1951
|
-
|
|
1952
|
-
// You can't start a number with `0,`, to prevent us
|
|
1953
|
-
// interpeting '0.342' as correct for '342'
|
|
1954
|
-
const badLeadingZero = text.match(/^0[0,]*,/);
|
|
1955
|
-
if (match && !badLeadingZero) {
|
|
1956
|
-
let x = parseFloat(match[1].replace(/[, ]/g, ""));
|
|
1957
|
-
if (options.inexact === undefined) {
|
|
1958
|
-
x = Math.round(x * precision) / precision;
|
|
1959
|
-
}
|
|
1960
|
-
return x;
|
|
1961
|
-
}
|
|
1962
|
-
};
|
|
1963
|
-
const commas = function commas(text) {
|
|
1964
|
-
text = text.replace(/([\.,])/g, function (_, c) {
|
|
1965
|
-
return c === "." ? "," : ".";
|
|
1966
|
-
});
|
|
1967
|
-
return normal(text);
|
|
1968
|
-
};
|
|
1969
|
-
return [{
|
|
1970
|
-
value: normal(text),
|
|
1971
|
-
exact: true
|
|
1972
|
-
}, {
|
|
1973
|
-
value: commas(text),
|
|
1974
|
-
exact: true
|
|
1975
|
-
}];
|
|
1976
|
-
}
|
|
1977
|
-
};
|
|
1978
|
-
|
|
1979
|
-
// validator function
|
|
1980
|
-
return function (guess) {
|
|
1981
|
-
// The fallback variable is used in place of the answer, if no
|
|
1982
|
-
// answer is provided (i.e. the field is left blank)
|
|
1983
|
-
const fallback = options.fallback != null ? "" + options.fallback : "";
|
|
1984
|
-
guess = $.trim(guess) || fallback;
|
|
1985
|
-
const score = {
|
|
1986
|
-
empty: guess === "",
|
|
1987
|
-
correct: false,
|
|
1988
|
-
message: null,
|
|
1989
|
-
guess: guess
|
|
1990
|
-
};
|
|
1991
|
-
|
|
1992
|
-
// iterate over all the acceptable forms, and if one of the
|
|
1993
|
-
// answers is correct, return true
|
|
1994
|
-
$.each(acceptableForms, function (i, form) {
|
|
1995
|
-
const transformed = forms[form](guess);
|
|
1996
|
-
for (let j = 0, l = transformed.length; j < l; j++) {
|
|
1997
|
-
const val = transformed[j].value;
|
|
1998
|
-
const exact = transformed[j].exact;
|
|
1999
|
-
const piApprox = transformed[j].piApprox;
|
|
2000
|
-
// If a string was returned, and it exactly matches,
|
|
2001
|
-
// return true
|
|
2002
|
-
if (predicate(val, options.maxError)) {
|
|
2003
|
-
// If the exact correct number was returned,
|
|
2004
|
-
// return true
|
|
2005
|
-
if (exact || options.simplify === "optional") {
|
|
2006
|
-
score.correct = true;
|
|
2007
|
-
score.message = options.message || null;
|
|
2008
|
-
// If the answer is correct, don't say it's
|
|
2009
|
-
// empty. This happens, for example, with the
|
|
2010
|
-
// coefficient type where guess === "" but is
|
|
2011
|
-
// interpreted as "1" which is correct.
|
|
2012
|
-
score.empty = false;
|
|
2013
|
-
} else if (form === "percent") {
|
|
2014
|
-
// Otherwise, an error was returned
|
|
2015
|
-
score.empty = true;
|
|
2016
|
-
score.message = strings.MISSING_PERCENT_ERROR;
|
|
2017
|
-
} else {
|
|
2018
|
-
if (options.simplify !== "enforced") {
|
|
2019
|
-
score.empty = true;
|
|
2020
|
-
}
|
|
2021
|
-
score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
|
|
2022
|
-
}
|
|
2023
|
-
// The return false below stops the looping of the
|
|
2024
|
-
// callback since predicate check succeeded.
|
|
2025
|
-
// No more forms to look to verify the user guess.
|
|
2026
|
-
return false;
|
|
2027
|
-
}
|
|
2028
|
-
if (piApprox && predicate(val, Math.abs(val * 0.001))) {
|
|
2029
|
-
score.empty = true;
|
|
2030
|
-
score.message = strings.APPROXIMATED_PI_ERROR;
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
});
|
|
2034
|
-
if (score.correct === false) {
|
|
2035
|
-
let interpretedGuess = false;
|
|
2036
|
-
_.each(forms, function (form) {
|
|
2037
|
-
const anyAreNaN = _.any(form(guess), function (t) {
|
|
2038
|
-
return t.value != null && !_.isNaN(t.value);
|
|
2039
|
-
});
|
|
2040
|
-
if (anyAreNaN) {
|
|
2041
|
-
interpretedGuess = true;
|
|
2042
|
-
}
|
|
2043
|
-
});
|
|
2044
|
-
if (!interpretedGuess) {
|
|
2045
|
-
score.empty = true;
|
|
2046
|
-
score.message = strings.EXTRA_SYMBOLS_ERROR;
|
|
2047
|
-
return score;
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
return score;
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
},
|
|
2054
|
-
/*
|
|
2055
|
-
* number answer type
|
|
2056
|
-
*
|
|
2057
|
-
* wraps the predicate answer type to performs simple number-based checking
|
|
2058
|
-
* of a solution
|
|
2059
|
-
*/
|
|
2060
|
-
number: {
|
|
2061
|
-
convertToPredicate: function (correctAnswer, options) {
|
|
2062
|
-
const correctFloat = parseFloat(correctAnswer);
|
|
2063
|
-
return [function (guess, maxError) {
|
|
2064
|
-
return Math.abs(guess - correctFloat) < maxError;
|
|
2065
|
-
}, $.extend({}, options, {
|
|
2066
|
-
type: "predicate"
|
|
2067
|
-
})];
|
|
2068
|
-
},
|
|
2069
|
-
createValidatorFunctional: function (correctAnswer, options, strings) {
|
|
2070
|
-
return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
2071
|
-
}
|
|
2072
|
-
},
|
|
2073
|
-
/*
|
|
2074
|
-
* The expression answer type parses a given expression or equation
|
|
2075
|
-
* and semantically compares it to the solution. In addition, instant
|
|
2076
|
-
* feedback is provided by rendering the last answer that fully parsed.
|
|
2077
|
-
*
|
|
2078
|
-
* Parsing options:
|
|
2079
|
-
* functions (e.g. data-functions="f g h")
|
|
2080
|
-
* A space or comma separated list of single-letter variables that
|
|
2081
|
-
* should be interpreted as functions. Case sensitive. "e" and "i"
|
|
2082
|
-
* are reserved.
|
|
2083
|
-
*
|
|
2084
|
-
* no functions specified: f(x+y) == fx + fy
|
|
2085
|
-
* with "f" as a function: f(x+y) != fx + fy
|
|
2086
|
-
*
|
|
2087
|
-
* Comparison options:
|
|
2088
|
-
* same-form (e.g. data-same-form)
|
|
2089
|
-
* If present, the answer must match the solution's structure in
|
|
2090
|
-
* addition to evaluating the same. Commutativity and excess negation
|
|
2091
|
-
* are ignored, but all other changes will trigger a rejection. Useful
|
|
2092
|
-
* for requiring a particular form of an equation, or if the answer
|
|
2093
|
-
* must be factored.
|
|
2094
|
-
*
|
|
2095
|
-
* example question: Factor x^2 + x - 2
|
|
2096
|
-
* example solution: (x-1)(x+2)
|
|
2097
|
-
* accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
|
|
2098
|
-
* rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
|
|
2099
|
-
* rejection message: Your answer is not in the correct form
|
|
2100
|
-
*
|
|
2101
|
-
* simplify (e.g. data-simplify)
|
|
2102
|
-
* If present, the answer must be fully expanded and simplified. Use
|
|
2103
|
-
* carefully - simplification is hard and there may be bugs, or you
|
|
2104
|
-
* might not agree on the definition of "simplified" used. You will
|
|
2105
|
-
* get an error if the provided solution is not itself fully expanded
|
|
2106
|
-
* and simplified.
|
|
2107
|
-
*
|
|
2108
|
-
* example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
|
|
2109
|
-
* example solution: x^31 / n
|
|
2110
|
-
* accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
|
|
2111
|
-
* rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
|
|
2112
|
-
* rejection message: Your answer is not fully expanded and simplified
|
|
2113
|
-
*
|
|
2114
|
-
* Rendering options:
|
|
2115
|
-
* times (e.g. data-times)
|
|
2116
|
-
* If present, explicit multiplication (such as between numbers) will
|
|
2117
|
-
* be rendered with a cross/x symbol (TeX: \times) instead of the usual
|
|
2118
|
-
* center dot (TeX: \cdot).
|
|
2119
|
-
*
|
|
2120
|
-
* normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
|
|
2121
|
-
* but with "times": 2 * 3^x -> 2 \times 3^{x}
|
|
2122
|
-
*/
|
|
2123
|
-
expression: {
|
|
2124
|
-
parseSolution: function (solutionString, options) {
|
|
2125
|
-
let solution = KAS.parse(solutionString, options);
|
|
2126
|
-
if (!solution.parsed) {
|
|
2127
|
-
throw new PerseusError("The provided solution (" + solutionString + ") didn't parse.", Errors.InvalidInput);
|
|
2128
|
-
} else if (options.simplified && !solution.expr.isSimplified()) {
|
|
2129
|
-
throw new PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", Errors.InvalidInput);
|
|
2130
|
-
} else {
|
|
2131
|
-
solution = solution.expr;
|
|
2132
|
-
}
|
|
2133
|
-
return solution;
|
|
2134
|
-
},
|
|
2135
|
-
createValidatorFunctional: function (solution, options, strings) {
|
|
2136
|
-
return function (guess) {
|
|
2137
|
-
const score = {
|
|
2138
|
-
empty: false,
|
|
2139
|
-
correct: false,
|
|
2140
|
-
message: null,
|
|
2141
|
-
guess: guess,
|
|
2142
|
-
// Setting `ungraded` to true indicates that if the
|
|
2143
|
-
// guess doesn't match any of the solutions, the guess
|
|
2144
|
-
// shouldn't be marked as incorrect; instead, `message`
|
|
2145
|
-
// should be shown to the user. This is different from
|
|
2146
|
-
// setting `empty` to true, since the behavior of `empty`
|
|
2147
|
-
// is that `message` only will be shown if the guess is
|
|
2148
|
-
// graded as empty for every solution.
|
|
2149
|
-
ungraded: false
|
|
2150
|
-
};
|
|
2151
|
-
|
|
2152
|
-
// Don't bother parsing an empty input
|
|
2153
|
-
if (!guess) {
|
|
2154
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2155
|
-
score.empty = true;
|
|
2156
|
-
return score;
|
|
2157
|
-
}
|
|
2158
|
-
const answer = KAS.parse(guess, options);
|
|
2159
|
-
|
|
2160
|
-
// An unsuccessful parse doesn't count as wrong
|
|
2161
|
-
if (!answer.parsed) {
|
|
2162
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2163
|
-
score.empty = true;
|
|
2164
|
-
return score;
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
// Solution will need to be parsed again if we're creating
|
|
2168
|
-
// this from a multiple question type
|
|
2169
|
-
if (typeof solution === "string") {
|
|
2170
|
-
solution = KhanAnswerTypes.expression.parseSolution(solution, options);
|
|
2171
|
-
}
|
|
2172
|
-
const result = KAS.compare(answer.expr, solution, options);
|
|
2173
|
-
if (result.equal) {
|
|
2174
|
-
// Correct answer
|
|
2175
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'correct' because it is a read-only property.
|
|
2176
|
-
score.correct = true;
|
|
2177
|
-
} else if (result.wrongVariableNames || result.wrongVariableCase) {
|
|
2178
|
-
// We don't want to give people an error for getting the
|
|
2179
|
-
// variable names or the variable case wrong.
|
|
2180
|
-
// TODO(aasmund): This should ideally have been handled
|
|
2181
|
-
// under the `result.message` condition, but the
|
|
2182
|
-
// KAS messages currently aren't translatable.
|
|
2183
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2184
|
-
score.ungraded = true;
|
|
2185
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2186
|
-
score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
|
|
2187
|
-
// Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
|
|
2188
|
-
// @ts-expect-error - TS2339 - Property 'suppressAlmostThere' does not exist on type '{ readonly empty: false; readonly correct: false; readonly message: string | null | undefined; readonly guess: any; readonly ungraded: false; }'.
|
|
2189
|
-
score.suppressAlmostThere = true;
|
|
2190
|
-
} else if (result.message) {
|
|
2191
|
-
// Nearly correct answer
|
|
2192
|
-
// TODO(aasmund): This message also isn't translatable;
|
|
2193
|
-
// need to fix that in KAS
|
|
2194
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2195
|
-
score.message = result.message;
|
|
2196
|
-
} else {
|
|
2197
|
-
// Replace x with * and see if it would have been correct
|
|
2198
|
-
// TODO(aasmund): I think this branch is effectively dead,
|
|
2199
|
-
// because the replacement will only work in situations
|
|
2200
|
-
// where the variables are wrong (except if the variable
|
|
2201
|
-
// is x, in which case the replacement won't work either),
|
|
2202
|
-
// which is handled by another branch. When we implement a
|
|
2203
|
-
// more sophisticated variable check, revive this or
|
|
2204
|
-
// remove it completely if it will never come into play.
|
|
2205
|
-
const answerX = KAS.parse(guess.replace(/[xX]/g, "*"), options);
|
|
2206
|
-
if (answerX.parsed) {
|
|
2207
|
-
const resultX = KAS.compare(answerX.expr, solution, options);
|
|
2208
|
-
if (resultX.equal) {
|
|
2209
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2210
|
-
score.ungraded = true;
|
|
2211
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2212
|
-
score.message = strings.MULTIPLICATION_SIGN_ERROR;
|
|
2213
|
-
} else if (resultX.message) {
|
|
2214
|
-
// TODO(aasmund): I18nize `score.message`
|
|
2215
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2216
|
-
score.message = resultX.message + " Also, I'm a computer. I only understand " + "multiplication if you use an " + "asterisk (*) as the multiplication " + "sign.";
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
return score;
|
|
2221
|
-
};
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
};
|
|
2225
|
-
|
|
2226
1495
|
/**
|
|
2227
1496
|
* Checks user input from the expression widget to see if it is scorable.
|
|
2228
1497
|
*
|
|
@@ -2289,7 +1558,7 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2289
1558
|
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
2290
1559
|
simplify: answer.simplify,
|
|
2291
1560
|
form: answer.form
|
|
2292
|
-
})
|
|
1561
|
+
}));
|
|
2293
1562
|
};
|
|
2294
1563
|
|
|
2295
1564
|
// Find the first answer form that matches the user's input and that
|
|
@@ -2964,7 +2233,7 @@ function firstNumericalParse$1(text, strings) {
|
|
|
2964
2233
|
simplify: "optional",
|
|
2965
2234
|
inexact: true,
|
|
2966
2235
|
forms: "integer, proper, improper, pi, log, mixed, decimal"
|
|
2967
|
-
}
|
|
2236
|
+
});
|
|
2968
2237
|
val(text);
|
|
2969
2238
|
return first;
|
|
2970
2239
|
}
|
|
@@ -4508,17 +3777,14 @@ const InteractiveUtil = {
|
|
|
4508
3777
|
}
|
|
4509
3778
|
};
|
|
4510
3779
|
|
|
4511
|
-
function _classPrivateFieldBase(
|
|
4512
|
-
if (!
|
|
4513
|
-
|
|
4514
|
-
}
|
|
4515
|
-
|
|
4516
|
-
return receiver;
|
|
3780
|
+
function _classPrivateFieldBase(e, t) {
|
|
3781
|
+
if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
|
|
3782
|
+
return e;
|
|
4517
3783
|
}
|
|
4518
3784
|
|
|
4519
3785
|
var id = 0;
|
|
4520
|
-
function _classPrivateFieldKey(
|
|
4521
|
-
return "__private_" + id++ + "_" +
|
|
3786
|
+
function _classPrivateFieldKey(e) {
|
|
3787
|
+
return "__private_" + id++ + "_" + e;
|
|
4522
3788
|
}
|
|
4523
3789
|
|
|
4524
3790
|
/*!
|
|
@@ -14695,54 +13961,6 @@ var MovableLineOptions = {
|
|
|
14695
13961
|
}
|
|
14696
13962
|
};
|
|
14697
13963
|
|
|
14698
|
-
/**
|
|
14699
|
-
* A work-in-progress of _ methods for objects.
|
|
14700
|
-
* That is, they take an object as a parameter,
|
|
14701
|
-
* and return an object instead of an array.
|
|
14702
|
-
*
|
|
14703
|
-
* TODO(aria): Move this out of interactive2
|
|
14704
|
-
*/
|
|
14705
|
-
|
|
14706
|
-
/**
|
|
14707
|
-
* Does a pluck on keys inside objects in an object
|
|
14708
|
-
*
|
|
14709
|
-
* Ex:
|
|
14710
|
-
* tools = {
|
|
14711
|
-
* translation: {
|
|
14712
|
-
* enabled: true
|
|
14713
|
-
* },
|
|
14714
|
-
* rotation: {
|
|
14715
|
-
* enabled: false
|
|
14716
|
-
* }
|
|
14717
|
-
* };
|
|
14718
|
-
* pluckObject(tools, "enabled") returns {
|
|
14719
|
-
* translation: true
|
|
14720
|
-
* rotation: false
|
|
14721
|
-
* }
|
|
14722
|
-
*/
|
|
14723
|
-
const pluck = function pluck(table, subKey) {
|
|
14724
|
-
return _.object(_.map(table, function (value, key) {
|
|
14725
|
-
return [key, value[subKey]];
|
|
14726
|
-
}));
|
|
14727
|
-
};
|
|
14728
|
-
|
|
14729
|
-
/**
|
|
14730
|
-
* Maps an object to an object
|
|
14731
|
-
*
|
|
14732
|
-
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
14733
|
-
* return value + 1;
|
|
14734
|
-
* });
|
|
14735
|
-
* {a: 2, b: 3}
|
|
14736
|
-
*/
|
|
14737
|
-
const mapObject = function mapObject(obj, lambda) {
|
|
14738
|
-
const result = {};
|
|
14739
|
-
Object.keys(obj).forEach(key => {
|
|
14740
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
14741
|
-
result[key] = lambda(obj[key], key);
|
|
14742
|
-
});
|
|
14743
|
-
return result;
|
|
14744
|
-
};
|
|
14745
|
-
|
|
14746
13964
|
/**
|
|
14747
13965
|
* MovableLine
|
|
14748
13966
|
*/
|
|
@@ -18558,7 +17776,7 @@ const inlineNodeTypes = {
|
|
|
18558
17776
|
|
|
18559
17777
|
// @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
|
|
18560
17778
|
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
18561
|
-
const parse$
|
|
17779
|
+
const parse$4 = (source, state) => {
|
|
18562
17780
|
const paragraphedSource = source + "\n\n";
|
|
18563
17781
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
18564
17782
|
inline: false
|
|
@@ -18641,14 +17859,14 @@ const getContent = ast => {
|
|
|
18641
17859
|
*/
|
|
18642
17860
|
const characterCount = source => {
|
|
18643
17861
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
18644
|
-
const ast = parse$
|
|
17862
|
+
const ast = parse$4(source);
|
|
18645
17863
|
const content = getContent(ast).join("");
|
|
18646
17864
|
return content.length;
|
|
18647
17865
|
};
|
|
18648
17866
|
var PerseusMarkdown = {
|
|
18649
17867
|
characterCount: characterCount,
|
|
18650
17868
|
traverseContent: traverseContent,
|
|
18651
|
-
parse: parse$
|
|
17869
|
+
parse: parse$4,
|
|
18652
17870
|
parseInline: inlineParser,
|
|
18653
17871
|
reactFor: SimpleMarkdown.reactFor,
|
|
18654
17872
|
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
@@ -21145,19 +20363,14 @@ TextInput.defaultProps = {
|
|
|
21145
20363
|
disabled: false
|
|
21146
20364
|
};
|
|
21147
20365
|
|
|
21148
|
-
function _objectWithoutPropertiesLoose(
|
|
21149
|
-
if (
|
|
21150
|
-
var
|
|
21151
|
-
var
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
for (i = 0; i < sourceKeys.length; i++) {
|
|
21155
|
-
key = sourceKeys[i];
|
|
21156
|
-
if (excluded.indexOf(key) >= 0) continue;
|
|
21157
|
-
target[key] = source[key];
|
|
20366
|
+
function _objectWithoutPropertiesLoose(r, e) {
|
|
20367
|
+
if (null == r) return {};
|
|
20368
|
+
var t = {};
|
|
20369
|
+
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
|
|
20370
|
+
if (e.includes(n)) continue;
|
|
20371
|
+
t[n] = r[n];
|
|
21158
20372
|
}
|
|
21159
|
-
|
|
21160
|
-
return target;
|
|
20373
|
+
return t;
|
|
21161
20374
|
}
|
|
21162
20375
|
|
|
21163
20376
|
const _excluded$7 = ["position", "visibility", "left", "top"];
|
|
@@ -21882,7 +21095,7 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
21882
21095
|
inexact: rubric.inexact || undefined,
|
|
21883
21096
|
maxError: rubric.maxError,
|
|
21884
21097
|
forms: answerTypes[rubric.answerType].forms
|
|
21885
|
-
}
|
|
21098
|
+
});
|
|
21886
21099
|
|
|
21887
21100
|
// We may have received TeX; try to parse it before grading.
|
|
21888
21101
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
@@ -22220,7 +21433,7 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22220
21433
|
// TODO(merlob) backfill / delete
|
|
22221
21434
|
maxError: answer.maxError,
|
|
22222
21435
|
forms: validatorForms
|
|
22223
|
-
}
|
|
21436
|
+
});
|
|
22224
21437
|
};
|
|
22225
21438
|
|
|
22226
21439
|
// We may have received TeX; try to parse it before grading.
|
|
@@ -24325,7 +23538,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24325
23538
|
// TODO(LEMS-2656): remove TS suppression
|
|
24326
23539
|
// @ts-expect-error: State is not assignable to ParseState
|
|
24327
23540
|
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24328
|
-
const parse$
|
|
23541
|
+
const parse$3 = (source, state) => {
|
|
24329
23542
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24330
23543
|
state = state || {};
|
|
24331
23544
|
const paragraphedSource = source + "\n\n";
|
|
@@ -24335,7 +23548,7 @@ const parse$2 = (source, state) => {
|
|
|
24335
23548
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24336
23549
|
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
24337
23550
|
var PassageMarkdown = {
|
|
24338
|
-
parse: parse$
|
|
23551
|
+
parse: parse$3,
|
|
24339
23552
|
output,
|
|
24340
23553
|
START_REF_PREFIX,
|
|
24341
23554
|
END_REF_PREFIX,
|
|
@@ -28972,6 +28185,9 @@ const typeToButton = type => {
|
|
|
28972
28185
|
};
|
|
28973
28186
|
|
|
28974
28187
|
function getCoefficientsByType(data) {
|
|
28188
|
+
if (data.coords == null) {
|
|
28189
|
+
return undefined;
|
|
28190
|
+
}
|
|
28975
28191
|
if (data.type === "exponential" || data.type === "logarithm") {
|
|
28976
28192
|
const grader = functionForType(data.type);
|
|
28977
28193
|
return grader.getCoefficients(data.coords, data.asymptote);
|
|
@@ -31726,9 +30942,10 @@ const AxisTicks = () => {
|
|
|
31726
30942
|
* @param {number} axisIndex which axis we're getting options for
|
|
31727
30943
|
*/
|
|
31728
30944
|
const axisOptions = (props, axisIndex) => {
|
|
30945
|
+
const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
|
|
31729
30946
|
return {
|
|
31730
|
-
axis: props.markings === "graph",
|
|
31731
|
-
lines:
|
|
30947
|
+
axis: props.markings === "graph" || props.markings === "axes",
|
|
30948
|
+
lines: lines,
|
|
31732
30949
|
labels: false
|
|
31733
30950
|
};
|
|
31734
30951
|
};
|
|
@@ -32310,231 +31527,6 @@ const LegacyGrid = ({
|
|
|
32310
31527
|
return null;
|
|
32311
31528
|
};
|
|
32312
31529
|
|
|
32313
|
-
/**
|
|
32314
|
-
* The Perseus "data schema" file.
|
|
32315
|
-
*
|
|
32316
|
-
* This file, and the types in it, represents the "data schema" that Perseus
|
|
32317
|
-
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
32318
|
-
* that conform to the types in this file. Similarly, the top-level renderers
|
|
32319
|
-
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
32320
|
-
*
|
|
32321
|
-
* WARNING: This file should not import any types from elsewhere so that it is
|
|
32322
|
-
* easy to reason about changes that alter the Perseus schema. This helps
|
|
32323
|
-
* ensure that it is not changed accidentally when upgrading a dependant
|
|
32324
|
-
* package or other part of Perseus code. Note that TypeScript does type
|
|
32325
|
-
* checking via something called "structural typing". This means that as long
|
|
32326
|
-
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
32327
|
-
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
32328
|
-
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
32329
|
-
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
32330
|
-
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
32331
|
-
* labels are for humans, not TypeScript. :)
|
|
32332
|
-
*
|
|
32333
|
-
* If you make changes to types in this file, be very sure that:
|
|
32334
|
-
*
|
|
32335
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
32336
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
32337
|
-
* introduce hard-to-diagnose bugs.
|
|
32338
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
32339
|
-
* the new format _as well as_ the old format.
|
|
32340
|
-
*/
|
|
32341
|
-
|
|
32342
|
-
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
32343
|
-
|
|
32344
|
-
// Same name as Mafs
|
|
32345
|
-
|
|
32346
|
-
/**
|
|
32347
|
-
* Our core set of Perseus widgets.
|
|
32348
|
-
*
|
|
32349
|
-
* This interface is the basis for "registering" all Perseus widget types.
|
|
32350
|
-
* There should be one key/value pair for each supported widget. If you create
|
|
32351
|
-
* a new widget, an entry should be added to this interface. Note that this
|
|
32352
|
-
* only registers the widget options type, you'll also need to register the
|
|
32353
|
-
* widget so that it's available at runtime (@see
|
|
32354
|
-
* {@link file://./widgets.ts#registerWidget}).
|
|
32355
|
-
*
|
|
32356
|
-
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
32357
|
-
* widgets that is the same as the widget option's `type` field. In cases where
|
|
32358
|
-
* a widget has been deprecated and replaced with the deprecated-standin
|
|
32359
|
-
* widget, it should be the original widget type!
|
|
32360
|
-
*
|
|
32361
|
-
* If you define the widget outside of this package, you can still add the new
|
|
32362
|
-
* widget to this interface by writing the following in that package that
|
|
32363
|
-
* contains the widget. TypeScript will merge that definition of the
|
|
32364
|
-
* `PerseusWidgets` with the one defined below.
|
|
32365
|
-
*
|
|
32366
|
-
* ```typescript
|
|
32367
|
-
* declare module "@khanacademy/perseus" {
|
|
32368
|
-
* interface PerseusWidgetTypes {
|
|
32369
|
-
* // A new widget
|
|
32370
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
32371
|
-
*
|
|
32372
|
-
* // A deprecated widget
|
|
32373
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
32374
|
-
* }
|
|
32375
|
-
* }
|
|
32376
|
-
*
|
|
32377
|
-
* // The new widget's options definition
|
|
32378
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
32379
|
-
*
|
|
32380
|
-
* // The deprecated widget's options definition
|
|
32381
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
32382
|
-
* ```
|
|
32383
|
-
*
|
|
32384
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
32385
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
32386
|
-
*
|
|
32387
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32388
|
-
*/
|
|
32389
|
-
|
|
32390
|
-
/**
|
|
32391
|
-
* A map of widget IDs to widget options. This is most often used as the type
|
|
32392
|
-
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
32393
|
-
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
32394
|
-
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
32395
|
-
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
32396
|
-
* reading/parsing the widget id to derive any information from it, except in
|
|
32397
|
-
* the case of this map.
|
|
32398
|
-
*
|
|
32399
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32400
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32401
|
-
*/
|
|
32402
|
-
|
|
32403
|
-
/**
|
|
32404
|
-
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32405
|
-
* `ServerItemRenderer` and the layout is pre-set.
|
|
32406
|
-
*
|
|
32407
|
-
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
32408
|
-
* area.
|
|
32409
|
-
*/
|
|
32410
|
-
|
|
32411
|
-
/**
|
|
32412
|
-
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32413
|
-
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32414
|
-
*/
|
|
32415
|
-
|
|
32416
|
-
const ItemExtras = [
|
|
32417
|
-
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
32418
|
-
"calculator",
|
|
32419
|
-
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
32420
|
-
"chi2Table",
|
|
32421
|
-
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
32422
|
-
"financialCalculatorMonthlyPayment",
|
|
32423
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
32424
|
-
"financialCalculatorTotalAmount",
|
|
32425
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
32426
|
-
"financialCalculatorTimeToPayOff",
|
|
32427
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
32428
|
-
"periodicTable",
|
|
32429
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
32430
|
-
"periodicTableWithKey",
|
|
32431
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
32432
|
-
"tTable",
|
|
32433
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
32434
|
-
"zTable"];
|
|
32435
|
-
|
|
32436
|
-
/**
|
|
32437
|
-
* The type representing the common structure of all widget's options. The
|
|
32438
|
-
* `Options` generic type represents the widget-specific option data.
|
|
32439
|
-
*/
|
|
32440
|
-
|
|
32441
|
-
// prettier-ignore
|
|
32442
|
-
|
|
32443
|
-
// prettier-ignore
|
|
32444
|
-
|
|
32445
|
-
// prettier-ignore
|
|
32446
|
-
|
|
32447
|
-
// prettier-ignore
|
|
32448
|
-
|
|
32449
|
-
// prettier-ignore
|
|
32450
|
-
|
|
32451
|
-
// prettier-ignore
|
|
32452
|
-
|
|
32453
|
-
// prettier-ignore
|
|
32454
|
-
|
|
32455
|
-
// prettier-ignore
|
|
32456
|
-
|
|
32457
|
-
// prettier-ignore
|
|
32458
|
-
|
|
32459
|
-
// prettier-ignore
|
|
32460
|
-
|
|
32461
|
-
// prettier-ignore
|
|
32462
|
-
|
|
32463
|
-
// prettier-ignore
|
|
32464
|
-
|
|
32465
|
-
// prettier-ignore
|
|
32466
|
-
|
|
32467
|
-
// prettier-ignore
|
|
32468
|
-
|
|
32469
|
-
// prettier-ignore
|
|
32470
|
-
|
|
32471
|
-
// prettier-ignore
|
|
32472
|
-
|
|
32473
|
-
// prettier-ignore
|
|
32474
|
-
|
|
32475
|
-
// prettier-ignore
|
|
32476
|
-
|
|
32477
|
-
// prettier-ignore
|
|
32478
|
-
|
|
32479
|
-
// prettier-ignore
|
|
32480
|
-
|
|
32481
|
-
// prettier-ignore
|
|
32482
|
-
|
|
32483
|
-
// prettier-ignore
|
|
32484
|
-
|
|
32485
|
-
// prettier-ignore
|
|
32486
|
-
|
|
32487
|
-
// prettier-ignore
|
|
32488
|
-
|
|
32489
|
-
// prettier-ignore
|
|
32490
|
-
|
|
32491
|
-
// prettier-ignore
|
|
32492
|
-
|
|
32493
|
-
// prettier-ignore
|
|
32494
|
-
|
|
32495
|
-
// prettier-ignore
|
|
32496
|
-
|
|
32497
|
-
// prettier-ignore
|
|
32498
|
-
|
|
32499
|
-
// prettier-ignore
|
|
32500
|
-
|
|
32501
|
-
// prettier-ignore
|
|
32502
|
-
|
|
32503
|
-
// prettier-ignore
|
|
32504
|
-
|
|
32505
|
-
// prettier-ignore
|
|
32506
|
-
|
|
32507
|
-
//prettier-ignore
|
|
32508
|
-
|
|
32509
|
-
/**
|
|
32510
|
-
* A background image applied to various widgets.
|
|
32511
|
-
*/
|
|
32512
|
-
|
|
32513
|
-
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32514
|
-
|
|
32515
|
-
// 2D range: xMin, xMax, yMin, yMax
|
|
32516
|
-
|
|
32517
|
-
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
32518
|
-
const lockedFigureColors = {
|
|
32519
|
-
blue: "#3D7586",
|
|
32520
|
-
green: "#447A53",
|
|
32521
|
-
grayH: "#3B3D45",
|
|
32522
|
-
purple: "#594094",
|
|
32523
|
-
pink: "#B25071",
|
|
32524
|
-
red: "#D92916",
|
|
32525
|
-
orange: "#946700"
|
|
32526
|
-
};
|
|
32527
|
-
const lockedFigureFillStyles = {
|
|
32528
|
-
none: 0,
|
|
32529
|
-
white: 1,
|
|
32530
|
-
translucent: 0.4,
|
|
32531
|
-
solid: 1
|
|
32532
|
-
};
|
|
32533
|
-
|
|
32534
|
-
// Not associated with a specific figure
|
|
32535
|
-
|
|
32536
|
-
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
32537
|
-
|
|
32538
31530
|
function LockedLabel(props) {
|
|
32539
31531
|
const {
|
|
32540
31532
|
coord,
|
|
@@ -34305,7 +33297,9 @@ function renderCircleGraph(state, dispatch) {
|
|
|
34305
33297
|
graphState: state,
|
|
34306
33298
|
dispatch: dispatch
|
|
34307
33299
|
}),
|
|
34308
|
-
interactiveElementsDescription:
|
|
33300
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(CircleGraphDescription, {
|
|
33301
|
+
state: state
|
|
33302
|
+
})
|
|
34309
33303
|
};
|
|
34310
33304
|
}
|
|
34311
33305
|
function CircleGraph(props) {
|
|
@@ -34329,38 +33323,26 @@ function CircleGraph(props) {
|
|
|
34329
33323
|
const outerPointsId = id + "-outer-points";
|
|
34330
33324
|
|
|
34331
33325
|
// Aria label strings
|
|
34332
|
-
const
|
|
34333
|
-
|
|
34334
|
-
|
|
34335
|
-
|
|
34336
|
-
|
|
34337
|
-
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
|
|
34341
|
-
const circleRadiusDescription = strings.srCircleRadius({
|
|
34342
|
-
radius
|
|
34343
|
-
});
|
|
34344
|
-
const circleOuterPointsDescription = strings.srCircleOuterPoints({
|
|
34345
|
-
point1X: srFormatNumber(center[0] + radius, locale),
|
|
34346
|
-
point1Y: srFormatNumber(center[1], locale),
|
|
34347
|
-
point2X: srFormatNumber(center[0], locale),
|
|
34348
|
-
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
34349
|
-
point3X: srFormatNumber(center[0] - radius, locale),
|
|
34350
|
-
point3Y: srFormatNumber(center[1], locale),
|
|
34351
|
-
point4X: srFormatNumber(center[0], locale),
|
|
34352
|
-
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33326
|
+
const {
|
|
33327
|
+
srCircleGraph,
|
|
33328
|
+
srCircleShape,
|
|
33329
|
+
srCircleRadiusPoint,
|
|
33330
|
+
srCircleRadius,
|
|
33331
|
+
srCircleOuterPoints
|
|
33332
|
+
} = describeCircleGraph(graphState, {
|
|
33333
|
+
strings,
|
|
33334
|
+
locale
|
|
34353
33335
|
});
|
|
34354
33336
|
return /*#__PURE__*/React.createElement("g", {
|
|
34355
33337
|
// Outer circle minimal description
|
|
34356
|
-
"aria-label":
|
|
33338
|
+
"aria-label": srCircleGraph,
|
|
34357
33339
|
"aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
|
|
34358
33340
|
}, /*#__PURE__*/React.createElement(MovableCircle, {
|
|
34359
33341
|
id: circleId
|
|
34360
33342
|
// Focusable circle aria label reads with every update
|
|
34361
33343
|
// because of the aria-live property in the circle <g>.
|
|
34362
33344
|
,
|
|
34363
|
-
ariaLabel:
|
|
33345
|
+
ariaLabel: srCircleShape
|
|
34364
33346
|
// Aria-describedby describes additional info on focus.
|
|
34365
33347
|
,
|
|
34366
33348
|
ariaDescribedBy: `${radiusId} ${outerPointsId}`,
|
|
@@ -34373,7 +33355,7 @@ function CircleGraph(props) {
|
|
|
34373
33355
|
}), /*#__PURE__*/React.createElement(MovablePoint$1
|
|
34374
33356
|
// Radius point aria label reads with every update.
|
|
34375
33357
|
, {
|
|
34376
|
-
ariaLabel: `${
|
|
33358
|
+
ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
|
|
34377
33359
|
// Aria-describedby describes additional info on focus.
|
|
34378
33360
|
,
|
|
34379
33361
|
ariaDescribedBy: `${outerPointsId}`
|
|
@@ -34396,12 +33378,12 @@ function CircleGraph(props) {
|
|
|
34396
33378
|
style: {
|
|
34397
33379
|
display: "hidden"
|
|
34398
33380
|
}
|
|
34399
|
-
},
|
|
33381
|
+
}, srCircleRadius), /*#__PURE__*/React.createElement("g", {
|
|
34400
33382
|
id: outerPointsId,
|
|
34401
33383
|
style: {
|
|
34402
33384
|
display: "hidden"
|
|
34403
33385
|
}
|
|
34404
|
-
},
|
|
33386
|
+
}, srCircleOuterPoints));
|
|
34405
33387
|
}
|
|
34406
33388
|
function MovableCircle(props) {
|
|
34407
33389
|
const {
|
|
@@ -34488,6 +33470,64 @@ function crossProduct(as, bs) {
|
|
|
34488
33470
|
}
|
|
34489
33471
|
return result;
|
|
34490
33472
|
}
|
|
33473
|
+
function CircleGraphDescription({
|
|
33474
|
+
state
|
|
33475
|
+
}) {
|
|
33476
|
+
// The reason that CircleGraphDescription is a component (rather than a
|
|
33477
|
+
// function that returns a string) is because it needs to use a
|
|
33478
|
+
// hook: `usePerseusI18n`.
|
|
33479
|
+
const i18n = usePerseusI18n();
|
|
33480
|
+
const strings = describeCircleGraph(state, i18n);
|
|
33481
|
+
return strings.srCircleInteractiveElement;
|
|
33482
|
+
}
|
|
33483
|
+
|
|
33484
|
+
// Exported for testing
|
|
33485
|
+
function describeCircleGraph(state, i18n) {
|
|
33486
|
+
const {
|
|
33487
|
+
strings,
|
|
33488
|
+
locale
|
|
33489
|
+
} = i18n;
|
|
33490
|
+
const {
|
|
33491
|
+
center,
|
|
33492
|
+
radiusPoint
|
|
33493
|
+
} = state;
|
|
33494
|
+
const radius = getRadius(state);
|
|
33495
|
+
|
|
33496
|
+
// Aria label strings
|
|
33497
|
+
const srCircleGraph = strings.srCircleGraph;
|
|
33498
|
+
const srCircleShape = strings.srCircleShape({
|
|
33499
|
+
centerX: srFormatNumber(center[0], locale),
|
|
33500
|
+
centerY: srFormatNumber(center[1], locale)
|
|
33501
|
+
});
|
|
33502
|
+
const srCircleRadiusPoint = strings.srCircleRadiusPoint({
|
|
33503
|
+
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
33504
|
+
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
33505
|
+
});
|
|
33506
|
+
const srCircleRadius = strings.srCircleRadius({
|
|
33507
|
+
radius
|
|
33508
|
+
});
|
|
33509
|
+
const srCircleOuterPoints = strings.srCircleOuterPoints({
|
|
33510
|
+
point1X: srFormatNumber(center[0] + radius, locale),
|
|
33511
|
+
point1Y: srFormatNumber(center[1], locale),
|
|
33512
|
+
point2X: srFormatNumber(center[0], locale),
|
|
33513
|
+
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
33514
|
+
point3X: srFormatNumber(center[0] - radius, locale),
|
|
33515
|
+
point3Y: srFormatNumber(center[1], locale),
|
|
33516
|
+
point4X: srFormatNumber(center[0], locale),
|
|
33517
|
+
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33518
|
+
});
|
|
33519
|
+
const srCircleInteractiveElement = strings.srInteractiveElements({
|
|
33520
|
+
elements: [srCircleShape, srCircleRadius].join(" ")
|
|
33521
|
+
});
|
|
33522
|
+
return {
|
|
33523
|
+
srCircleGraph,
|
|
33524
|
+
srCircleShape,
|
|
33525
|
+
srCircleRadiusPoint,
|
|
33526
|
+
srCircleRadius,
|
|
33527
|
+
srCircleOuterPoints,
|
|
33528
|
+
srCircleInteractiveElement
|
|
33529
|
+
};
|
|
33530
|
+
}
|
|
34491
33531
|
|
|
34492
33532
|
function renderLinearGraph(state, dispatch) {
|
|
34493
33533
|
return {
|
|
@@ -35478,7 +34518,9 @@ const MafsGraph = props => {
|
|
|
35478
34518
|
labels,
|
|
35479
34519
|
disableKeyboardInteraction: readOnly || !!props.static
|
|
35480
34520
|
}
|
|
35481
|
-
}, /*#__PURE__*/React.createElement(View,
|
|
34521
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
34522
|
+
className: "mafs-graph-container"
|
|
34523
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
35482
34524
|
className: "mafs-graph",
|
|
35483
34525
|
style: {
|
|
35484
34526
|
position: "relative",
|
|
@@ -35521,7 +34563,7 @@ const MafsGraph = props => {
|
|
|
35521
34563
|
bottom: 0,
|
|
35522
34564
|
left: 0
|
|
35523
34565
|
}
|
|
35524
|
-
}, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
34566
|
+
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
35525
34567
|
preserveAspectRatio: false,
|
|
35526
34568
|
viewBox: {
|
|
35527
34569
|
x: state.range[X],
|
|
@@ -35541,7 +34583,7 @@ const MafsGraph = props => {
|
|
|
35541
34583
|
height: height
|
|
35542
34584
|
})),
|
|
35543
34585
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
35544
|
-
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
34586
|
+
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
35545
34587
|
lockedFigures: props.lockedFigures,
|
|
35546
34588
|
range: state.range
|
|
35547
34589
|
}))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
@@ -41041,7 +40083,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41041
40083
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
41042
40084
|
solution[row][col], {
|
|
41043
40085
|
simplify: true
|
|
41044
|
-
}
|
|
40086
|
+
});
|
|
41045
40087
|
const result = validator(supplied[row][col]);
|
|
41046
40088
|
if (result.message) {
|
|
41047
40089
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
@@ -42194,10 +41236,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
42194
41236
|
const firstChar = smiles[0];
|
|
42195
41237
|
const rest = smiles.slice(1);
|
|
42196
41238
|
if (firstChar === "=") {
|
|
42197
|
-
return parse$
|
|
41239
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
42198
41240
|
}
|
|
42199
41241
|
if (firstChar === "#") {
|
|
42200
|
-
return parse$
|
|
41242
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
42201
41243
|
}
|
|
42202
41244
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
42203
41245
|
}
|
|
@@ -42256,8 +41298,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
42256
41298
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
42257
41299
|
parens: newCtx.parens.concat("(")
|
|
42258
41300
|
});
|
|
42259
|
-
const parenExpr = parse$
|
|
42260
|
-
const remainder = parse$
|
|
41301
|
+
const parenExpr = parse$2(rest, parenCtx);
|
|
41302
|
+
const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
42261
41303
|
return [parenExpr].concat(remainder);
|
|
42262
41304
|
}
|
|
42263
41305
|
if (firstChar === ")") {
|
|
@@ -42321,7 +41363,7 @@ function parseAtom(smiles, ctx) {
|
|
|
42321
41363
|
|
|
42322
41364
|
// increment the atom counter and reset the branch counter
|
|
42323
41365
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
42324
|
-
let restOfMolecule = parse$
|
|
41366
|
+
let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
42325
41367
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
42326
41368
|
//TODO(colin): fix this awkwardness.
|
|
42327
41369
|
restOfMolecule = [restOfMolecule];
|
|
@@ -42360,7 +41402,7 @@ function isModifierChar(s) {
|
|
|
42360
41402
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
42361
41403
|
* yet implemented.
|
|
42362
41404
|
*/
|
|
42363
|
-
function parse$
|
|
41405
|
+
function parse$2(smiles, ctx) {
|
|
42364
41406
|
if (!validate(smiles)) {
|
|
42365
41407
|
throw new ParseError$1("Invalid input.");
|
|
42366
41408
|
}
|
|
@@ -42385,7 +41427,7 @@ function parse$1(smiles, ctx) {
|
|
|
42385
41427
|
return parseParenthesizedExpression(smiles, ctx);
|
|
42386
41428
|
}
|
|
42387
41429
|
var SmilesParser = {
|
|
42388
|
-
parse: parse$
|
|
41430
|
+
parse: parse$2,
|
|
42389
41431
|
ParseError: ParseError$1
|
|
42390
41432
|
};
|
|
42391
41433
|
|
|
@@ -42393,7 +41435,7 @@ var SmilesParser = {
|
|
|
42393
41435
|
const {
|
|
42394
41436
|
layout
|
|
42395
41437
|
} = MoleculeLayout;
|
|
42396
|
-
const parse = SmilesParser.parse;
|
|
41438
|
+
const parse$1 = SmilesParser.parse;
|
|
42397
41439
|
const ParseError = SmilesParser.ParseError;
|
|
42398
41440
|
const borderSize = 30;
|
|
42399
41441
|
class Molecule extends React.Component {
|
|
@@ -42407,7 +41449,7 @@ class Molecule extends React.Component {
|
|
|
42407
41449
|
try {
|
|
42408
41450
|
this.setState({
|
|
42409
41451
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
42410
|
-
parsedSmiles: parse(smiles),
|
|
41452
|
+
parsedSmiles: parse$1(smiles),
|
|
42411
41453
|
error: null
|
|
42412
41454
|
});
|
|
42413
41455
|
} catch (e) {
|
|
@@ -45093,7 +44135,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45093
44135
|
const cellSolution = rowSolution[i];
|
|
45094
44136
|
const validator = createValidator(cellSolution, {
|
|
45095
44137
|
simplify: true
|
|
45096
|
-
}
|
|
44138
|
+
});
|
|
45097
44139
|
const result = validator(cellSupplied);
|
|
45098
44140
|
if (result.message) {
|
|
45099
44141
|
message = result.message;
|
|
@@ -45517,7 +44559,7 @@ const init = function init() {
|
|
|
45517
44559
|
|
|
45518
44560
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45519
44561
|
const libName = "@khanacademy/perseus";
|
|
45520
|
-
const libVersion = "
|
|
44562
|
+
const libVersion = "50.0.0";
|
|
45521
44563
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45522
44564
|
|
|
45523
44565
|
const apiVersion = {
|
|
@@ -47598,6 +46640,69 @@ function all(results, combineFailureDetails = a => a) {
|
|
|
47598
46640
|
return success(values);
|
|
47599
46641
|
}
|
|
47600
46642
|
|
|
46643
|
+
class ErrorTrackingParseContext {
|
|
46644
|
+
constructor(path) {
|
|
46645
|
+
this.path = path;
|
|
46646
|
+
}
|
|
46647
|
+
failure(expected, badValue) {
|
|
46648
|
+
return failure([{
|
|
46649
|
+
expected: wrapInArray(expected),
|
|
46650
|
+
badValue,
|
|
46651
|
+
path: this.path
|
|
46652
|
+
}]);
|
|
46653
|
+
}
|
|
46654
|
+
forSubtree(key) {
|
|
46655
|
+
return new ErrorTrackingParseContext([...this.path, key]);
|
|
46656
|
+
}
|
|
46657
|
+
success(value) {
|
|
46658
|
+
return success(value);
|
|
46659
|
+
}
|
|
46660
|
+
}
|
|
46661
|
+
function wrapInArray(a) {
|
|
46662
|
+
return Array.isArray(a) ? a : [a];
|
|
46663
|
+
}
|
|
46664
|
+
|
|
46665
|
+
function formatPath(path) {
|
|
46666
|
+
return "(root)" + path.map(formatPathSegment).join("");
|
|
46667
|
+
}
|
|
46668
|
+
function formatPathSegment(segment) {
|
|
46669
|
+
if (typeof segment === "string") {
|
|
46670
|
+
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
46671
|
+
}
|
|
46672
|
+
return `[${segment.toString()}]`;
|
|
46673
|
+
}
|
|
46674
|
+
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
46675
|
+
|
|
46676
|
+
function message(failure) {
|
|
46677
|
+
const expected = conjoin(failure.expected);
|
|
46678
|
+
const path = formatPath(failure.path);
|
|
46679
|
+
const badValue = JSON.stringify(failure.badValue);
|
|
46680
|
+
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
46681
|
+
}
|
|
46682
|
+
function conjoin(items) {
|
|
46683
|
+
switch (items.length) {
|
|
46684
|
+
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
46685
|
+
case 1:
|
|
46686
|
+
return items[0];
|
|
46687
|
+
case 2:
|
|
46688
|
+
return items.join(" or ");
|
|
46689
|
+
default:
|
|
46690
|
+
{
|
|
46691
|
+
const allButLast = items.slice(0, items.length - 1);
|
|
46692
|
+
const last = items[items.length - 1];
|
|
46693
|
+
return allButLast.join(", ") + ", or " + last;
|
|
46694
|
+
}
|
|
46695
|
+
}
|
|
46696
|
+
}
|
|
46697
|
+
|
|
46698
|
+
function parse(value, parser) {
|
|
46699
|
+
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
46700
|
+
if (isFailure(result)) {
|
|
46701
|
+
return failure(result.detail.map(message).join("; "));
|
|
46702
|
+
}
|
|
46703
|
+
return result;
|
|
46704
|
+
}
|
|
46705
|
+
|
|
47601
46706
|
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
47602
46707
|
|
|
47603
46708
|
function array(elementParser) {
|
|
@@ -47878,7 +46983,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
47878
46983
|
})),
|
|
47879
46984
|
showEditor: boolean,
|
|
47880
46985
|
showButtons: boolean,
|
|
47881
|
-
width: number,
|
|
47882
46986
|
height: number,
|
|
47883
46987
|
static: defaulted(boolean, () => false)
|
|
47884
46988
|
}));
|
|
@@ -48065,25 +47169,6 @@ function migrateV0ToV1(widget) {
|
|
|
48065
47169
|
}
|
|
48066
47170
|
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
48067
47171
|
|
|
48068
|
-
const parsePerseusRenderer = defaulted(object({
|
|
48069
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
48070
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
48071
|
-
content: defaulted(string, () => ""),
|
|
48072
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
48073
|
-
// `group` widget can contain another renderer.
|
|
48074
|
-
// The anonymous function below ensures that we don't try to access
|
|
48075
|
-
// parseWidgetsMap before it's defined.
|
|
48076
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48077
|
-
metadata: optional(array(string)),
|
|
48078
|
-
images: parseImages
|
|
48079
|
-
}),
|
|
48080
|
-
// Default value
|
|
48081
|
-
() => ({
|
|
48082
|
-
content: "",
|
|
48083
|
-
widgets: {},
|
|
48084
|
-
images: {}
|
|
48085
|
-
}));
|
|
48086
|
-
|
|
48087
47172
|
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
48088
47173
|
const parseGradedGroupWidgetOptions = object({
|
|
48089
47174
|
title: defaulted(string, () => ""),
|
|
@@ -48154,27 +47239,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
|
48154
47239
|
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
48155
47240
|
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
48156
47241
|
type: constant("absolute_value"),
|
|
48157
|
-
coords: pairOfPoints
|
|
47242
|
+
coords: nullable(pairOfPoints)
|
|
48158
47243
|
})).withBranch("exponential", object({
|
|
48159
47244
|
type: constant("exponential"),
|
|
48160
47245
|
asymptote: pairOfPoints,
|
|
48161
|
-
coords: pairOfPoints
|
|
47246
|
+
coords: nullable(pairOfPoints)
|
|
48162
47247
|
})).withBranch("linear", object({
|
|
48163
47248
|
type: constant("linear"),
|
|
48164
|
-
coords:
|
|
47249
|
+
coords: nullable(pairOfPoints)
|
|
48165
47250
|
})).withBranch("logarithm", object({
|
|
48166
47251
|
type: constant("logarithm"),
|
|
48167
47252
|
asymptote: pairOfPoints,
|
|
48168
|
-
coords: pairOfPoints
|
|
47253
|
+
coords: nullable(pairOfPoints)
|
|
48169
47254
|
})).withBranch("quadratic", object({
|
|
48170
47255
|
type: constant("quadratic"),
|
|
48171
|
-
coords: pairOfPoints
|
|
47256
|
+
coords: nullable(pairOfPoints)
|
|
48172
47257
|
})).withBranch("sinusoid", object({
|
|
48173
47258
|
type: constant("sinusoid"),
|
|
48174
|
-
coords: pairOfPoints
|
|
47259
|
+
coords: nullable(pairOfPoints)
|
|
48175
47260
|
})).withBranch("tangent", object({
|
|
48176
47261
|
type: constant("tangent"),
|
|
48177
|
-
coords: pairOfPoints
|
|
47262
|
+
coords: nullable(pairOfPoints)
|
|
48178
47263
|
})).parser,
|
|
48179
47264
|
graph: object({
|
|
48180
47265
|
backgroundImage: object({
|
|
@@ -48210,13 +47295,13 @@ const parseGroupWidget = parseWidget(constant("group"),
|
|
|
48210
47295
|
|
|
48211
47296
|
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
48212
47297
|
url: string,
|
|
48213
|
-
settings: array(object({
|
|
47298
|
+
settings: optional(array(object({
|
|
48214
47299
|
name: string,
|
|
48215
47300
|
value: string
|
|
48216
|
-
})),
|
|
47301
|
+
}))),
|
|
48217
47302
|
width: union(number).or(string).parser,
|
|
48218
47303
|
height: union(number).or(string).parser,
|
|
48219
|
-
allowFullScreen: boolean,
|
|
47304
|
+
allowFullScreen: defaulted(boolean, () => false),
|
|
48220
47305
|
allowTopNavigation: optional(boolean),
|
|
48221
47306
|
static: defaulted(boolean, () => false)
|
|
48222
47307
|
}));
|
|
@@ -48289,10 +47374,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48289
47374
|
|
|
48290
47375
|
const pairOfNumbers$1 = pair(number, number);
|
|
48291
47376
|
const stringOrEmpty = defaulted(string, () => "");
|
|
48292
|
-
const
|
|
47377
|
+
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
48293
47378
|
const parseFunctionElement = object({
|
|
48294
|
-
type:
|
|
48295
|
-
key:
|
|
47379
|
+
type: constant("function"),
|
|
47380
|
+
key: parseKey,
|
|
48296
47381
|
options: object({
|
|
48297
47382
|
value: string,
|
|
48298
47383
|
funcName: string,
|
|
@@ -48303,10 +47388,9 @@ const parseFunctionElement = object({
|
|
|
48303
47388
|
strokeWidth: number
|
|
48304
47389
|
})
|
|
48305
47390
|
});
|
|
48306
|
-
const parseLabelType = constant("label");
|
|
48307
47391
|
const parseLabelElement = object({
|
|
48308
|
-
type:
|
|
48309
|
-
key:
|
|
47392
|
+
type: constant("label"),
|
|
47393
|
+
key: parseKey,
|
|
48310
47394
|
options: object({
|
|
48311
47395
|
label: string,
|
|
48312
47396
|
color: string,
|
|
@@ -48314,10 +47398,9 @@ const parseLabelElement = object({
|
|
|
48314
47398
|
coordY: string
|
|
48315
47399
|
})
|
|
48316
47400
|
});
|
|
48317
|
-
const parseLineType = constant("line");
|
|
48318
47401
|
const parseLineElement = object({
|
|
48319
|
-
type:
|
|
48320
|
-
key:
|
|
47402
|
+
type: constant("line"),
|
|
47403
|
+
key: parseKey,
|
|
48321
47404
|
options: object({
|
|
48322
47405
|
color: string,
|
|
48323
47406
|
startX: string,
|
|
@@ -48329,10 +47412,9 @@ const parseLineElement = object({
|
|
|
48329
47412
|
arrows: string
|
|
48330
47413
|
})
|
|
48331
47414
|
});
|
|
48332
|
-
const parseMovableLineType = constant("movable-line");
|
|
48333
47415
|
const parseMovableLineElement = object({
|
|
48334
|
-
type:
|
|
48335
|
-
key:
|
|
47416
|
+
type: constant("movable-line"),
|
|
47417
|
+
key: parseKey,
|
|
48336
47418
|
options: object({
|
|
48337
47419
|
startX: string,
|
|
48338
47420
|
startY: string,
|
|
@@ -48349,10 +47431,9 @@ const parseMovableLineElement = object({
|
|
|
48349
47431
|
constraintYMax: string
|
|
48350
47432
|
})
|
|
48351
47433
|
});
|
|
48352
|
-
const parseMovablePointType = constant("movable-point");
|
|
48353
47434
|
const parseMovablePointElement = object({
|
|
48354
|
-
type:
|
|
48355
|
-
key:
|
|
47435
|
+
type: constant("movable-point"),
|
|
47436
|
+
key: parseKey,
|
|
48356
47437
|
options: object({
|
|
48357
47438
|
startX: string,
|
|
48358
47439
|
startY: string,
|
|
@@ -48366,10 +47447,9 @@ const parseMovablePointElement = object({
|
|
|
48366
47447
|
constraintYMax: stringOrEmpty
|
|
48367
47448
|
})
|
|
48368
47449
|
});
|
|
48369
|
-
const parseParametricType = constant("parametric");
|
|
48370
47450
|
const parseParametricElement = object({
|
|
48371
|
-
type:
|
|
48372
|
-
key:
|
|
47451
|
+
type: constant("parametric"),
|
|
47452
|
+
key: parseKey,
|
|
48373
47453
|
options: object({
|
|
48374
47454
|
x: string,
|
|
48375
47455
|
y: string,
|
|
@@ -48380,20 +47460,18 @@ const parseParametricElement = object({
|
|
|
48380
47460
|
strokeWidth: number
|
|
48381
47461
|
})
|
|
48382
47462
|
});
|
|
48383
|
-
const parsePointType = constant("point");
|
|
48384
47463
|
const parsePointElement = object({
|
|
48385
|
-
type:
|
|
48386
|
-
key:
|
|
47464
|
+
type: constant("point"),
|
|
47465
|
+
key: parseKey,
|
|
48387
47466
|
options: object({
|
|
48388
47467
|
color: string,
|
|
48389
47468
|
coordX: string,
|
|
48390
47469
|
coordY: string
|
|
48391
47470
|
})
|
|
48392
47471
|
});
|
|
48393
|
-
const parseRectangleType = constant("rectangle");
|
|
48394
47472
|
const parseRectangleElement = object({
|
|
48395
|
-
type:
|
|
48396
|
-
key:
|
|
47473
|
+
type: constant("rectangle"),
|
|
47474
|
+
key: parseKey,
|
|
48397
47475
|
options: object({
|
|
48398
47476
|
color: string,
|
|
48399
47477
|
coordX: string,
|
|
@@ -48539,8 +47617,8 @@ const parseLockedLineType = object({
|
|
|
48539
47617
|
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48540
47618
|
color: parseLockedFigureColor,
|
|
48541
47619
|
lineStyle: parseLockedLineStyle,
|
|
48542
|
-
showPoint1: boolean,
|
|
48543
|
-
showPoint2: boolean,
|
|
47620
|
+
showPoint1: defaulted(boolean, () => false),
|
|
47621
|
+
showPoint2: defaulted(boolean, () => false),
|
|
48544
47622
|
// TODO(benchristel): default labels to empty array?
|
|
48545
47623
|
labels: optional(array(parseLockedLabelType)),
|
|
48546
47624
|
ariaLabel: optional(string)
|
|
@@ -48587,7 +47665,7 @@ const parseLockedFunctionType = object({
|
|
|
48587
47665
|
labels: optional(array(parseLockedLabelType)),
|
|
48588
47666
|
ariaLabel: optional(string)
|
|
48589
47667
|
});
|
|
48590
|
-
const parseLockedFigure =
|
|
47668
|
+
const parseLockedFigure = discriminatedUnionOn("type").withBranch("point", parseLockedPointType).withBranch("line", parseLockedLineType).withBranch("vector", parseLockedVectorType).withBranch("ellipse", parseLockedEllipseType).withBranch("polygon", parseLockedPolygonType).withBranch("function", parseLockedFunctionType).withBranch("label", parseLockedLabelType).parser;
|
|
48591
47669
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48592
47670
|
step: pairOfNumbers,
|
|
48593
47671
|
// TODO(benchristel): rather than making gridStep and snapStep
|
|
@@ -48655,7 +47733,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
|
|
|
48655
47733
|
}));
|
|
48656
47734
|
|
|
48657
47735
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
48658
|
-
image
|
|
47736
|
+
// The default value for image comes from measurer.tsx.
|
|
47737
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47738
|
+
// defaults here.
|
|
47739
|
+
image: defaulted(parsePerseusImageBackground, () => ({
|
|
47740
|
+
url: null,
|
|
47741
|
+
top: 0,
|
|
47742
|
+
left: 0
|
|
47743
|
+
})),
|
|
48659
47744
|
showProtractor: boolean,
|
|
48660
47745
|
showRuler: boolean,
|
|
48661
47746
|
rulerLabel: string,
|
|
@@ -48766,9 +47851,15 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
|
48766
47851
|
categories: array(string),
|
|
48767
47852
|
type: enumeration(...plotterPlotTypes),
|
|
48768
47853
|
maxY: number,
|
|
48769
|
-
scaleY
|
|
47854
|
+
// The default value for scaleY comes from plotter.tsx.
|
|
47855
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47856
|
+
// defaults here.
|
|
47857
|
+
scaleY: defaulted(number, () => 1),
|
|
48770
47858
|
labelInterval: optional(nullable(number)),
|
|
48771
|
-
snapsPerLine
|
|
47859
|
+
// The default value for snapsPerLine comes from plotter.tsx.
|
|
47860
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47861
|
+
// defaults here.
|
|
47862
|
+
snapsPerLine: defaulted(number, () => 2),
|
|
48772
47863
|
starting: array(number),
|
|
48773
47864
|
correct: array(number),
|
|
48774
47865
|
picUrl: optional(nullable(string)),
|
|
@@ -48845,12 +47936,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
48845
47936
|
}
|
|
48846
47937
|
return ctx.success(widgetsMap);
|
|
48847
47938
|
};
|
|
48848
|
-
const parseWidgetsMapEntry = ([
|
|
48849
|
-
const
|
|
48850
|
-
if (isFailure(
|
|
48851
|
-
return
|
|
47939
|
+
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
47940
|
+
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
47941
|
+
if (isFailure(idComponentsResult)) {
|
|
47942
|
+
return idComponentsResult;
|
|
48852
47943
|
}
|
|
48853
|
-
const [type,
|
|
47944
|
+
const [type, n] = idComponentsResult.value;
|
|
48854
47945
|
function parseAndAssign(key, parse) {
|
|
48855
47946
|
const widgetResult = parse(widget, ctx);
|
|
48856
47947
|
if (isFailure(widgetResult)) {
|
|
@@ -48861,88 +47952,88 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
48861
47952
|
}
|
|
48862
47953
|
switch (type) {
|
|
48863
47954
|
case "categorizer":
|
|
48864
|
-
return parseAndAssign(`categorizer ${
|
|
47955
|
+
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
48865
47956
|
case "cs-program":
|
|
48866
|
-
return parseAndAssign(`cs-program ${
|
|
47957
|
+
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
48867
47958
|
case "definition":
|
|
48868
|
-
return parseAndAssign(`definition ${
|
|
47959
|
+
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
48869
47960
|
case "dropdown":
|
|
48870
|
-
return parseAndAssign(`dropdown ${
|
|
47961
|
+
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
48871
47962
|
case "explanation":
|
|
48872
|
-
return parseAndAssign(`explanation ${
|
|
47963
|
+
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
48873
47964
|
case "expression":
|
|
48874
|
-
return parseAndAssign(`expression ${
|
|
47965
|
+
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
48875
47966
|
case "grapher":
|
|
48876
|
-
return parseAndAssign(`grapher ${
|
|
47967
|
+
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
48877
47968
|
case "group":
|
|
48878
|
-
return parseAndAssign(`group ${
|
|
47969
|
+
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
48879
47970
|
case "graded-group":
|
|
48880
|
-
return parseAndAssign(`graded-group ${
|
|
47971
|
+
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
48881
47972
|
case "graded-group-set":
|
|
48882
|
-
return parseAndAssign(`graded-group-set ${
|
|
47973
|
+
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
48883
47974
|
case "iframe":
|
|
48884
|
-
return parseAndAssign(`iframe ${
|
|
47975
|
+
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
48885
47976
|
case "image":
|
|
48886
|
-
return parseAndAssign(`image ${
|
|
47977
|
+
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
48887
47978
|
case "input-number":
|
|
48888
|
-
return parseAndAssign(`input-number ${
|
|
47979
|
+
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
48889
47980
|
case "interaction":
|
|
48890
|
-
return parseAndAssign(`interaction ${
|
|
47981
|
+
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
48891
47982
|
case "interactive-graph":
|
|
48892
|
-
return parseAndAssign(`interactive-graph ${
|
|
47983
|
+
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
48893
47984
|
case "label-image":
|
|
48894
|
-
return parseAndAssign(`label-image ${
|
|
47985
|
+
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
48895
47986
|
case "matcher":
|
|
48896
|
-
return parseAndAssign(`matcher ${
|
|
47987
|
+
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
48897
47988
|
case "matrix":
|
|
48898
|
-
return parseAndAssign(`matrix ${
|
|
47989
|
+
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
48899
47990
|
case "measurer":
|
|
48900
|
-
return parseAndAssign(`measurer ${
|
|
47991
|
+
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
48901
47992
|
case "molecule-renderer":
|
|
48902
|
-
return parseAndAssign(`molecule-renderer ${
|
|
47993
|
+
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
48903
47994
|
case "number-line":
|
|
48904
|
-
return parseAndAssign(`number-line ${
|
|
47995
|
+
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
48905
47996
|
case "numeric-input":
|
|
48906
|
-
return parseAndAssign(`numeric-input ${
|
|
47997
|
+
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
48907
47998
|
case "orderer":
|
|
48908
|
-
return parseAndAssign(`orderer ${
|
|
47999
|
+
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
48909
48000
|
case "passage":
|
|
48910
|
-
return parseAndAssign(`passage ${
|
|
48001
|
+
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
48911
48002
|
case "passage-ref":
|
|
48912
|
-
return parseAndAssign(`passage-ref ${
|
|
48003
|
+
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
48913
48004
|
case "passage-ref-target":
|
|
48914
48005
|
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
48915
48006
|
// used in test content. See:
|
|
48916
48007
|
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
48917
|
-
return parseAndAssign(`passage-ref-target ${
|
|
48008
|
+
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
48918
48009
|
case "phet-simulation":
|
|
48919
|
-
return parseAndAssign(`phet-simulation ${
|
|
48010
|
+
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
48920
48011
|
case "plotter":
|
|
48921
|
-
return parseAndAssign(`plotter ${
|
|
48012
|
+
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
48922
48013
|
case "python-program":
|
|
48923
|
-
return parseAndAssign(`python-program ${
|
|
48014
|
+
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
48924
48015
|
case "radio":
|
|
48925
|
-
return parseAndAssign(`radio ${
|
|
48016
|
+
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
48926
48017
|
case "sorter":
|
|
48927
|
-
return parseAndAssign(`sorter ${
|
|
48018
|
+
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
48928
48019
|
case "table":
|
|
48929
|
-
return parseAndAssign(`table ${
|
|
48020
|
+
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
48930
48021
|
case "video":
|
|
48931
|
-
return parseAndAssign(`video ${
|
|
48022
|
+
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
48932
48023
|
case "sequence":
|
|
48933
48024
|
// sequence is a deprecated widget type, and the corresponding
|
|
48934
48025
|
// widget component no longer exists.
|
|
48935
|
-
return parseAndAssign(`sequence ${
|
|
48026
|
+
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
48936
48027
|
case "lights-puzzle":
|
|
48937
|
-
return parseAndAssign(`lights-puzzle ${
|
|
48028
|
+
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
48938
48029
|
case "simulator":
|
|
48939
|
-
return parseAndAssign(`simulator ${
|
|
48030
|
+
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
48940
48031
|
case "transformer":
|
|
48941
|
-
return parseAndAssign(`transformer ${
|
|
48032
|
+
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
48942
48033
|
default:
|
|
48943
48034
|
if (getWidget(type)) {
|
|
48944
48035
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
48945
|
-
return parseAndAssign(`${type} ${
|
|
48036
|
+
return parseAndAssign(`${type} ${n}`, any);
|
|
48946
48037
|
}
|
|
48947
48038
|
return ctx.failure("a valid widget type", type);
|
|
48948
48039
|
}
|
|
@@ -48954,11 +48045,32 @@ const parseDeprecatedWidget = parseWidget(
|
|
|
48954
48045
|
object({}));
|
|
48955
48046
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48956
48047
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48957
|
-
return ctx.failure("
|
|
48048
|
+
return ctx.failure("a string representing a positive integer", rawValue);
|
|
48958
48049
|
}
|
|
48959
48050
|
return ctx.success(+rawValue);
|
|
48960
48051
|
};
|
|
48961
|
-
const
|
|
48052
|
+
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
48053
|
+
|
|
48054
|
+
const parsePerseusRenderer = defaulted(object({
|
|
48055
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
48056
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
48057
|
+
content: defaulted(string, () => ""),
|
|
48058
|
+
// This module has an import cycle with parseWidgetsMap, because the
|
|
48059
|
+
// `group` widget can contain another renderer.
|
|
48060
|
+
// The anonymous function below ensures that we don't try to access
|
|
48061
|
+
// parseWidgetsMap before it's defined.
|
|
48062
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48063
|
+
metadata: optional(array(string)),
|
|
48064
|
+
images: parseImages
|
|
48065
|
+
}),
|
|
48066
|
+
// Default value
|
|
48067
|
+
() => ({
|
|
48068
|
+
content: "",
|
|
48069
|
+
widgets: {},
|
|
48070
|
+
images: {}
|
|
48071
|
+
}));
|
|
48072
|
+
|
|
48073
|
+
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
48962
48074
|
|
|
48963
48075
|
const parseHint = object({
|
|
48964
48076
|
replace: optional(boolean),
|
|
@@ -48969,7 +48081,7 @@ const parseHint = object({
|
|
|
48969
48081
|
});
|
|
48970
48082
|
|
|
48971
48083
|
const _excluded = ["type", "options"];
|
|
48972
|
-
object({
|
|
48084
|
+
const parsePerseusItem$1 = object({
|
|
48973
48085
|
question: parsePerseusRenderer,
|
|
48974
48086
|
hints: defaulted(array(parseHint), () => []),
|
|
48975
48087
|
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
@@ -49004,6 +48116,7 @@ function migrateAnswerArea(rawValue, ctx) {
|
|
|
49004
48116
|
* Why not just use JSON.parse? We want:
|
|
49005
48117
|
* - To make sure types are correct
|
|
49006
48118
|
* - To give us a central place to validate/transform output if needed
|
|
48119
|
+
* @deprecated - use parseAndMigratePerseusItem instead
|
|
49007
48120
|
* @param {string} json - the stringified PerseusItem JSON
|
|
49008
48121
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
49009
48122
|
*/
|
|
@@ -49015,6 +48128,60 @@ function parsePerseusItem(json) {
|
|
|
49015
48128
|
}
|
|
49016
48129
|
throw new Error("Something went wrong.");
|
|
49017
48130
|
}
|
|
48131
|
+
/**
|
|
48132
|
+
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
48133
|
+
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
48134
|
+
* data.
|
|
48135
|
+
*
|
|
48136
|
+
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
48137
|
+
* failure, it will contain an error message describing where in the tree
|
|
48138
|
+
* parsing failed.
|
|
48139
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48140
|
+
*/
|
|
48141
|
+
function parseAndMigratePerseusItem(json) {
|
|
48142
|
+
throwErrorIfCheatingDetected();
|
|
48143
|
+
const object = JSON.parse(json);
|
|
48144
|
+
const result = parse(object, parsePerseusItem$1);
|
|
48145
|
+
if (isFailure(result)) {
|
|
48146
|
+
return failure({
|
|
48147
|
+
message: result.detail,
|
|
48148
|
+
invalidObject: object
|
|
48149
|
+
});
|
|
48150
|
+
}
|
|
48151
|
+
return result;
|
|
48152
|
+
}
|
|
48153
|
+
|
|
48154
|
+
/**
|
|
48155
|
+
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
48156
|
+
* latest schema, and runtime-typechecks the result.
|
|
48157
|
+
*
|
|
48158
|
+
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
48159
|
+
* failure, it will contain an error message describing where in the tree
|
|
48160
|
+
* parsing failed.
|
|
48161
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48162
|
+
*/
|
|
48163
|
+
function parseAndMigratePerseusArticle(json) {
|
|
48164
|
+
throwErrorIfCheatingDetected();
|
|
48165
|
+
const object = JSON.parse(json);
|
|
48166
|
+
const result = parse(object, parsePerseusArticle);
|
|
48167
|
+
if (isFailure(result)) {
|
|
48168
|
+
return failure({
|
|
48169
|
+
message: result.detail,
|
|
48170
|
+
invalidObject: object
|
|
48171
|
+
});
|
|
48172
|
+
}
|
|
48173
|
+
return result;
|
|
48174
|
+
}
|
|
48175
|
+
|
|
48176
|
+
/**
|
|
48177
|
+
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
48178
|
+
*/
|
|
48179
|
+
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
48180
|
+
function throwErrorIfCheatingDetected() {
|
|
48181
|
+
if (!isRealJSONParse(JSON.parse)) {
|
|
48182
|
+
throw new Error("Something went wrong.");
|
|
48183
|
+
}
|
|
48184
|
+
}
|
|
49018
48185
|
|
|
49019
48186
|
const genericPerseusItemData = {
|
|
49020
48187
|
question: {
|
|
@@ -49234,5 +48401,5 @@ const ScoringUtil = {
|
|
|
49234
48401
|
* Types
|
|
49235
48402
|
*/
|
|
49236
48403
|
|
|
49237
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget,
|
|
48404
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49238
48405
|
//# sourceMappingURL=index.js.map
|