@khanacademy/perseus 49.2.2 → 50.1.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 +573 -1354
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +19 -1
- 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 +631 -1415
- 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 +19 -0
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +11 -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 +4 -5
- package/dist/widgets/categorizer/categorizer.util.d.ts +17 -0
- 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 +3 -2
- package/package.json +47 -46
- 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/index.js
CHANGED
|
@@ -13,6 +13,7 @@ var classNames$1 = require('classnames');
|
|
|
13
13
|
var React = require('react');
|
|
14
14
|
var ReactDOM = require('react-dom');
|
|
15
15
|
var _ = require('underscore');
|
|
16
|
+
var perseusScore = require('@khanacademy/perseus-score');
|
|
16
17
|
var Clickable = require('@khanacademy/wonder-blocks-clickable');
|
|
17
18
|
var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
|
|
18
19
|
var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
@@ -20,9 +21,9 @@ var $ = require('jquery');
|
|
|
20
21
|
var uuid = require('uuid');
|
|
21
22
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
22
23
|
var PropTypes = require('prop-types');
|
|
23
|
-
var kmath = require('@khanacademy/kmath');
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
26
|
+
var kmath = require('@khanacademy/kmath');
|
|
26
27
|
var SimpleMarkdown = require('@khanacademy/simple-markdown');
|
|
27
28
|
var pureMarkdown = require('@khanacademy/pure-markdown');
|
|
28
29
|
var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
|
|
@@ -108,6 +109,183 @@ function _extends() {
|
|
|
108
109
|
* The translated strings that are used to render Perseus.
|
|
109
110
|
*/
|
|
110
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Untranslated strings used in Perseus. To be used by an external
|
|
114
|
+
* translator to produce translated strings, passed in as `PerseusStrings`.
|
|
115
|
+
* !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
|
|
116
|
+
*/
|
|
117
|
+
const strings = {
|
|
118
|
+
closeKeypad: "close math keypad",
|
|
119
|
+
openKeypad: "open math keypad",
|
|
120
|
+
mathInputBox: "Math input box",
|
|
121
|
+
removeHighlight: "Remove highlight",
|
|
122
|
+
addHighlight: "Add highlight",
|
|
123
|
+
hintPos: "Hint #%(pos)s",
|
|
124
|
+
errorRendering: "Error rendering: %(error)s",
|
|
125
|
+
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",
|
|
126
|
+
EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
|
|
127
|
+
NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
|
|
128
|
+
MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
|
|
129
|
+
MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
|
|
130
|
+
WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
|
|
131
|
+
WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
|
|
132
|
+
invalidSelection: "Make sure you select something for every row.",
|
|
133
|
+
ERROR_TITLE: "Oops!",
|
|
134
|
+
ERROR_MESSAGE: "Sorry, I don't understand that!",
|
|
135
|
+
hints: "Hints",
|
|
136
|
+
getAnotherHint: "Get another hint",
|
|
137
|
+
deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
|
|
138
|
+
keepTrying: "Keep trying",
|
|
139
|
+
tryAgain: "Try again",
|
|
140
|
+
check: "Check",
|
|
141
|
+
correctExcited: "Correct!",
|
|
142
|
+
nextQuestion: "Next question",
|
|
143
|
+
skipToTitle: "Skip to %(title)s",
|
|
144
|
+
current: "Current",
|
|
145
|
+
correct: "Correct",
|
|
146
|
+
correctSelected: "Correct (selected)",
|
|
147
|
+
correctCrossedOut: "Correct (but you crossed it out)",
|
|
148
|
+
incorrect: "Incorrect",
|
|
149
|
+
incorrectSelected: "Incorrect (selected)",
|
|
150
|
+
hideExplanation: "Hide explanation",
|
|
151
|
+
explain: "Explain",
|
|
152
|
+
INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
|
|
153
|
+
DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
|
|
154
|
+
DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
|
|
155
|
+
integerExample: "an integer, like $6$",
|
|
156
|
+
properExample: "a *proper* fraction, like $1/2$ or $6/10$",
|
|
157
|
+
simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
|
|
158
|
+
improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
|
|
159
|
+
simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
|
|
160
|
+
mixedExample: "a mixed number, like $1\\\\ 3/4$",
|
|
161
|
+
decimalExample: "an *exact* decimal, like $0.75$",
|
|
162
|
+
percentExample: "a percent, like $12.34\\\\%$",
|
|
163
|
+
piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
|
|
164
|
+
yourAnswer: "**Your answer should be** ",
|
|
165
|
+
yourAnswerLabel: "Your answer:",
|
|
166
|
+
addPoints: "Click to add points",
|
|
167
|
+
addVertices: "Click to add vertices",
|
|
168
|
+
tapMultiple: "Tap each dot on the image to select all answers that apply.",
|
|
169
|
+
tapSingle: "Tap each dot on the image to select an answer.",
|
|
170
|
+
clickMultiple: "Click each dot on the image to select all answers that apply.",
|
|
171
|
+
clickSingle: "Click each dot on the image to select an answer.",
|
|
172
|
+
choices: "Choices:",
|
|
173
|
+
answers: {
|
|
174
|
+
one: "%(num)s answer",
|
|
175
|
+
other: "%(num)s answers"
|
|
176
|
+
},
|
|
177
|
+
hideAnswersToggleLabel: "Hide answer choices",
|
|
178
|
+
moves: {
|
|
179
|
+
one: "Moves: %(num)s",
|
|
180
|
+
other: "Moves: %(num)s"
|
|
181
|
+
},
|
|
182
|
+
clickTiles: "Click on the tiles to change the lights.",
|
|
183
|
+
turnOffLights: "You must turn on all of the lights to continue.",
|
|
184
|
+
fillAllCells: "Make sure you fill in all cells in the matrix.",
|
|
185
|
+
molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
|
|
186
|
+
switchDirection: "Switch direction",
|
|
187
|
+
circleOpen: "Make circle open",
|
|
188
|
+
circleFilled: "Make circle filled",
|
|
189
|
+
numDivisions: "Number of divisions:",
|
|
190
|
+
divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
|
|
191
|
+
lineRange: "lines %(lineRange)s",
|
|
192
|
+
lineNumber: "line %(lineNumber)s",
|
|
193
|
+
symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
|
|
194
|
+
symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
|
|
195
|
+
lineLabel: {
|
|
196
|
+
context: "a label next to a reading passage to denote the line number",
|
|
197
|
+
message: "Line"
|
|
198
|
+
},
|
|
199
|
+
beginningPassage: "Beginning of reading passage.",
|
|
200
|
+
beginningFootnotes: "Beginning of reading passage footnotes.",
|
|
201
|
+
endPassage: "End of reading passage.",
|
|
202
|
+
questionMarker: "[Marker for question %(number)s]",
|
|
203
|
+
circleMarker: "[Circle marker %(number)s]",
|
|
204
|
+
sentenceMarker: "[Sentence %(number)s]",
|
|
205
|
+
dragHandles: "Drag handles to make graph",
|
|
206
|
+
tapAddPoints: "Tap to add points",
|
|
207
|
+
false: "False",
|
|
208
|
+
true: "True",
|
|
209
|
+
no: "No",
|
|
210
|
+
yes: "Yes",
|
|
211
|
+
chooseCorrectNum: "Please choose the correct number of answers.",
|
|
212
|
+
notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
|
|
213
|
+
noneOfTheAbove: "None of the above",
|
|
214
|
+
chooseNumAnswers: "Choose %(numCorrect)s answers:",
|
|
215
|
+
chooseAllAnswers: "Choose all answers that apply:",
|
|
216
|
+
chooseOneAnswer: "Choose 1 answer:",
|
|
217
|
+
choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
|
|
218
|
+
choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
|
|
219
|
+
choiceCorrect: "(Choice %(letter)s, Correct)",
|
|
220
|
+
choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
|
|
221
|
+
choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
|
|
222
|
+
choiceIncorrect: "(Choice %(letter)s, Incorrect)",
|
|
223
|
+
choiceChecked: "(Choice %(letter)s, Checked)",
|
|
224
|
+
choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
|
|
225
|
+
choice: "(Choice %(letter)s)",
|
|
226
|
+
crossOut: "Cross out",
|
|
227
|
+
crossOutOption: "Cross out option",
|
|
228
|
+
crossOutChoice: "Cross out Choice %(letter)s",
|
|
229
|
+
bringBack: "Bring back",
|
|
230
|
+
openMenuForChoice: "Open menu for Choice %(letter)s",
|
|
231
|
+
letters: {
|
|
232
|
+
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.",
|
|
233
|
+
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"
|
|
234
|
+
},
|
|
235
|
+
rightArrow: "Reaction arrow pointing to the right.",
|
|
236
|
+
dontUnderstandUnits: "I couldn't understand those units.",
|
|
237
|
+
checkSigFigs: "Check your significant figures.",
|
|
238
|
+
answerNumericallyIncorrect: "That answer is numerically incorrect.",
|
|
239
|
+
checkUnits: "Check your units.",
|
|
240
|
+
dontUnderstand: "I don't understand that",
|
|
241
|
+
loading: "Loading...",
|
|
242
|
+
videoTranscript: "See video transcript",
|
|
243
|
+
somethingWrong: "Something went wrong.",
|
|
244
|
+
videoWrapper: "Khan Academy video wrapper",
|
|
245
|
+
mathInputTitle: "mathematics keyboard",
|
|
246
|
+
mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
|
|
247
|
+
sin: "sin",
|
|
248
|
+
cos: "cos",
|
|
249
|
+
tan: "tan",
|
|
250
|
+
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
251
|
+
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
252
|
+
selectAnAnswer: "Select an answer",
|
|
253
|
+
// The following strings are used for interactive graph SR descriptions.
|
|
254
|
+
addPoint: "Add Point",
|
|
255
|
+
removePoint: "Remove Point",
|
|
256
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
257
|
+
closePolygon: "Close shape",
|
|
258
|
+
openPolygon: "Re-open shape",
|
|
259
|
+
srInteractiveElements: "Interactive elements: %(elements)s",
|
|
260
|
+
srNoInteractiveElements: "No interactive elements",
|
|
261
|
+
// TODO(LEMS-2660): The following strings are ones that will need
|
|
262
|
+
// translation tickets after all interactive graph SR strings have
|
|
263
|
+
// been finalized. Remove this comment after the tickets have been
|
|
264
|
+
// created.
|
|
265
|
+
srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
|
|
266
|
+
srCircleGraph: "A circle on a coordinate plane.",
|
|
267
|
+
srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
|
|
268
|
+
srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
|
|
269
|
+
srCircleRadius: "Circle radius is %(radius)s.",
|
|
270
|
+
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.",
|
|
271
|
+
srLinearGraph: "A line on a coordinate plane.",
|
|
272
|
+
srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
273
|
+
srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
|
|
274
|
+
srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
|
|
275
|
+
srLinearGraphSlopeHorizontal: "Its slope is zero.",
|
|
276
|
+
srLinearGraphSlopeVertical: "Its slope is undefined.",
|
|
277
|
+
srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
|
|
278
|
+
srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
|
|
279
|
+
srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
|
|
280
|
+
srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
|
|
281
|
+
srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
282
|
+
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
283
|
+
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
284
|
+
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
285
|
+
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"
|
|
286
|
+
// The above strings are used for interactive graph SR descriptions.
|
|
287
|
+
};
|
|
288
|
+
|
|
111
289
|
/**
|
|
112
290
|
* Mock strings for the Perseus package, to be used for tests and Storybook.
|
|
113
291
|
*/
|
|
@@ -497,6 +675,22 @@ const mockStrings = {
|
|
|
497
675
|
// The above strings are used for interactive graph SR descriptions.
|
|
498
676
|
};
|
|
499
677
|
|
|
678
|
+
const errorToString = {
|
|
679
|
+
[perseusScore.ErrorCodes.MISSING_PERCENT_ERROR]: strings.MISSING_PERCENT_ERROR,
|
|
680
|
+
[perseusScore.ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR]: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
681
|
+
[perseusScore.ErrorCodes.APPROXIMATED_PI_ERROR]: strings.APPROXIMATED_PI_ERROR,
|
|
682
|
+
[perseusScore.ErrorCodes.EXTRA_SYMBOLS_ERROR]: strings.EXTRA_SYMBOLS_ERROR,
|
|
683
|
+
[perseusScore.ErrorCodes.WRONG_CASE_ERROR]: strings.WRONG_CASE_ERROR,
|
|
684
|
+
[perseusScore.ErrorCodes.WRONG_LETTER_ERROR]: strings.WRONG_LETTER_ERROR,
|
|
685
|
+
[perseusScore.ErrorCodes.MULTIPLICATION_SIGN_ERROR]: strings.MULTIPLICATION_SIGN_ERROR
|
|
686
|
+
};
|
|
687
|
+
function mapErrorToString(err) {
|
|
688
|
+
if (!err) {
|
|
689
|
+
return err;
|
|
690
|
+
}
|
|
691
|
+
return errorToString[err] || err;
|
|
692
|
+
}
|
|
693
|
+
|
|
500
694
|
/**
|
|
501
695
|
* PerseusI18nContext provides a way to set the strings and locale that are
|
|
502
696
|
* used inside the Perseus package.
|
|
@@ -1464,918 +1658,6 @@ const Log = {
|
|
|
1464
1658
|
}
|
|
1465
1659
|
};
|
|
1466
1660
|
|
|
1467
|
-
const KhanMath = {
|
|
1468
|
-
// Simplify formulas before display
|
|
1469
|
-
cleanMath: function (expr) {
|
|
1470
|
-
return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
|
|
1471
|
-
},
|
|
1472
|
-
// Bound a number by 1e-6 and 1e20 to avoid exponents after toString
|
|
1473
|
-
bound: function (num) {
|
|
1474
|
-
if (num === 0) {
|
|
1475
|
-
return num;
|
|
1476
|
-
}
|
|
1477
|
-
if (num < 0) {
|
|
1478
|
-
return -KhanMath.bound(-num);
|
|
1479
|
-
}
|
|
1480
|
-
return Math.max(1e-6, Math.min(num, 1e20));
|
|
1481
|
-
},
|
|
1482
|
-
factorial: function (x) {
|
|
1483
|
-
if (x <= 1) {
|
|
1484
|
-
return x;
|
|
1485
|
-
}
|
|
1486
|
-
return x * KhanMath.factorial(x - 1);
|
|
1487
|
-
},
|
|
1488
|
-
getGCD: function (a, b) {
|
|
1489
|
-
if (arguments.length > 2) {
|
|
1490
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1491
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1492
|
-
const rest = [].slice.call(arguments, 1);
|
|
1493
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1494
|
-
return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
|
|
1495
|
-
}
|
|
1496
|
-
let mod;
|
|
1497
|
-
a = Math.abs(a);
|
|
1498
|
-
b = Math.abs(b);
|
|
1499
|
-
while (b) {
|
|
1500
|
-
mod = a % b;
|
|
1501
|
-
a = b;
|
|
1502
|
-
b = mod;
|
|
1503
|
-
}
|
|
1504
|
-
return a;
|
|
1505
|
-
},
|
|
1506
|
-
getLCM: function (a, b) {
|
|
1507
|
-
if (arguments.length > 2) {
|
|
1508
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1509
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1510
|
-
const rest = [].slice.call(arguments, 1);
|
|
1511
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1512
|
-
return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
|
|
1513
|
-
}
|
|
1514
|
-
return Math.abs(a * b) / KhanMath.getGCD(a, b);
|
|
1515
|
-
},
|
|
1516
|
-
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],
|
|
1517
|
-
isPrime: function (n) {
|
|
1518
|
-
if (n <= 1) {
|
|
1519
|
-
return false;
|
|
1520
|
-
}
|
|
1521
|
-
if (n < 101) {
|
|
1522
|
-
return !!$__default["default"].grep(KhanMath.primes, function (p, i) {
|
|
1523
|
-
return Math.abs(p - n) <= 0.5;
|
|
1524
|
-
}).length;
|
|
1525
|
-
}
|
|
1526
|
-
if (n <= 1 || n > 2 && n % 2 === 0) {
|
|
1527
|
-
return false;
|
|
1528
|
-
}
|
|
1529
|
-
for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
|
|
1530
|
-
if (n % i === 0) {
|
|
1531
|
-
return false;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
return true;
|
|
1535
|
-
},
|
|
1536
|
-
// @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
|
|
1537
|
-
getPrimeFactorization: function (number) {
|
|
1538
|
-
if (number === 1) {
|
|
1539
|
-
return [];
|
|
1540
|
-
}
|
|
1541
|
-
if (KhanMath.isPrime(number)) {
|
|
1542
|
-
return [number];
|
|
1543
|
-
}
|
|
1544
|
-
const maxf = Math.sqrt(number);
|
|
1545
|
-
for (let f = 2; f <= maxf; f++) {
|
|
1546
|
-
if (number % f === 0) {
|
|
1547
|
-
return $__default["default"].merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
},
|
|
1551
|
-
// Round a number to the nearest increment
|
|
1552
|
-
// E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
|
|
1553
|
-
// num = 45, return 60.
|
|
1554
|
-
roundToNearest: function (increment, num) {
|
|
1555
|
-
return Math.round(num / increment) * increment;
|
|
1556
|
-
},
|
|
1557
|
-
// Round a number to a certain number of decimal places
|
|
1558
|
-
roundTo: function (precision, num) {
|
|
1559
|
-
const factor = Math.pow(10, precision).toFixed(5);
|
|
1560
|
-
// @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.
|
|
1561
|
-
return Math.round((num * factor).toFixed(5)) / factor;
|
|
1562
|
-
},
|
|
1563
|
-
/**
|
|
1564
|
-
* Return a string of num rounded to a fixed precision decimal places,
|
|
1565
|
-
* with an approx symbol if num had to be rounded, and trailing 0s
|
|
1566
|
-
*/
|
|
1567
|
-
toFixedApprox: function (num, precision) {
|
|
1568
|
-
// TODO(aria): Make this locale-dependent
|
|
1569
|
-
const fixedStr = num.toFixed(precision);
|
|
1570
|
-
if (kmath.number.equal(+fixedStr, num)) {
|
|
1571
|
-
return fixedStr;
|
|
1572
|
-
}
|
|
1573
|
-
return "\\approx " + fixedStr;
|
|
1574
|
-
},
|
|
1575
|
-
/**
|
|
1576
|
-
* Return a string of num rounded to precision decimal places, with an
|
|
1577
|
-
* approx symbol if num had to be rounded, but no trailing 0s if it was
|
|
1578
|
-
* not rounded.
|
|
1579
|
-
*/
|
|
1580
|
-
roundToApprox: function (num, precision) {
|
|
1581
|
-
const fixed = KhanMath.roundTo(precision, num);
|
|
1582
|
-
if (kmath.number.equal(fixed, num)) {
|
|
1583
|
-
return String(fixed);
|
|
1584
|
-
}
|
|
1585
|
-
return KhanMath.toFixedApprox(num, precision);
|
|
1586
|
-
},
|
|
1587
|
-
// toFraction(4/8) => [1, 2]
|
|
1588
|
-
// toFraction(0.666) => [333, 500]
|
|
1589
|
-
// toFraction(0.666, 0.001) => [2, 3]
|
|
1590
|
-
//
|
|
1591
|
-
// tolerance can't be bigger than 1, sorry
|
|
1592
|
-
toFraction: function (decimal, tolerance) {
|
|
1593
|
-
if (tolerance == null) {
|
|
1594
|
-
tolerance = Math.pow(2, -46);
|
|
1595
|
-
}
|
|
1596
|
-
if (decimal < 0 || decimal > 1) {
|
|
1597
|
-
let fract = decimal % 1;
|
|
1598
|
-
fract += fract < 0 ? 1 : 0;
|
|
1599
|
-
const nd = KhanMath.toFraction(fract, tolerance);
|
|
1600
|
-
nd[0] += Math.round(decimal - fract) * nd[1];
|
|
1601
|
-
return nd;
|
|
1602
|
-
}
|
|
1603
|
-
if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
|
|
1604
|
-
return [Math.round(decimal), 1];
|
|
1605
|
-
}
|
|
1606
|
-
let loN = 0;
|
|
1607
|
-
let loD = 1;
|
|
1608
|
-
let hiN = 1;
|
|
1609
|
-
let hiD = 1;
|
|
1610
|
-
let midN = 1;
|
|
1611
|
-
let midD = 2;
|
|
1612
|
-
|
|
1613
|
-
// eslint-disable-next-line no-constant-condition
|
|
1614
|
-
while (true) {
|
|
1615
|
-
if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
|
|
1616
|
-
return [midN, midD];
|
|
1617
|
-
}
|
|
1618
|
-
if (midN / midD < decimal) {
|
|
1619
|
-
loN = midN;
|
|
1620
|
-
loD = midD;
|
|
1621
|
-
} else {
|
|
1622
|
-
hiN = midN;
|
|
1623
|
-
hiD = midD;
|
|
1624
|
-
}
|
|
1625
|
-
midN = loN + hiN;
|
|
1626
|
-
midD = loD + hiD;
|
|
1627
|
-
}
|
|
1628
|
-
},
|
|
1629
|
-
// Returns the format (string) of a given numeric string
|
|
1630
|
-
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
1631
|
-
// That is, it is not necessarily true that interpreted input are numeric
|
|
1632
|
-
getNumericFormat: function (text) {
|
|
1633
|
-
text = $__default["default"].trim(text);
|
|
1634
|
-
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
1635
|
-
if (text.match(/^[+-]?\d+$/)) {
|
|
1636
|
-
return "integer";
|
|
1637
|
-
}
|
|
1638
|
-
if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
|
|
1639
|
-
return "mixed";
|
|
1640
|
-
}
|
|
1641
|
-
const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
|
|
1642
|
-
if (fraction) {
|
|
1643
|
-
return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
|
|
1644
|
-
}
|
|
1645
|
-
if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
|
|
1646
|
-
return "decimal";
|
|
1647
|
-
}
|
|
1648
|
-
if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
|
|
1649
|
-
return "pi";
|
|
1650
|
-
}
|
|
1651
|
-
return null;
|
|
1652
|
-
},
|
|
1653
|
-
// Returns a string of the number in a specified format
|
|
1654
|
-
toNumericString: function (number, format) {
|
|
1655
|
-
if (number == null) {
|
|
1656
|
-
return "";
|
|
1657
|
-
}
|
|
1658
|
-
if (number === 0) {
|
|
1659
|
-
return "0"; // otherwise it might end up as 0% or 0pi
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
if (format === "percent") {
|
|
1663
|
-
return number * 100 + "%";
|
|
1664
|
-
}
|
|
1665
|
-
if (format === "pi") {
|
|
1666
|
-
const fraction = kmath.number.toFraction(number / Math.PI);
|
|
1667
|
-
const numerator = Math.abs(fraction[0]);
|
|
1668
|
-
const denominator = fraction[1];
|
|
1669
|
-
if (kmath.number.isInteger(numerator)) {
|
|
1670
|
-
const sign = number < 0 ? "-" : "";
|
|
1671
|
-
const pi = "\u03C0";
|
|
1672
|
-
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
if (___default["default"](["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
1676
|
-
const fraction = kmath.number.toFraction(number);
|
|
1677
|
-
const numerator = Math.abs(fraction[0]);
|
|
1678
|
-
const denominator = fraction[1];
|
|
1679
|
-
const sign = number < 0 ? "-" : "";
|
|
1680
|
-
if (denominator === 1) {
|
|
1681
|
-
return sign + numerator; // for integers, irrational, d > 1000
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
if (format === "mixed") {
|
|
1685
|
-
const modulus = numerator % denominator;
|
|
1686
|
-
const integer = (numerator - modulus) / denominator;
|
|
1687
|
-
return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
|
|
1688
|
-
} // otherwise proper, improper, or fraction
|
|
1689
|
-
return sign + numerator + "/" + denominator;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
// otherwise (decimal, float, long long)
|
|
1693
|
-
return String(number);
|
|
1694
|
-
}
|
|
1695
|
-
};
|
|
1696
|
-
function sum$1(array) {
|
|
1697
|
-
return array.reduce(add$3, 0);
|
|
1698
|
-
}
|
|
1699
|
-
function add$3(a, b) {
|
|
1700
|
-
return a + b;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
/* eslint-disable no-useless-escape */
|
|
1704
|
-
const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
1705
|
-
|
|
1706
|
-
// TOOD(kevinb): Figure out how this relates to KEScore in
|
|
1707
|
-
// perseus-all-package/types.js and see if there's a way to
|
|
1708
|
-
// unify these types.
|
|
1709
|
-
/**
|
|
1710
|
-
* Answer types
|
|
1711
|
-
*
|
|
1712
|
-
* Utility for creating answerable questions displayed in exercises
|
|
1713
|
-
*
|
|
1714
|
-
* Different answer types produce different kinds of input displays, and do
|
|
1715
|
-
* different kinds of checking on the solutions.
|
|
1716
|
-
*
|
|
1717
|
-
* Each of the objects contain two functions, setup and createValidator.
|
|
1718
|
-
*
|
|
1719
|
-
* The setup function takes a solutionarea and solution, and performs setup
|
|
1720
|
-
* within the solutionarea, and then returns an object which contains:
|
|
1721
|
-
*
|
|
1722
|
-
* answer: a function which, when called, will retrieve the current answer from
|
|
1723
|
-
* the solutionarea, which can then be validated using the validator
|
|
1724
|
-
* function
|
|
1725
|
-
* validator: a function returned from the createValidator function (defined
|
|
1726
|
-
* below)
|
|
1727
|
-
* solution: the correct answer to the problem
|
|
1728
|
-
* showGuess: a function which, when given a guess, shows the guess within the
|
|
1729
|
-
* provided solutionarea
|
|
1730
|
-
* showGuessCustom: a function which displays parts of a guess that are not
|
|
1731
|
-
* within the solutionarea; currently only used for custom
|
|
1732
|
-
* answers
|
|
1733
|
-
*
|
|
1734
|
-
* The createValidator function only takes a solution, and it returns a
|
|
1735
|
-
* function which can be used to validate an answer.
|
|
1736
|
-
*
|
|
1737
|
-
* The resulting validator function returns:
|
|
1738
|
-
* - true: if the answer is fully correct
|
|
1739
|
-
* - false: if the answer is incorrect
|
|
1740
|
-
* - "" (the empty string): if no answer has been provided (e.g. the answer box
|
|
1741
|
-
* is left unfilled)
|
|
1742
|
-
* - a string: if there is some slight error
|
|
1743
|
-
*
|
|
1744
|
-
* In most cases, setup and createValidator don't really need the solution DOM
|
|
1745
|
-
* element so we have setupFunctional and createValidatorFunctional for them
|
|
1746
|
-
* which take only $solution.text() and $solution.data(). This makes it easier
|
|
1747
|
-
* to reuse specific answer types.
|
|
1748
|
-
*
|
|
1749
|
-
* TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
|
|
1750
|
-
*
|
|
1751
|
-
*/
|
|
1752
|
-
const KhanAnswerTypes = {
|
|
1753
|
-
/*
|
|
1754
|
-
* predicate answer type
|
|
1755
|
-
*
|
|
1756
|
-
* performs simple predicate-based checking of a numeric solution, with
|
|
1757
|
-
* different kinds of number formats
|
|
1758
|
-
*
|
|
1759
|
-
* Uses the data-forms option on the solution to choose which number formats
|
|
1760
|
-
* are acceptable. Available data-forms:
|
|
1761
|
-
*
|
|
1762
|
-
* - integer: 3
|
|
1763
|
-
* - proper: 3/5
|
|
1764
|
-
* - improper: 5/3
|
|
1765
|
-
* - pi: 3 pi
|
|
1766
|
-
* - log: log(5)
|
|
1767
|
-
* - percent: 15%
|
|
1768
|
-
* - mixed: 1 1/3
|
|
1769
|
-
* - decimal: 1.7
|
|
1770
|
-
*
|
|
1771
|
-
* The solution should be a predicate of the form:
|
|
1772
|
-
*
|
|
1773
|
-
* function(guess, maxError) {
|
|
1774
|
-
* return abs(guess - 3) < maxError;
|
|
1775
|
-
* }
|
|
1776
|
-
*
|
|
1777
|
-
*/
|
|
1778
|
-
predicate: {
|
|
1779
|
-
defaultForms: "integer, proper, improper, mixed, decimal",
|
|
1780
|
-
createValidatorFunctional: function (predicate, options, strings) {
|
|
1781
|
-
// Extract the options from the given solution object
|
|
1782
|
-
options = ___default["default"].extend({
|
|
1783
|
-
simplify: "required",
|
|
1784
|
-
ratio: false,
|
|
1785
|
-
forms: KhanAnswerTypes.predicate.defaultForms
|
|
1786
|
-
}, options);
|
|
1787
|
-
let acceptableForms;
|
|
1788
|
-
// this is maintaining backwards compatibility
|
|
1789
|
-
// TODO(merlob) fix all places that depend on this, then delete
|
|
1790
|
-
if (!___default["default"].isArray(options.forms)) {
|
|
1791
|
-
acceptableForms = options.forms.split(/\s*,\s*/);
|
|
1792
|
-
} else {
|
|
1793
|
-
acceptableForms = options.forms;
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
// TODO(jack): remove options.inexact in favor of options.maxError
|
|
1797
|
-
if (options.inexact === undefined) {
|
|
1798
|
-
// If we aren't allowing inexact, ensure that we don't have a
|
|
1799
|
-
// large maxError as well.
|
|
1800
|
-
options.maxError = 0;
|
|
1801
|
-
}
|
|
1802
|
-
// Allow a small tolerance on maxError, to avoid numerical
|
|
1803
|
-
// representation issues (2.3 should be correct for a solution of
|
|
1804
|
-
// 2.45 with maxError=0.15).
|
|
1805
|
-
options.maxError = +options.maxError + MAXERROR_EPSILON;
|
|
1806
|
-
|
|
1807
|
-
// If percent is an acceptable form, make sure it's the last one
|
|
1808
|
-
// in the list so we don't prematurely complain about not having
|
|
1809
|
-
// a percent sign when the user entered the correct answer in a
|
|
1810
|
-
// different form (such as a decimal or fraction)
|
|
1811
|
-
if (___default["default"].contains(acceptableForms, "percent")) {
|
|
1812
|
-
acceptableForms = ___default["default"].without(acceptableForms, "percent");
|
|
1813
|
-
acceptableForms.push("percent");
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
// Take text looking like a fraction, and turn it into a number
|
|
1817
|
-
const fractionTransformer = function (text) {
|
|
1818
|
-
text = text
|
|
1819
|
-
// Replace unicode minus sign with hyphen
|
|
1820
|
-
.replace(/\u2212/, "-")
|
|
1821
|
-
// Remove space after +, -
|
|
1822
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1823
|
-
// Remove leading/trailing whitespace
|
|
1824
|
-
.replace(/(^\s*)|(\s*$)/gi, "");
|
|
1825
|
-
|
|
1826
|
-
// Extract numerator and denominator
|
|
1827
|
-
const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
|
|
1828
|
-
// Fractions are represented as "-\frac{numerator}{denominator}"
|
|
1829
|
-
// in Mobile device input instead of "numerator/denominator" as
|
|
1830
|
-
// in web-browser.
|
|
1831
|
-
const mobileDeviceMatch = text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);
|
|
1832
|
-
const parsedInt = parseInt(text, 10);
|
|
1833
|
-
if (match || mobileDeviceMatch) {
|
|
1834
|
-
let num;
|
|
1835
|
-
let denom;
|
|
1836
|
-
let simplified = true;
|
|
1837
|
-
if (match) {
|
|
1838
|
-
num = parseFloat(match[1]);
|
|
1839
|
-
denom = parseFloat(match[2]);
|
|
1840
|
-
} else {
|
|
1841
|
-
num = parseFloat(mobileDeviceMatch[2]);
|
|
1842
|
-
if (mobileDeviceMatch[1] === "-") {
|
|
1843
|
-
if (num < 0) {
|
|
1844
|
-
simplified = false;
|
|
1845
|
-
}
|
|
1846
|
-
num = -num;
|
|
1847
|
-
}
|
|
1848
|
-
denom = parseFloat(mobileDeviceMatch[3]);
|
|
1849
|
-
}
|
|
1850
|
-
simplified = simplified && denom > 0 && (options.ratio || denom !== 1) && KhanMath.getGCD(num, denom) === 1;
|
|
1851
|
-
return [{
|
|
1852
|
-
value: num / denom,
|
|
1853
|
-
exact: simplified
|
|
1854
|
-
}];
|
|
1855
|
-
}
|
|
1856
|
-
if (!isNaN(parsedInt) && "" + parsedInt === text) {
|
|
1857
|
-
return [{
|
|
1858
|
-
value: parsedInt,
|
|
1859
|
-
exact: true
|
|
1860
|
-
}];
|
|
1861
|
-
}
|
|
1862
|
-
return [];
|
|
1863
|
-
};
|
|
1864
|
-
|
|
1865
|
-
/*
|
|
1866
|
-
* Different forms of numbers
|
|
1867
|
-
*
|
|
1868
|
-
* Each function returns a list of objects of the form:
|
|
1869
|
-
*
|
|
1870
|
-
* {
|
|
1871
|
-
* value: numerical value,
|
|
1872
|
-
* exact: true/false
|
|
1873
|
-
* }
|
|
1874
|
-
*/
|
|
1875
|
-
const forms = {
|
|
1876
|
-
// integer, which is encompassed by decimal
|
|
1877
|
-
integer: function (text) {
|
|
1878
|
-
// Compare the decimal form to the decimal form rounded to
|
|
1879
|
-
// an integer. Only accept if the user actually entered an
|
|
1880
|
-
// integer.
|
|
1881
|
-
const decimal = forms.decimal(text);
|
|
1882
|
-
const rounded = forms.decimal(text, 1);
|
|
1883
|
-
if (decimal[0].value != null && decimal[0].value === rounded[0].value || decimal[1].value != null && decimal[1].value === rounded[1].value) {
|
|
1884
|
-
return decimal;
|
|
1885
|
-
}
|
|
1886
|
-
return [];
|
|
1887
|
-
},
|
|
1888
|
-
// A proper fraction
|
|
1889
|
-
proper: function (text) {
|
|
1890
|
-
return $__default["default"].map(fractionTransformer(text), function (o) {
|
|
1891
|
-
// All fractions that are less than 1
|
|
1892
|
-
if (Math.abs(o.value) < 1) {
|
|
1893
|
-
return [o];
|
|
1894
|
-
}
|
|
1895
|
-
return [];
|
|
1896
|
-
});
|
|
1897
|
-
},
|
|
1898
|
-
// an improper fraction
|
|
1899
|
-
improper: function (text) {
|
|
1900
|
-
return $__default["default"].map(fractionTransformer(text), function (o) {
|
|
1901
|
-
// All fractions that are greater than 1
|
|
1902
|
-
if (Math.abs(o.value) >= 1) {
|
|
1903
|
-
return [o];
|
|
1904
|
-
}
|
|
1905
|
-
return [];
|
|
1906
|
-
});
|
|
1907
|
-
},
|
|
1908
|
-
// pi-like numbers
|
|
1909
|
-
pi: function (text) {
|
|
1910
|
-
let match;
|
|
1911
|
-
let possibilities = [];
|
|
1912
|
-
|
|
1913
|
-
// Replace unicode minus sign with hyphen
|
|
1914
|
-
text = text.replace(/\u2212/, "-");
|
|
1915
|
-
|
|
1916
|
-
// - pi
|
|
1917
|
-
// (Note: we also support \pi (for TeX), p, tau (and \tau,
|
|
1918
|
-
// and t), pau.)
|
|
1919
|
-
if (match = text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1920
|
-
possibilities = [{
|
|
1921
|
-
value: parseFloat(match[1] + "1"),
|
|
1922
|
-
exact: true
|
|
1923
|
-
}];
|
|
1924
|
-
|
|
1925
|
-
// 5 / 6 pi
|
|
1926
|
-
} else if (match = text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1927
|
-
possibilities = fractionTransformer(match[1]);
|
|
1928
|
-
|
|
1929
|
-
// 4 5 / 6 pi
|
|
1930
|
-
} else if (match = text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1931
|
-
const sign = parseFloat(match[1] + "1");
|
|
1932
|
-
const integ = parseFloat(match[2]);
|
|
1933
|
-
const num = parseFloat(match[3]);
|
|
1934
|
-
const denom = parseFloat(match[4]);
|
|
1935
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
1936
|
-
possibilities = [{
|
|
1937
|
-
value: sign * (integ + num / denom),
|
|
1938
|
-
exact: simplified
|
|
1939
|
-
}];
|
|
1940
|
-
|
|
1941
|
-
// 5 pi / 6
|
|
1942
|
-
} else if (match = text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)) {
|
|
1943
|
-
possibilities = fractionTransformer(match[1] + "/" + match[3]);
|
|
1944
|
-
|
|
1945
|
-
// - pi / 4
|
|
1946
|
-
} else if (match = text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)) {
|
|
1947
|
-
possibilities = fractionTransformer(match[1] + "1/" + match[3]);
|
|
1948
|
-
|
|
1949
|
-
// 0
|
|
1950
|
-
} else if (text === "0") {
|
|
1951
|
-
possibilities = [{
|
|
1952
|
-
value: 0,
|
|
1953
|
-
exact: true
|
|
1954
|
-
}];
|
|
1955
|
-
|
|
1956
|
-
// 0.5 pi (fallback)
|
|
1957
|
-
} else if (match = text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1958
|
-
possibilities = forms.decimal(match[1]);
|
|
1959
|
-
} else {
|
|
1960
|
-
possibilities = ___default["default"].reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/), function (memo, form) {
|
|
1961
|
-
return memo.concat(forms[form](text));
|
|
1962
|
-
}, []);
|
|
1963
|
-
|
|
1964
|
-
// If the answer is a floating point number that's
|
|
1965
|
-
// near a multiple of pi, mark is as being possibly
|
|
1966
|
-
// an approximation of pi. We actually check if
|
|
1967
|
-
// it's a plausible approximation of pi/12, since
|
|
1968
|
-
// sometimes the correct answer is like pi/3 or pi/4.
|
|
1969
|
-
// We also say it's a pi-approximation if it involves
|
|
1970
|
-
// x/7 (since 22/7 is an approximation of pi.)
|
|
1971
|
-
// Never mark an integer as being an approximation
|
|
1972
|
-
// of pi.
|
|
1973
|
-
let approximatesPi = false;
|
|
1974
|
-
const number = parseFloat(text);
|
|
1975
|
-
if (!isNaN(number) && number !== parseInt(text)) {
|
|
1976
|
-
const piMult = Math.PI / 12;
|
|
1977
|
-
const roundedNumber = piMult * Math.round(number / piMult);
|
|
1978
|
-
if (Math.abs(number - roundedNumber) < 0.01) {
|
|
1979
|
-
approximatesPi = true;
|
|
1980
|
-
}
|
|
1981
|
-
} else if (text.match(/\/\s*7/)) {
|
|
1982
|
-
approximatesPi = true;
|
|
1983
|
-
}
|
|
1984
|
-
if (approximatesPi) {
|
|
1985
|
-
___default["default"].each(possibilities, function (possibility) {
|
|
1986
|
-
possibility.piApprox = true;
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
|
-
return possibilities;
|
|
1990
|
-
}
|
|
1991
|
-
let multiplier = Math.PI;
|
|
1992
|
-
if (text.match(/\\?tau|t|\u03c4/)) {
|
|
1993
|
-
multiplier = Math.PI * 2;
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// We're taking an early stand along side xkcd in the
|
|
1997
|
-
// inevitable ti vs. pau debate... http://xkcd.com/1292
|
|
1998
|
-
if (text.match(/pau/)) {
|
|
1999
|
-
multiplier = Math.PI * 1.5;
|
|
2000
|
-
}
|
|
2001
|
-
$__default["default"].each(possibilities, function (ix, possibility) {
|
|
2002
|
-
possibility.value *= multiplier;
|
|
2003
|
-
});
|
|
2004
|
-
return possibilities;
|
|
2005
|
-
},
|
|
2006
|
-
// Converts '' to 1 and '-' to -1 so you can write "[___] x"
|
|
2007
|
-
// and accept sane things
|
|
2008
|
-
coefficient: function (text) {
|
|
2009
|
-
let possibilities = [];
|
|
2010
|
-
|
|
2011
|
-
// Replace unicode minus sign with hyphen
|
|
2012
|
-
text = text.replace(/\u2212/, "-");
|
|
2013
|
-
if (text === "") {
|
|
2014
|
-
possibilities = [{
|
|
2015
|
-
value: 1,
|
|
2016
|
-
exact: true
|
|
2017
|
-
}];
|
|
2018
|
-
} else if (text === "-") {
|
|
2019
|
-
possibilities = [{
|
|
2020
|
-
value: -1,
|
|
2021
|
-
exact: true
|
|
2022
|
-
}];
|
|
2023
|
-
}
|
|
2024
|
-
return possibilities;
|
|
2025
|
-
},
|
|
2026
|
-
// simple log(c) form
|
|
2027
|
-
log: function (text) {
|
|
2028
|
-
let match;
|
|
2029
|
-
let possibilities = [];
|
|
2030
|
-
|
|
2031
|
-
// Replace unicode minus sign with hyphen
|
|
2032
|
-
text = text.replace(/\u2212/, "-");
|
|
2033
|
-
text = text.replace(/[ \(\)]/g, "");
|
|
2034
|
-
if (match = text.match(/^log\s*(\S+)\s*$/i)) {
|
|
2035
|
-
// @ts-expect-error - TS2322 - Type '{ value: number | undefined; exact: boolean; }[]' is not assignable to type 'never[]'.
|
|
2036
|
-
possibilities = forms.decimal(match[1]);
|
|
2037
|
-
} else if (text === "0") {
|
|
2038
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'. | TS2322 - Type 'boolean' is not assignable to type 'never'.
|
|
2039
|
-
possibilities = [{
|
|
2040
|
-
value: 0,
|
|
2041
|
-
exact: true
|
|
2042
|
-
}];
|
|
2043
|
-
}
|
|
2044
|
-
return possibilities;
|
|
2045
|
-
},
|
|
2046
|
-
// Numbers with percent signs
|
|
2047
|
-
percent: function (text) {
|
|
2048
|
-
text = $__default["default"].trim(text);
|
|
2049
|
-
// store whether or not there is a percent sign
|
|
2050
|
-
let hasPercentSign = false;
|
|
2051
|
-
if (text.indexOf("%") === text.length - 1) {
|
|
2052
|
-
text = $__default["default"].trim(text.substring(0, text.length - 1));
|
|
2053
|
-
hasPercentSign = true;
|
|
2054
|
-
}
|
|
2055
|
-
const transformed = forms.decimal(text);
|
|
2056
|
-
$__default["default"].each(transformed, function (ix, t) {
|
|
2057
|
-
t.exact = hasPercentSign;
|
|
2058
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
2059
|
-
t.value = t.value / 100;
|
|
2060
|
-
});
|
|
2061
|
-
return transformed;
|
|
2062
|
-
},
|
|
2063
|
-
// Mixed numbers, like 1 3/4
|
|
2064
|
-
mixed: function (text) {
|
|
2065
|
-
const match = text
|
|
2066
|
-
// Replace unicode minus sign with hyphen
|
|
2067
|
-
.replace(/\u2212/, "-")
|
|
2068
|
-
// Remove space after +, -
|
|
2069
|
-
.replace(/([+-])\s+/g, "$1")
|
|
2070
|
-
// Extract integer, numerator and denominator
|
|
2071
|
-
.match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
|
|
2072
|
-
if (match) {
|
|
2073
|
-
const sign = parseFloat(match[1] + "1");
|
|
2074
|
-
const integ = parseFloat(match[2]);
|
|
2075
|
-
const num = parseFloat(match[3]);
|
|
2076
|
-
const denom = parseFloat(match[4]);
|
|
2077
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
2078
|
-
return [{
|
|
2079
|
-
value: sign * (integ + num / denom),
|
|
2080
|
-
exact: simplified
|
|
2081
|
-
}];
|
|
2082
|
-
}
|
|
2083
|
-
return [];
|
|
2084
|
-
},
|
|
2085
|
-
// Decimal numbers -- compare entered text rounded to
|
|
2086
|
-
// 'precision' Reciprical of the precision against the correct
|
|
2087
|
-
// answer. We round to 1/1e10 by default, which is healthily
|
|
2088
|
-
// less than machine epsilon but should be more than any real
|
|
2089
|
-
// decimal answer would use. (The 'integer' answer type uses
|
|
2090
|
-
// precision == 1.)
|
|
2091
|
-
decimal: function (text) {
|
|
2092
|
-
let precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1e10;
|
|
2093
|
-
const normal = function (text) {
|
|
2094
|
-
text = $__default["default"].trim(text);
|
|
2095
|
-
const match = text
|
|
2096
|
-
// Replace unicode minus sign with hyphen
|
|
2097
|
-
.replace(/\u2212/, "-")
|
|
2098
|
-
// Remove space after +, -
|
|
2099
|
-
.replace(/([+-])\s+/g, "$1")
|
|
2100
|
-
// Extract integer, numerator and denominator. If
|
|
2101
|
-
// commas or spaces are used, they must be in the
|
|
2102
|
-
// "correct" places
|
|
2103
|
-
.match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);
|
|
2104
|
-
|
|
2105
|
-
// You can't start a number with `0,`, to prevent us
|
|
2106
|
-
// interpeting '0.342' as correct for '342'
|
|
2107
|
-
const badLeadingZero = text.match(/^0[0,]*,/);
|
|
2108
|
-
if (match && !badLeadingZero) {
|
|
2109
|
-
let x = parseFloat(match[1].replace(/[, ]/g, ""));
|
|
2110
|
-
if (options.inexact === undefined) {
|
|
2111
|
-
x = Math.round(x * precision) / precision;
|
|
2112
|
-
}
|
|
2113
|
-
return x;
|
|
2114
|
-
}
|
|
2115
|
-
};
|
|
2116
|
-
const commas = function (text) {
|
|
2117
|
-
text = text.replace(/([\.,])/g, function (_, c) {
|
|
2118
|
-
return c === "." ? "," : ".";
|
|
2119
|
-
});
|
|
2120
|
-
return normal(text);
|
|
2121
|
-
};
|
|
2122
|
-
return [{
|
|
2123
|
-
value: normal(text),
|
|
2124
|
-
exact: true
|
|
2125
|
-
}, {
|
|
2126
|
-
value: commas(text),
|
|
2127
|
-
exact: true
|
|
2128
|
-
}];
|
|
2129
|
-
}
|
|
2130
|
-
};
|
|
2131
|
-
|
|
2132
|
-
// validator function
|
|
2133
|
-
return function (guess) {
|
|
2134
|
-
// The fallback variable is used in place of the answer, if no
|
|
2135
|
-
// answer is provided (i.e. the field is left blank)
|
|
2136
|
-
const fallback = options.fallback != null ? "" + options.fallback : "";
|
|
2137
|
-
guess = $__default["default"].trim(guess) || fallback;
|
|
2138
|
-
const score = {
|
|
2139
|
-
empty: guess === "",
|
|
2140
|
-
correct: false,
|
|
2141
|
-
message: null,
|
|
2142
|
-
guess: guess
|
|
2143
|
-
};
|
|
2144
|
-
|
|
2145
|
-
// iterate over all the acceptable forms, and if one of the
|
|
2146
|
-
// answers is correct, return true
|
|
2147
|
-
$__default["default"].each(acceptableForms, function (i, form) {
|
|
2148
|
-
const transformed = forms[form](guess);
|
|
2149
|
-
for (let j = 0, l = transformed.length; j < l; j++) {
|
|
2150
|
-
const val = transformed[j].value;
|
|
2151
|
-
const exact = transformed[j].exact;
|
|
2152
|
-
const piApprox = transformed[j].piApprox;
|
|
2153
|
-
// If a string was returned, and it exactly matches,
|
|
2154
|
-
// return true
|
|
2155
|
-
if (predicate(val, options.maxError)) {
|
|
2156
|
-
// If the exact correct number was returned,
|
|
2157
|
-
// return true
|
|
2158
|
-
if (exact || options.simplify === "optional") {
|
|
2159
|
-
score.correct = true;
|
|
2160
|
-
score.message = options.message || null;
|
|
2161
|
-
// If the answer is correct, don't say it's
|
|
2162
|
-
// empty. This happens, for example, with the
|
|
2163
|
-
// coefficient type where guess === "" but is
|
|
2164
|
-
// interpreted as "1" which is correct.
|
|
2165
|
-
score.empty = false;
|
|
2166
|
-
} else if (form === "percent") {
|
|
2167
|
-
// Otherwise, an error was returned
|
|
2168
|
-
score.empty = true;
|
|
2169
|
-
score.message = strings.MISSING_PERCENT_ERROR;
|
|
2170
|
-
} else {
|
|
2171
|
-
if (options.simplify !== "enforced") {
|
|
2172
|
-
score.empty = true;
|
|
2173
|
-
}
|
|
2174
|
-
score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
|
|
2175
|
-
}
|
|
2176
|
-
// The return false below stops the looping of the
|
|
2177
|
-
// callback since predicate check succeeded.
|
|
2178
|
-
// No more forms to look to verify the user guess.
|
|
2179
|
-
return false;
|
|
2180
|
-
}
|
|
2181
|
-
if (piApprox && predicate(val, Math.abs(val * 0.001))) {
|
|
2182
|
-
score.empty = true;
|
|
2183
|
-
score.message = strings.APPROXIMATED_PI_ERROR;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
if (score.correct === false) {
|
|
2188
|
-
let interpretedGuess = false;
|
|
2189
|
-
___default["default"].each(forms, function (form) {
|
|
2190
|
-
const anyAreNaN = ___default["default"].any(form(guess), function (t) {
|
|
2191
|
-
return t.value != null && !___default["default"].isNaN(t.value);
|
|
2192
|
-
});
|
|
2193
|
-
if (anyAreNaN) {
|
|
2194
|
-
interpretedGuess = true;
|
|
2195
|
-
}
|
|
2196
|
-
});
|
|
2197
|
-
if (!interpretedGuess) {
|
|
2198
|
-
score.empty = true;
|
|
2199
|
-
score.message = strings.EXTRA_SYMBOLS_ERROR;
|
|
2200
|
-
return score;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
return score;
|
|
2204
|
-
};
|
|
2205
|
-
}
|
|
2206
|
-
},
|
|
2207
|
-
/*
|
|
2208
|
-
* number answer type
|
|
2209
|
-
*
|
|
2210
|
-
* wraps the predicate answer type to performs simple number-based checking
|
|
2211
|
-
* of a solution
|
|
2212
|
-
*/
|
|
2213
|
-
number: {
|
|
2214
|
-
convertToPredicate: function (correctAnswer, options) {
|
|
2215
|
-
const correctFloat = parseFloat(correctAnswer);
|
|
2216
|
-
return [function (guess, maxError) {
|
|
2217
|
-
return Math.abs(guess - correctFloat) < maxError;
|
|
2218
|
-
}, $__default["default"].extend({}, options, {
|
|
2219
|
-
type: "predicate"
|
|
2220
|
-
})];
|
|
2221
|
-
},
|
|
2222
|
-
createValidatorFunctional: function (correctAnswer, options, strings) {
|
|
2223
|
-
return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
2224
|
-
}
|
|
2225
|
-
},
|
|
2226
|
-
/*
|
|
2227
|
-
* The expression answer type parses a given expression or equation
|
|
2228
|
-
* and semantically compares it to the solution. In addition, instant
|
|
2229
|
-
* feedback is provided by rendering the last answer that fully parsed.
|
|
2230
|
-
*
|
|
2231
|
-
* Parsing options:
|
|
2232
|
-
* functions (e.g. data-functions="f g h")
|
|
2233
|
-
* A space or comma separated list of single-letter variables that
|
|
2234
|
-
* should be interpreted as functions. Case sensitive. "e" and "i"
|
|
2235
|
-
* are reserved.
|
|
2236
|
-
*
|
|
2237
|
-
* no functions specified: f(x+y) == fx + fy
|
|
2238
|
-
* with "f" as a function: f(x+y) != fx + fy
|
|
2239
|
-
*
|
|
2240
|
-
* Comparison options:
|
|
2241
|
-
* same-form (e.g. data-same-form)
|
|
2242
|
-
* If present, the answer must match the solution's structure in
|
|
2243
|
-
* addition to evaluating the same. Commutativity and excess negation
|
|
2244
|
-
* are ignored, but all other changes will trigger a rejection. Useful
|
|
2245
|
-
* for requiring a particular form of an equation, or if the answer
|
|
2246
|
-
* must be factored.
|
|
2247
|
-
*
|
|
2248
|
-
* example question: Factor x^2 + x - 2
|
|
2249
|
-
* example solution: (x-1)(x+2)
|
|
2250
|
-
* accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
|
|
2251
|
-
* rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
|
|
2252
|
-
* rejection message: Your answer is not in the correct form
|
|
2253
|
-
*
|
|
2254
|
-
* simplify (e.g. data-simplify)
|
|
2255
|
-
* If present, the answer must be fully expanded and simplified. Use
|
|
2256
|
-
* carefully - simplification is hard and there may be bugs, or you
|
|
2257
|
-
* might not agree on the definition of "simplified" used. You will
|
|
2258
|
-
* get an error if the provided solution is not itself fully expanded
|
|
2259
|
-
* and simplified.
|
|
2260
|
-
*
|
|
2261
|
-
* example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
|
|
2262
|
-
* example solution: x^31 / n
|
|
2263
|
-
* accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
|
|
2264
|
-
* rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
|
|
2265
|
-
* rejection message: Your answer is not fully expanded and simplified
|
|
2266
|
-
*
|
|
2267
|
-
* Rendering options:
|
|
2268
|
-
* times (e.g. data-times)
|
|
2269
|
-
* If present, explicit multiplication (such as between numbers) will
|
|
2270
|
-
* be rendered with a cross/x symbol (TeX: \times) instead of the usual
|
|
2271
|
-
* center dot (TeX: \cdot).
|
|
2272
|
-
*
|
|
2273
|
-
* normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
|
|
2274
|
-
* but with "times": 2 * 3^x -> 2 \times 3^{x}
|
|
2275
|
-
*/
|
|
2276
|
-
expression: {
|
|
2277
|
-
parseSolution: function (solutionString, options) {
|
|
2278
|
-
let solution = KAS__namespace.parse(solutionString, options);
|
|
2279
|
-
if (!solution.parsed) {
|
|
2280
|
-
throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") didn't parse.", perseusCore.Errors.InvalidInput);
|
|
2281
|
-
} else if (options.simplified && !solution.expr.isSimplified()) {
|
|
2282
|
-
throw new perseusCore.PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", perseusCore.Errors.InvalidInput);
|
|
2283
|
-
} else {
|
|
2284
|
-
solution = solution.expr;
|
|
2285
|
-
}
|
|
2286
|
-
return solution;
|
|
2287
|
-
},
|
|
2288
|
-
createValidatorFunctional: function (solution, options, strings) {
|
|
2289
|
-
return function (guess) {
|
|
2290
|
-
const score = {
|
|
2291
|
-
empty: false,
|
|
2292
|
-
correct: false,
|
|
2293
|
-
message: null,
|
|
2294
|
-
guess: guess,
|
|
2295
|
-
// Setting `ungraded` to true indicates that if the
|
|
2296
|
-
// guess doesn't match any of the solutions, the guess
|
|
2297
|
-
// shouldn't be marked as incorrect; instead, `message`
|
|
2298
|
-
// should be shown to the user. This is different from
|
|
2299
|
-
// setting `empty` to true, since the behavior of `empty`
|
|
2300
|
-
// is that `message` only will be shown if the guess is
|
|
2301
|
-
// graded as empty for every solution.
|
|
2302
|
-
ungraded: false
|
|
2303
|
-
};
|
|
2304
|
-
|
|
2305
|
-
// Don't bother parsing an empty input
|
|
2306
|
-
if (!guess) {
|
|
2307
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2308
|
-
score.empty = true;
|
|
2309
|
-
return score;
|
|
2310
|
-
}
|
|
2311
|
-
const answer = KAS__namespace.parse(guess, options);
|
|
2312
|
-
|
|
2313
|
-
// An unsuccessful parse doesn't count as wrong
|
|
2314
|
-
if (!answer.parsed) {
|
|
2315
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2316
|
-
score.empty = true;
|
|
2317
|
-
return score;
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
// Solution will need to be parsed again if we're creating
|
|
2321
|
-
// this from a multiple question type
|
|
2322
|
-
if (typeof solution === "string") {
|
|
2323
|
-
solution = KhanAnswerTypes.expression.parseSolution(solution, options);
|
|
2324
|
-
}
|
|
2325
|
-
const result = KAS__namespace.compare(answer.expr, solution, options);
|
|
2326
|
-
if (result.equal) {
|
|
2327
|
-
// Correct answer
|
|
2328
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'correct' because it is a read-only property.
|
|
2329
|
-
score.correct = true;
|
|
2330
|
-
} else if (result.wrongVariableNames || result.wrongVariableCase) {
|
|
2331
|
-
// We don't want to give people an error for getting the
|
|
2332
|
-
// variable names or the variable case wrong.
|
|
2333
|
-
// TODO(aasmund): This should ideally have been handled
|
|
2334
|
-
// under the `result.message` condition, but the
|
|
2335
|
-
// KAS messages currently aren't translatable.
|
|
2336
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2337
|
-
score.ungraded = true;
|
|
2338
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2339
|
-
score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
|
|
2340
|
-
// Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
|
|
2341
|
-
// @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; }'.
|
|
2342
|
-
score.suppressAlmostThere = true;
|
|
2343
|
-
} else if (result.message) {
|
|
2344
|
-
// Nearly correct answer
|
|
2345
|
-
// TODO(aasmund): This message also isn't translatable;
|
|
2346
|
-
// need to fix that in KAS
|
|
2347
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2348
|
-
score.message = result.message;
|
|
2349
|
-
} else {
|
|
2350
|
-
// Replace x with * and see if it would have been correct
|
|
2351
|
-
// TODO(aasmund): I think this branch is effectively dead,
|
|
2352
|
-
// because the replacement will only work in situations
|
|
2353
|
-
// where the variables are wrong (except if the variable
|
|
2354
|
-
// is x, in which case the replacement won't work either),
|
|
2355
|
-
// which is handled by another branch. When we implement a
|
|
2356
|
-
// more sophisticated variable check, revive this or
|
|
2357
|
-
// remove it completely if it will never come into play.
|
|
2358
|
-
const answerX = KAS__namespace.parse(guess.replace(/[xX]/g, "*"), options);
|
|
2359
|
-
if (answerX.parsed) {
|
|
2360
|
-
const resultX = KAS__namespace.compare(answerX.expr, solution, options);
|
|
2361
|
-
if (resultX.equal) {
|
|
2362
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2363
|
-
score.ungraded = true;
|
|
2364
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2365
|
-
score.message = strings.MULTIPLICATION_SIGN_ERROR;
|
|
2366
|
-
} else if (resultX.message) {
|
|
2367
|
-
// TODO(aasmund): I18nize `score.message`
|
|
2368
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2369
|
-
score.message = resultX.message + " Also, I'm a computer. I only understand " + "multiplication if you use an " + "asterisk (*) as the multiplication " + "sign.";
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
return score;
|
|
2374
|
-
};
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
};
|
|
2378
|
-
|
|
2379
1661
|
/**
|
|
2380
1662
|
* Checks user input from the expression widget to see if it is scorable.
|
|
2381
1663
|
*
|
|
@@ -2439,10 +1721,10 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2439
1721
|
});
|
|
2440
1722
|
return null;
|
|
2441
1723
|
}
|
|
2442
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
1724
|
+
return perseusScore.KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2443
1725
|
simplify: answer.simplify,
|
|
2444
1726
|
form: answer.form
|
|
2445
|
-
})
|
|
1727
|
+
}));
|
|
2446
1728
|
};
|
|
2447
1729
|
|
|
2448
1730
|
// Find the first answer form that matches the user's input and that
|
|
@@ -3108,14 +2390,14 @@ const split = "x".split(/(.)/g).length ? function (str, r) {
|
|
|
3108
2390
|
function firstNumericalParse$1(text, strings) {
|
|
3109
2391
|
// TODO(alpert): This is sort of hacky...
|
|
3110
2392
|
let first;
|
|
3111
|
-
const val = KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
|
|
2393
|
+
const val = perseusScore.KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
|
|
3112
2394
|
first = ans;
|
|
3113
2395
|
return true; /* break */
|
|
3114
2396
|
}, {
|
|
3115
2397
|
simplify: "optional",
|
|
3116
2398
|
inexact: true,
|
|
3117
2399
|
forms: "integer, proper, improper, pi, log, mixed, decimal"
|
|
3118
|
-
}
|
|
2400
|
+
});
|
|
3119
2401
|
val(text);
|
|
3120
2402
|
return first;
|
|
3121
2403
|
}
|
|
@@ -9128,7 +8410,7 @@ var Tex$1 = {
|
|
|
9128
8410
|
text = text != null ? text + "" : "";
|
|
9129
8411
|
|
|
9130
8412
|
// Attempt to clean up some of the math
|
|
9131
|
-
text = KhanMath.cleanMath(text);
|
|
8413
|
+
text = kmath.KhanMath.cleanMath(text);
|
|
9132
8414
|
|
|
9133
8415
|
// Store the formula that we're using
|
|
9134
8416
|
$elem.attr("data-math-formula", text);
|
|
@@ -9815,7 +9097,7 @@ class Graphie$1 {
|
|
|
9815
9097
|
return "z";
|
|
9816
9098
|
}
|
|
9817
9099
|
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
9818
|
-
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
9100
|
+
return (i === 0 ? "M" : "L") + kmath.KhanMath.bound(scaled[0]) + " " + kmath.KhanMath.bound(scaled[1]);
|
|
9819
9101
|
}).join("");
|
|
9820
9102
|
};
|
|
9821
9103
|
svgParabolaPath = (a, b, c) => {
|
|
@@ -9851,7 +9133,7 @@ class Graphie$1 {
|
|
|
9851
9133
|
|
|
9852
9134
|
// Scale and bound
|
|
9853
9135
|
const points = [left, control, right].map(this.scalePoint);
|
|
9854
|
-
const values = points.flat().map(KhanMath.bound);
|
|
9136
|
+
const values = points.flat().map(kmath.KhanMath.bound);
|
|
9855
9137
|
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
9856
9138
|
};
|
|
9857
9139
|
svgSinusoidPath = (a, b, c, d) => {
|
|
@@ -10901,7 +10183,7 @@ class Arrowhead$1 extends WrappedPath {
|
|
|
10901
10183
|
}
|
|
10902
10184
|
toCoordAtAngle(coord, angle) {
|
|
10903
10185
|
const clipPoint = this.graphie.scalePoint(getClipPoint(this.graphie, coord, angle));
|
|
10904
|
-
this.transform("translateX(" + (clipPoint[0] + Arrowhead$1.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead$1.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - KhanMath.bound(angle)) + "deg)");
|
|
10186
|
+
this.transform("translateX(" + (clipPoint[0] + Arrowhead$1.scale * this.center[0]) + "px) " + "translateY(" + (clipPoint[1] + Arrowhead$1.scale * this.center[1]) + "px) " + "translateZ(0) " + "rotate(" + (360 - kmath.KhanMath.bound(angle)) + "deg)");
|
|
10905
10187
|
}
|
|
10906
10188
|
}
|
|
10907
10189
|
|
|
@@ -11025,9 +10307,9 @@ class WrappedLine extends WrappedDrawing {
|
|
|
11025
10307
|
// Compute transformation parameters
|
|
11026
10308
|
const polarDiff = kmath.vector.polarDegFromCart(kmath.vector.subtract(scaledEnd, scaledStart));
|
|
11027
10309
|
const lineLength = polarDiff[0];
|
|
11028
|
-
const angle = KhanMath.bound(polarDiff[1]);
|
|
10310
|
+
const angle = kmath.KhanMath.bound(polarDiff[1]);
|
|
11029
10311
|
const delta = kmath.vector.subtract(scaledStart, this.initialPoint);
|
|
11030
|
-
const scale = KhanMath.bound(lineLength / this.initialLength);
|
|
10312
|
+
const scale = kmath.KhanMath.bound(lineLength / this.initialLength);
|
|
11031
10313
|
|
|
11032
10314
|
// Construct and apply transformation string
|
|
11033
10315
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
@@ -11133,7 +10415,7 @@ function clockwise$1(points) {
|
|
|
11133
10415
|
const p2 = segment[1];
|
|
11134
10416
|
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
11135
10417
|
});
|
|
11136
|
-
return sum
|
|
10418
|
+
return kmath.sum(areas) > 0;
|
|
11137
10419
|
}
|
|
11138
10420
|
function magnitude(v) {
|
|
11139
10421
|
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
@@ -11380,7 +10662,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11380
10662
|
const match = text.match(/\$deg(\d)?/);
|
|
11381
10663
|
if (match) {
|
|
11382
10664
|
const precision = match[1] || 1;
|
|
11383
|
-
text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
10665
|
+
text = text.replace(match[0], kmath.KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
11384
10666
|
}
|
|
11385
10667
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
11386
10668
|
const sPosition = kmath.vector.add(sVertex, sOffset);
|
|
@@ -11473,7 +10755,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11473
10755
|
if (match) {
|
|
11474
10756
|
const distance = GraphUtils.getDistance(p1, p2);
|
|
11475
10757
|
const precision = match[1] || 1;
|
|
11476
|
-
text = text.replace(match[0], KhanMath.toFixedApprox(distance, precision));
|
|
10758
|
+
text = text.replace(match[0], kmath.KhanMath.toFixedApprox(distance, precision));
|
|
11477
10759
|
}
|
|
11478
10760
|
const sOffset = 20;
|
|
11479
10761
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
@@ -11696,8 +10978,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11696
10978
|
// convert back to coordinates relative to graphie canvas
|
|
11697
10979
|
mouseX = mouseXrel + centerX;
|
|
11698
10980
|
mouseY = -mouseYrel + centerY;
|
|
11699
|
-
coordX = KhanMath.roundTo(5, mouseX / graph.scale[0] + graph.range[0][0]);
|
|
11700
|
-
coordY = KhanMath.roundTo(5, graph.range[1][1] - mouseY / graph.scale[1]);
|
|
10981
|
+
coordX = kmath.KhanMath.roundTo(5, mouseX / graph.scale[0] + graph.range[0][0]);
|
|
10982
|
+
coordY = kmath.KhanMath.roundTo(5, graph.range[1][1] - mouseY / graph.scale[1]);
|
|
11701
10983
|
}
|
|
11702
10984
|
const result = movablePoint.applyConstraint([coordX, coordY]);
|
|
11703
10985
|
return result;
|
|
@@ -14869,54 +14151,6 @@ var MovableLineOptions = {
|
|
|
14869
14151
|
}
|
|
14870
14152
|
};
|
|
14871
14153
|
|
|
14872
|
-
/**
|
|
14873
|
-
* A work-in-progress of _ methods for objects.
|
|
14874
|
-
* That is, they take an object as a parameter,
|
|
14875
|
-
* and return an object instead of an array.
|
|
14876
|
-
*
|
|
14877
|
-
* TODO(aria): Move this out of interactive2
|
|
14878
|
-
*/
|
|
14879
|
-
|
|
14880
|
-
/**
|
|
14881
|
-
* Does a pluck on keys inside objects in an object
|
|
14882
|
-
*
|
|
14883
|
-
* Ex:
|
|
14884
|
-
* tools = {
|
|
14885
|
-
* translation: {
|
|
14886
|
-
* enabled: true
|
|
14887
|
-
* },
|
|
14888
|
-
* rotation: {
|
|
14889
|
-
* enabled: false
|
|
14890
|
-
* }
|
|
14891
|
-
* };
|
|
14892
|
-
* pluckObject(tools, "enabled") returns {
|
|
14893
|
-
* translation: true
|
|
14894
|
-
* rotation: false
|
|
14895
|
-
* }
|
|
14896
|
-
*/
|
|
14897
|
-
const pluck = function (table, subKey) {
|
|
14898
|
-
return ___default["default"].object(___default["default"].map(table, function (value, key) {
|
|
14899
|
-
return [key, value[subKey]];
|
|
14900
|
-
}));
|
|
14901
|
-
};
|
|
14902
|
-
|
|
14903
|
-
/**
|
|
14904
|
-
* Maps an object to an object
|
|
14905
|
-
*
|
|
14906
|
-
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
14907
|
-
* return value + 1;
|
|
14908
|
-
* });
|
|
14909
|
-
* {a: 2, b: 3}
|
|
14910
|
-
*/
|
|
14911
|
-
const mapObject = function (obj, lambda) {
|
|
14912
|
-
const result = {};
|
|
14913
|
-
Object.keys(obj).forEach(key => {
|
|
14914
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
14915
|
-
result[key] = lambda(obj[key], key);
|
|
14916
|
-
});
|
|
14917
|
-
return result;
|
|
14918
|
-
};
|
|
14919
|
-
|
|
14920
14154
|
/**
|
|
14921
14155
|
* MovableLine
|
|
14922
14156
|
*/
|
|
@@ -14981,7 +14215,7 @@ ___default["default"].extend(MovableLine$4.prototype, {
|
|
|
14981
14215
|
// - are objects, not primitives (and need a deeper copy)
|
|
14982
14216
|
// - they don't need getters created for them
|
|
14983
14217
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
14984
|
-
pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14218
|
+
perseusCore.pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14985
14219
|
},
|
|
14986
14220
|
/**
|
|
14987
14221
|
* Resets the object to its state as if it were constructed with
|
|
@@ -15488,7 +14722,7 @@ class MovablePoint$5 {
|
|
|
15488
14722
|
// - are objects, not primitives (and need a deeper copy)
|
|
15489
14723
|
// - they don't need getters created for them
|
|
15490
14724
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
15491
|
-
pluck(MovablePointOptions, "standard")
|
|
14725
|
+
perseusCore.pluck(MovablePointOptions, "standard")
|
|
15492
14726
|
// We only update props here, because we want things on state to
|
|
15493
14727
|
// be persistent, and updated appropriately in modify()
|
|
15494
14728
|
),
|
|
@@ -16389,7 +15623,7 @@ ___default["default"].extend(MovablePolygon.prototype, {
|
|
|
16389
15623
|
// - are objects, not primitives (and need a deeper copy)
|
|
16390
15624
|
// - they don't need getters created for them
|
|
16391
15625
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
16392
|
-
pluck(MovablePolygonOptions, "standard")
|
|
15626
|
+
perseusCore.pluck(MovablePolygonOptions, "standard")
|
|
16393
15627
|
|
|
16394
15628
|
// We only update props here, because we want things on state to
|
|
16395
15629
|
// be persistent, and updated appropriately in modify()
|
|
@@ -18774,7 +18008,7 @@ const inlineNodeTypes = {
|
|
|
18774
18008
|
|
|
18775
18009
|
// @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'.
|
|
18776
18010
|
const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
|
|
18777
|
-
const parse$
|
|
18011
|
+
const parse$4 = (source, state) => {
|
|
18778
18012
|
const paragraphedSource = source + "\n\n";
|
|
18779
18013
|
return builtParser$1(paragraphedSource, {
|
|
18780
18014
|
...state,
|
|
@@ -18859,14 +18093,14 @@ const getContent = ast => {
|
|
|
18859
18093
|
*/
|
|
18860
18094
|
const characterCount = source => {
|
|
18861
18095
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
18862
|
-
const ast = parse$
|
|
18096
|
+
const ast = parse$4(source);
|
|
18863
18097
|
const content = getContent(ast).join("");
|
|
18864
18098
|
return content.length;
|
|
18865
18099
|
};
|
|
18866
18100
|
var PerseusMarkdown = {
|
|
18867
18101
|
characterCount: characterCount,
|
|
18868
18102
|
traverseContent: traverseContent,
|
|
18869
|
-
parse: parse$
|
|
18103
|
+
parse: parse$4,
|
|
18870
18104
|
parseInline: inlineParser,
|
|
18871
18105
|
reactFor: SimpleMarkdown__default["default"].reactFor,
|
|
18872
18106
|
ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
|
|
@@ -19241,6 +18475,9 @@ const getWidgetExport = name => {
|
|
|
19241
18475
|
const getWidgetScorer = name => {
|
|
19242
18476
|
return widgets[name]?.scorer ?? null;
|
|
19243
18477
|
};
|
|
18478
|
+
const getPublicWidgetOptionsFunction = name => {
|
|
18479
|
+
return widgets[name]?.getPublicWidgetOptions ?? (i => i);
|
|
18480
|
+
};
|
|
19244
18481
|
const getEditor = name => {
|
|
19245
18482
|
return ___default["default"].has(editors, name) ? editors[name] : null;
|
|
19246
18483
|
};
|
|
@@ -19548,6 +18785,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19548
18785
|
getWidget: getWidget,
|
|
19549
18786
|
getWidgetExport: getWidgetExport,
|
|
19550
18787
|
getWidgetScorer: getWidgetScorer,
|
|
18788
|
+
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
19551
18789
|
getEditor: getEditor,
|
|
19552
18790
|
getTransform: getTransform,
|
|
19553
18791
|
getVersion: getVersion,
|
|
@@ -19569,7 +18807,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19569
18807
|
|
|
19570
18808
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
19571
18809
|
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19572
|
-
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18810
|
+
return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
19573
18811
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19574
18812
|
const newValues = {};
|
|
19575
18813
|
if (!widgetInfo.type) {
|
|
@@ -20295,7 +19533,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20295
19533
|
* state from that instead of the current widget props.
|
|
20296
19534
|
*/
|
|
20297
19535
|
getSerializedState = widgetProps => {
|
|
20298
|
-
return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
19536
|
+
return perseusCore.mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
20299
19537
|
const widget = this.getWidgetInstance(widgetId);
|
|
20300
19538
|
if (widget && widget.getSerializedState) {
|
|
20301
19539
|
return widget.getSerializedState();
|
|
@@ -20331,7 +19569,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20331
19569
|
}
|
|
20332
19570
|
};
|
|
20333
19571
|
this.setState({
|
|
20334
|
-
widgetProps: mapObject(serializedState, (props, widgetId) => {
|
|
19572
|
+
widgetProps: perseusCore.mapObject(serializedState, (props, widgetId) => {
|
|
20335
19573
|
const widget = this.getWidgetInstance(widgetId);
|
|
20336
19574
|
if (widget && widget.restoreSerializedState) {
|
|
20337
19575
|
// Note that we probably can't call
|
|
@@ -22118,12 +21356,12 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
22118
21356
|
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
22119
21357
|
// expected here
|
|
22120
21358
|
const stringValue = `${rubric.value}`;
|
|
22121
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21359
|
+
const val = perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
22122
21360
|
simplify: rubric.simplify,
|
|
22123
21361
|
inexact: rubric.inexact || undefined,
|
|
22124
21362
|
maxError: rubric.maxError,
|
|
22125
21363
|
forms: answerTypes[rubric.answerType].forms
|
|
22126
|
-
}
|
|
21364
|
+
});
|
|
22127
21365
|
|
|
22128
21366
|
// We may have received TeX; try to parse it before grading.
|
|
22129
21367
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
@@ -22450,14 +21688,14 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22450
21688
|
if (!answer.strict || validatorForms.length === 0) {
|
|
22451
21689
|
validatorForms.push(...defaultAnswerForms);
|
|
22452
21690
|
}
|
|
22453
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
21691
|
+
return perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22454
21692
|
message: answer.message,
|
|
22455
21693
|
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22456
21694
|
inexact: true,
|
|
22457
21695
|
// TODO(merlob) backfill / delete
|
|
22458
21696
|
maxError: answer.maxError,
|
|
22459
21697
|
forms: validatorForms
|
|
22460
|
-
}
|
|
21698
|
+
});
|
|
22461
21699
|
};
|
|
22462
21700
|
|
|
22463
21701
|
// We may have received TeX; try to parse it before grading.
|
|
@@ -22760,11 +21998,11 @@ var NumericInput$1 = {
|
|
|
22760
21998
|
}
|
|
22761
21999
|
|
|
22762
22000
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
|
|
22763
|
-
let answerString = KhanMath.toNumericString(answer.value, format);
|
|
22001
|
+
let answerString = kmath.KhanMath.toNumericString(answer.value, format);
|
|
22764
22002
|
if (answer.maxError) {
|
|
22765
22003
|
answerString += " \u00B1 " +
|
|
22766
22004
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
|
|
22767
|
-
KhanMath.toNumericString(answer.maxError, format);
|
|
22005
|
+
kmath.KhanMath.toNumericString(answer.maxError, format);
|
|
22768
22006
|
}
|
|
22769
22007
|
return answerString;
|
|
22770
22008
|
});
|
|
@@ -24567,7 +23805,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24567
23805
|
// TODO(LEMS-2656): remove TS suppression
|
|
24568
23806
|
// @ts-expect-error: State is not assignable to ParseState
|
|
24569
23807
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
24570
|
-
const parse$
|
|
23808
|
+
const parse$3 = (source, state) => {
|
|
24571
23809
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24572
23810
|
state = state || {};
|
|
24573
23811
|
const paragraphedSource = source + "\n\n";
|
|
@@ -24577,7 +23815,7 @@ const parse$2 = (source, state) => {
|
|
|
24577
23815
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24578
23816
|
const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
|
|
24579
23817
|
var PassageMarkdown = {
|
|
24580
|
-
parse: parse$
|
|
23818
|
+
parse: parse$3,
|
|
24581
23819
|
output,
|
|
24582
23820
|
START_REF_PREFIX,
|
|
24583
23821
|
END_REF_PREFIX,
|
|
@@ -27060,6 +26298,24 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
27060
26298
|
};
|
|
27061
26299
|
};
|
|
27062
26300
|
|
|
26301
|
+
/**
|
|
26302
|
+
* For details on the individual options, see the
|
|
26303
|
+
* PerseusCategorizerWidgetOptions type
|
|
26304
|
+
*/
|
|
26305
|
+
|
|
26306
|
+
/**
|
|
26307
|
+
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
26308
|
+
* the public options that should be exposed to the client.
|
|
26309
|
+
*/
|
|
26310
|
+
function getCategorizerPublicWidgetOptions(options) {
|
|
26311
|
+
return {
|
|
26312
|
+
items: options.items,
|
|
26313
|
+
categories: options.categories,
|
|
26314
|
+
randomizeItems: options.randomizeItems,
|
|
26315
|
+
static: options.static
|
|
26316
|
+
};
|
|
26317
|
+
}
|
|
26318
|
+
|
|
27063
26319
|
/**
|
|
27064
26320
|
* Checks userInput from the categorizer widget to see if the user has selected
|
|
27065
26321
|
* a category for each item.
|
|
@@ -27279,7 +26535,8 @@ var Categorizer$1 = {
|
|
|
27279
26535
|
isLintable: true,
|
|
27280
26536
|
// TODO(LEMS-2656): remove TS suppression
|
|
27281
26537
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27282
|
-
scorer: scoreCategorizer
|
|
26538
|
+
scorer: scoreCategorizer,
|
|
26539
|
+
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
27283
26540
|
};
|
|
27284
26541
|
|
|
27285
26542
|
/**
|
|
@@ -28143,7 +27400,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
28143
27400
|
DEFAULT_INVALID_MESSAGE_2
|
|
28144
27401
|
} = this.context.strings;
|
|
28145
27402
|
const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
|
|
28146
|
-
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
27403
|
+
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${mapErrorToString(score.message)}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
28147
27404
|
this.setState({
|
|
28148
27405
|
status: status,
|
|
28149
27406
|
message: message,
|
|
@@ -29254,6 +28511,9 @@ const typeToButton = type => {
|
|
|
29254
28511
|
};
|
|
29255
28512
|
|
|
29256
28513
|
function getCoefficientsByType(data) {
|
|
28514
|
+
if (data.coords == null) {
|
|
28515
|
+
return undefined;
|
|
28516
|
+
}
|
|
29257
28517
|
if (data.type === "exponential" || data.type === "logarithm") {
|
|
29258
28518
|
const grader = functionForType(data.type);
|
|
29259
28519
|
return grader.getCoefficients(data.coords, data.asymptote);
|
|
@@ -32043,9 +31303,10 @@ const AxisTicks = () => {
|
|
|
32043
31303
|
* @param {number} axisIndex which axis we're getting options for
|
|
32044
31304
|
*/
|
|
32045
31305
|
const axisOptions = (props, axisIndex) => {
|
|
31306
|
+
const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
|
|
32046
31307
|
return {
|
|
32047
|
-
axis: props.markings === "graph",
|
|
32048
|
-
lines:
|
|
31308
|
+
axis: props.markings === "graph" || props.markings === "axes",
|
|
31309
|
+
lines: lines,
|
|
32049
31310
|
labels: false
|
|
32050
31311
|
};
|
|
32051
31312
|
};
|
|
@@ -32332,8 +31593,8 @@ const {
|
|
|
32332
31593
|
firstNumericalParse,
|
|
32333
31594
|
captureScratchpadTouchStart
|
|
32334
31595
|
} = Util;
|
|
32335
|
-
const toNumericString = KhanMath.toNumericString;
|
|
32336
|
-
const getNumericFormat = KhanMath.getNumericFormat;
|
|
31596
|
+
const toNumericString = kmath.KhanMath.toNumericString;
|
|
31597
|
+
const getNumericFormat = kmath.KhanMath.getNumericFormat;
|
|
32337
31598
|
|
|
32338
31599
|
/**
|
|
32339
31600
|
* An input box that accepts only numeric strings
|
|
@@ -32628,231 +31889,6 @@ const LegacyGrid = _ref => {
|
|
|
32628
31889
|
return null;
|
|
32629
31890
|
};
|
|
32630
31891
|
|
|
32631
|
-
/**
|
|
32632
|
-
* The Perseus "data schema" file.
|
|
32633
|
-
*
|
|
32634
|
-
* This file, and the types in it, represents the "data schema" that Perseus
|
|
32635
|
-
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
32636
|
-
* that conform to the types in this file. Similarly, the top-level renderers
|
|
32637
|
-
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
32638
|
-
*
|
|
32639
|
-
* WARNING: This file should not import any types from elsewhere so that it is
|
|
32640
|
-
* easy to reason about changes that alter the Perseus schema. This helps
|
|
32641
|
-
* ensure that it is not changed accidentally when upgrading a dependant
|
|
32642
|
-
* package or other part of Perseus code. Note that TypeScript does type
|
|
32643
|
-
* checking via something called "structural typing". This means that as long
|
|
32644
|
-
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
32645
|
-
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
32646
|
-
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
32647
|
-
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
32648
|
-
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
32649
|
-
* labels are for humans, not TypeScript. :)
|
|
32650
|
-
*
|
|
32651
|
-
* If you make changes to types in this file, be very sure that:
|
|
32652
|
-
*
|
|
32653
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
32654
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
32655
|
-
* introduce hard-to-diagnose bugs.
|
|
32656
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
32657
|
-
* the new format _as well as_ the old format.
|
|
32658
|
-
*/
|
|
32659
|
-
|
|
32660
|
-
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
32661
|
-
|
|
32662
|
-
// Same name as Mafs
|
|
32663
|
-
|
|
32664
|
-
/**
|
|
32665
|
-
* Our core set of Perseus widgets.
|
|
32666
|
-
*
|
|
32667
|
-
* This interface is the basis for "registering" all Perseus widget types.
|
|
32668
|
-
* There should be one key/value pair for each supported widget. If you create
|
|
32669
|
-
* a new widget, an entry should be added to this interface. Note that this
|
|
32670
|
-
* only registers the widget options type, you'll also need to register the
|
|
32671
|
-
* widget so that it's available at runtime (@see
|
|
32672
|
-
* {@link file://./widgets.ts#registerWidget}).
|
|
32673
|
-
*
|
|
32674
|
-
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
32675
|
-
* widgets that is the same as the widget option's `type` field. In cases where
|
|
32676
|
-
* a widget has been deprecated and replaced with the deprecated-standin
|
|
32677
|
-
* widget, it should be the original widget type!
|
|
32678
|
-
*
|
|
32679
|
-
* If you define the widget outside of this package, you can still add the new
|
|
32680
|
-
* widget to this interface by writing the following in that package that
|
|
32681
|
-
* contains the widget. TypeScript will merge that definition of the
|
|
32682
|
-
* `PerseusWidgets` with the one defined below.
|
|
32683
|
-
*
|
|
32684
|
-
* ```typescript
|
|
32685
|
-
* declare module "@khanacademy/perseus" {
|
|
32686
|
-
* interface PerseusWidgetTypes {
|
|
32687
|
-
* // A new widget
|
|
32688
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
32689
|
-
*
|
|
32690
|
-
* // A deprecated widget
|
|
32691
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
32692
|
-
* }
|
|
32693
|
-
* }
|
|
32694
|
-
*
|
|
32695
|
-
* // The new widget's options definition
|
|
32696
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
32697
|
-
*
|
|
32698
|
-
* // The deprecated widget's options definition
|
|
32699
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
32700
|
-
* ```
|
|
32701
|
-
*
|
|
32702
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
32703
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
32704
|
-
*
|
|
32705
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32706
|
-
*/
|
|
32707
|
-
|
|
32708
|
-
/**
|
|
32709
|
-
* A map of widget IDs to widget options. This is most often used as the type
|
|
32710
|
-
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
32711
|
-
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
32712
|
-
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
32713
|
-
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
32714
|
-
* reading/parsing the widget id to derive any information from it, except in
|
|
32715
|
-
* the case of this map.
|
|
32716
|
-
*
|
|
32717
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32718
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32719
|
-
*/
|
|
32720
|
-
|
|
32721
|
-
/**
|
|
32722
|
-
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32723
|
-
* `ServerItemRenderer` and the layout is pre-set.
|
|
32724
|
-
*
|
|
32725
|
-
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
32726
|
-
* area.
|
|
32727
|
-
*/
|
|
32728
|
-
|
|
32729
|
-
/**
|
|
32730
|
-
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32731
|
-
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32732
|
-
*/
|
|
32733
|
-
|
|
32734
|
-
const ItemExtras = [
|
|
32735
|
-
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
32736
|
-
"calculator",
|
|
32737
|
-
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
32738
|
-
"chi2Table",
|
|
32739
|
-
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
32740
|
-
"financialCalculatorMonthlyPayment",
|
|
32741
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
32742
|
-
"financialCalculatorTotalAmount",
|
|
32743
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
32744
|
-
"financialCalculatorTimeToPayOff",
|
|
32745
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
32746
|
-
"periodicTable",
|
|
32747
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
32748
|
-
"periodicTableWithKey",
|
|
32749
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
32750
|
-
"tTable",
|
|
32751
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
32752
|
-
"zTable"];
|
|
32753
|
-
|
|
32754
|
-
/**
|
|
32755
|
-
* The type representing the common structure of all widget's options. The
|
|
32756
|
-
* `Options` generic type represents the widget-specific option data.
|
|
32757
|
-
*/
|
|
32758
|
-
|
|
32759
|
-
// prettier-ignore
|
|
32760
|
-
|
|
32761
|
-
// prettier-ignore
|
|
32762
|
-
|
|
32763
|
-
// prettier-ignore
|
|
32764
|
-
|
|
32765
|
-
// prettier-ignore
|
|
32766
|
-
|
|
32767
|
-
// prettier-ignore
|
|
32768
|
-
|
|
32769
|
-
// prettier-ignore
|
|
32770
|
-
|
|
32771
|
-
// prettier-ignore
|
|
32772
|
-
|
|
32773
|
-
// prettier-ignore
|
|
32774
|
-
|
|
32775
|
-
// prettier-ignore
|
|
32776
|
-
|
|
32777
|
-
// prettier-ignore
|
|
32778
|
-
|
|
32779
|
-
// prettier-ignore
|
|
32780
|
-
|
|
32781
|
-
// prettier-ignore
|
|
32782
|
-
|
|
32783
|
-
// prettier-ignore
|
|
32784
|
-
|
|
32785
|
-
// prettier-ignore
|
|
32786
|
-
|
|
32787
|
-
// prettier-ignore
|
|
32788
|
-
|
|
32789
|
-
// prettier-ignore
|
|
32790
|
-
|
|
32791
|
-
// prettier-ignore
|
|
32792
|
-
|
|
32793
|
-
// prettier-ignore
|
|
32794
|
-
|
|
32795
|
-
// prettier-ignore
|
|
32796
|
-
|
|
32797
|
-
// prettier-ignore
|
|
32798
|
-
|
|
32799
|
-
// prettier-ignore
|
|
32800
|
-
|
|
32801
|
-
// prettier-ignore
|
|
32802
|
-
|
|
32803
|
-
// prettier-ignore
|
|
32804
|
-
|
|
32805
|
-
// prettier-ignore
|
|
32806
|
-
|
|
32807
|
-
// prettier-ignore
|
|
32808
|
-
|
|
32809
|
-
// prettier-ignore
|
|
32810
|
-
|
|
32811
|
-
// prettier-ignore
|
|
32812
|
-
|
|
32813
|
-
// prettier-ignore
|
|
32814
|
-
|
|
32815
|
-
// prettier-ignore
|
|
32816
|
-
|
|
32817
|
-
// prettier-ignore
|
|
32818
|
-
|
|
32819
|
-
// prettier-ignore
|
|
32820
|
-
|
|
32821
|
-
// prettier-ignore
|
|
32822
|
-
|
|
32823
|
-
// prettier-ignore
|
|
32824
|
-
|
|
32825
|
-
//prettier-ignore
|
|
32826
|
-
|
|
32827
|
-
/**
|
|
32828
|
-
* A background image applied to various widgets.
|
|
32829
|
-
*/
|
|
32830
|
-
|
|
32831
|
-
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32832
|
-
|
|
32833
|
-
// 2D range: xMin, xMax, yMin, yMax
|
|
32834
|
-
|
|
32835
|
-
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
32836
|
-
const lockedFigureColors = {
|
|
32837
|
-
blue: "#3D7586",
|
|
32838
|
-
green: "#447A53",
|
|
32839
|
-
grayH: "#3B3D45",
|
|
32840
|
-
purple: "#594094",
|
|
32841
|
-
pink: "#B25071",
|
|
32842
|
-
red: "#D92916",
|
|
32843
|
-
orange: "#946700"
|
|
32844
|
-
};
|
|
32845
|
-
const lockedFigureFillStyles = {
|
|
32846
|
-
none: 0,
|
|
32847
|
-
white: 1,
|
|
32848
|
-
translucent: 0.4,
|
|
32849
|
-
solid: 1
|
|
32850
|
-
};
|
|
32851
|
-
|
|
32852
|
-
// Not associated with a specific figure
|
|
32853
|
-
|
|
32854
|
-
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
32855
|
-
|
|
32856
31892
|
function LockedLabel(props) {
|
|
32857
31893
|
const {
|
|
32858
31894
|
coord,
|
|
@@ -32872,7 +31908,7 @@ function LockedLabel(props) {
|
|
|
32872
31908
|
position: "absolute",
|
|
32873
31909
|
left: x,
|
|
32874
31910
|
top: y,
|
|
32875
|
-
color: lockedFigureColors[color],
|
|
31911
|
+
color: perseusCore.lockedFigureColors[color],
|
|
32876
31912
|
fontSize: wonderBlocksTokens.font.size[size],
|
|
32877
31913
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
32878
31914
|
},
|
|
@@ -32916,21 +31952,22 @@ const LockedEllipse = props => {
|
|
|
32916
31952
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32917
31953
|
className: "locked-ellipse",
|
|
32918
31954
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
32919
|
-
"aria-hidden": !hasAria
|
|
31955
|
+
"aria-hidden": !hasAria,
|
|
31956
|
+
role: "img"
|
|
32920
31957
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Ellipse, {
|
|
32921
31958
|
center: center,
|
|
32922
31959
|
radius: radius,
|
|
32923
31960
|
angle: angle,
|
|
32924
|
-
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
31961
|
+
fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
|
|
32925
31962
|
strokeStyle: strokeStyle,
|
|
32926
|
-
color: lockedFigureColors[color]
|
|
31963
|
+
color: perseusCore.lockedFigureColors[color]
|
|
32927
31964
|
// We need to override the svg props if we want to have a
|
|
32928
31965
|
// different fill color than the stroke color (specifically,
|
|
32929
31966
|
// in the case where the fillStyle is "white").
|
|
32930
31967
|
,
|
|
32931
31968
|
svgEllipseProps: {
|
|
32932
31969
|
style: {
|
|
32933
|
-
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
|
|
31970
|
+
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
|
|
32934
31971
|
}
|
|
32935
31972
|
}
|
|
32936
31973
|
}));
|
|
@@ -32945,7 +31982,7 @@ const LockedFunction = props => {
|
|
|
32945
31982
|
domain
|
|
32946
31983
|
} = props;
|
|
32947
31984
|
const plotProps = {
|
|
32948
|
-
color: lockedFigureColors[color],
|
|
31985
|
+
color: perseusCore.lockedFigureColors[color],
|
|
32949
31986
|
style: strokeStyle,
|
|
32950
31987
|
domain
|
|
32951
31988
|
};
|
|
@@ -32961,7 +31998,8 @@ const LockedFunction = props => {
|
|
|
32961
31998
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32962
31999
|
className: "locked-function",
|
|
32963
32000
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
32964
|
-
"aria-hidden": !hasAria
|
|
32001
|
+
"aria-hidden": !hasAria,
|
|
32002
|
+
role: "img"
|
|
32965
32003
|
}, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
|
|
32966
32004
|
y: x => equation.eval({
|
|
32967
32005
|
x
|
|
@@ -33087,7 +32125,7 @@ const LockedLine = props => {
|
|
|
33087
32125
|
line = /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
33088
32126
|
tail: point1.coord,
|
|
33089
32127
|
tip: extendedPoint,
|
|
33090
|
-
color: lockedFigureColors[color],
|
|
32128
|
+
color: perseusCore.lockedFigureColors[color],
|
|
33091
32129
|
style: {
|
|
33092
32130
|
strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
|
|
33093
32131
|
}
|
|
@@ -33102,33 +32140,34 @@ const LockedLine = props => {
|
|
|
33102
32140
|
const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
|
|
33103
32141
|
angle: angle,
|
|
33104
32142
|
tip: arrowTip,
|
|
33105
|
-
color: lockedFigureColors[color]
|
|
32143
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33106
32144
|
});
|
|
33107
32145
|
arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
|
|
33108
32146
|
angle = angle > 180 ? angle - 180 : angle + 180;
|
|
33109
32147
|
const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
|
|
33110
32148
|
angle: angle,
|
|
33111
32149
|
tip: arrowTip,
|
|
33112
|
-
color: lockedFigureColors[color]
|
|
32150
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33113
32151
|
});
|
|
33114
32152
|
line = /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, startArrowHead, /*#__PURE__*/React__namespace.createElement(LineType, {
|
|
33115
32153
|
point1: point1.coord,
|
|
33116
32154
|
point2: point2.coord,
|
|
33117
|
-
color: lockedFigureColors[color],
|
|
32155
|
+
color: perseusCore.lockedFigureColors[color],
|
|
33118
32156
|
style: lineStyle
|
|
33119
32157
|
}), endArrowHead);
|
|
33120
32158
|
}
|
|
33121
32159
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33122
32160
|
className: kind === "ray" ? "locked-ray" : "locked-line",
|
|
33123
32161
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
33124
|
-
"aria-hidden": !hasAria
|
|
32162
|
+
"aria-hidden": !hasAria,
|
|
32163
|
+
role: "img"
|
|
33125
32164
|
}, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
33126
32165
|
x: point1.coord[X],
|
|
33127
32166
|
y: point1.coord[Y],
|
|
33128
32167
|
svgCircleProps: {
|
|
33129
32168
|
style: {
|
|
33130
|
-
fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
|
|
33131
|
-
stroke: lockedFigureColors[point1.color],
|
|
32169
|
+
fill: point1.filled ? perseusCore.lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
|
|
32170
|
+
stroke: perseusCore.lockedFigureColors[point1.color],
|
|
33132
32171
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33133
32172
|
}
|
|
33134
32173
|
}
|
|
@@ -33137,8 +32176,8 @@ const LockedLine = props => {
|
|
|
33137
32176
|
y: point2.coord[Y],
|
|
33138
32177
|
svgCircleProps: {
|
|
33139
32178
|
style: {
|
|
33140
|
-
fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
|
|
33141
|
-
stroke: lockedFigureColors[point2.color],
|
|
32179
|
+
fill: point2.filled ? perseusCore.lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
|
|
32180
|
+
stroke: perseusCore.lockedFigureColors[point2.color],
|
|
33142
32181
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33143
32182
|
}
|
|
33144
32183
|
}
|
|
@@ -33157,14 +32196,15 @@ const LockedPoint = props => {
|
|
|
33157
32196
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33158
32197
|
className: "locked-point",
|
|
33159
32198
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
33160
|
-
"aria-hidden": !hasAria
|
|
32199
|
+
"aria-hidden": !hasAria,
|
|
32200
|
+
role: "img"
|
|
33161
32201
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
33162
32202
|
x: x,
|
|
33163
32203
|
y: y,
|
|
33164
32204
|
svgCircleProps: {
|
|
33165
32205
|
style: {
|
|
33166
|
-
fill: filled ? lockedFigureColors[color] : wonderBlocksTokens.color.white,
|
|
33167
|
-
stroke: lockedFigureColors[color],
|
|
32206
|
+
fill: filled ? perseusCore.lockedFigureColors[color] : wonderBlocksTokens.color.white,
|
|
32207
|
+
stroke: perseusCore.lockedFigureColors[color],
|
|
33168
32208
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33169
32209
|
}
|
|
33170
32210
|
}
|
|
@@ -33183,26 +32223,27 @@ const LockedPolygon = props => {
|
|
|
33183
32223
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33184
32224
|
className: "locked-polygon",
|
|
33185
32225
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
33186
|
-
"aria-hidden": !hasAria
|
|
32226
|
+
"aria-hidden": !hasAria,
|
|
32227
|
+
role: "img"
|
|
33187
32228
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
33188
32229
|
points: [...points],
|
|
33189
|
-
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
32230
|
+
fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
|
|
33190
32231
|
strokeStyle: strokeStyle,
|
|
33191
|
-
color: lockedFigureColors[color]
|
|
32232
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33192
32233
|
// We need to override the svg props if we want to have a
|
|
33193
32234
|
// different fill color than the stroke color (specifically,
|
|
33194
32235
|
// in the case where the fillStyle is "white").
|
|
33195
32236
|
,
|
|
33196
32237
|
svgPolygonProps: {
|
|
33197
32238
|
style: {
|
|
33198
|
-
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
|
|
32239
|
+
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
|
|
33199
32240
|
}
|
|
33200
32241
|
}
|
|
33201
32242
|
}), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
33202
32243
|
key: `locked-polygon-point-${index}`,
|
|
33203
32244
|
x: point[X],
|
|
33204
32245
|
y: point[Y],
|
|
33205
|
-
color: lockedFigureColors[color]
|
|
32246
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33206
32247
|
})));
|
|
33207
32248
|
};
|
|
33208
32249
|
|
|
@@ -33217,11 +32258,12 @@ const LockedVector = props => {
|
|
|
33217
32258
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33218
32259
|
className: "locked-vector",
|
|
33219
32260
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
33220
|
-
"aria-hidden": !hasAria
|
|
32261
|
+
"aria-hidden": !hasAria,
|
|
32262
|
+
role: "img"
|
|
33221
32263
|
}, /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
33222
32264
|
tail: tail,
|
|
33223
32265
|
tip: tip,
|
|
33224
|
-
color: lockedFigureColors[color]
|
|
32266
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33225
32267
|
}));
|
|
33226
32268
|
};
|
|
33227
32269
|
|
|
@@ -34657,7 +33699,9 @@ function renderCircleGraph(state, dispatch) {
|
|
|
34657
33699
|
graphState: state,
|
|
34658
33700
|
dispatch: dispatch
|
|
34659
33701
|
}),
|
|
34660
|
-
interactiveElementsDescription:
|
|
33702
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(CircleGraphDescription, {
|
|
33703
|
+
state: state
|
|
33704
|
+
})
|
|
34661
33705
|
};
|
|
34662
33706
|
}
|
|
34663
33707
|
function CircleGraph(props) {
|
|
@@ -34681,38 +33725,26 @@ function CircleGraph(props) {
|
|
|
34681
33725
|
const outerPointsId = id + "-outer-points";
|
|
34682
33726
|
|
|
34683
33727
|
// Aria label strings
|
|
34684
|
-
const
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
const circleRadiusDescription = strings.srCircleRadius({
|
|
34694
|
-
radius
|
|
34695
|
-
});
|
|
34696
|
-
const circleOuterPointsDescription = strings.srCircleOuterPoints({
|
|
34697
|
-
point1X: srFormatNumber(center[0] + radius, locale),
|
|
34698
|
-
point1Y: srFormatNumber(center[1], locale),
|
|
34699
|
-
point2X: srFormatNumber(center[0], locale),
|
|
34700
|
-
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
34701
|
-
point3X: srFormatNumber(center[0] - radius, locale),
|
|
34702
|
-
point3Y: srFormatNumber(center[1], locale),
|
|
34703
|
-
point4X: srFormatNumber(center[0], locale),
|
|
34704
|
-
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33728
|
+
const {
|
|
33729
|
+
srCircleGraph,
|
|
33730
|
+
srCircleShape,
|
|
33731
|
+
srCircleRadiusPoint,
|
|
33732
|
+
srCircleRadius,
|
|
33733
|
+
srCircleOuterPoints
|
|
33734
|
+
} = describeCircleGraph(graphState, {
|
|
33735
|
+
strings,
|
|
33736
|
+
locale
|
|
34705
33737
|
});
|
|
34706
33738
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34707
33739
|
// Outer circle minimal description
|
|
34708
|
-
"aria-label":
|
|
33740
|
+
"aria-label": srCircleGraph,
|
|
34709
33741
|
"aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
|
|
34710
33742
|
}, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
|
|
34711
33743
|
id: circleId
|
|
34712
33744
|
// Focusable circle aria label reads with every update
|
|
34713
33745
|
// because of the aria-live property in the circle <g>.
|
|
34714
33746
|
,
|
|
34715
|
-
ariaLabel:
|
|
33747
|
+
ariaLabel: srCircleShape
|
|
34716
33748
|
// Aria-describedby describes additional info on focus.
|
|
34717
33749
|
,
|
|
34718
33750
|
ariaDescribedBy: `${radiusId} ${outerPointsId}`,
|
|
@@ -34725,7 +33757,7 @@ function CircleGraph(props) {
|
|
|
34725
33757
|
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1
|
|
34726
33758
|
// Radius point aria label reads with every update.
|
|
34727
33759
|
, {
|
|
34728
|
-
ariaLabel: `${
|
|
33760
|
+
ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
|
|
34729
33761
|
// Aria-describedby describes additional info on focus.
|
|
34730
33762
|
,
|
|
34731
33763
|
ariaDescribedBy: `${outerPointsId}`
|
|
@@ -34748,12 +33780,12 @@ function CircleGraph(props) {
|
|
|
34748
33780
|
style: {
|
|
34749
33781
|
display: "hidden"
|
|
34750
33782
|
}
|
|
34751
|
-
},
|
|
33783
|
+
}, srCircleRadius), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34752
33784
|
id: outerPointsId,
|
|
34753
33785
|
style: {
|
|
34754
33786
|
display: "hidden"
|
|
34755
33787
|
}
|
|
34756
|
-
},
|
|
33788
|
+
}, srCircleOuterPoints));
|
|
34757
33789
|
}
|
|
34758
33790
|
function MovableCircle(props) {
|
|
34759
33791
|
const {
|
|
@@ -34840,6 +33872,65 @@ function crossProduct(as, bs) {
|
|
|
34840
33872
|
}
|
|
34841
33873
|
return result;
|
|
34842
33874
|
}
|
|
33875
|
+
function CircleGraphDescription(_ref) {
|
|
33876
|
+
let {
|
|
33877
|
+
state
|
|
33878
|
+
} = _ref;
|
|
33879
|
+
// The reason that CircleGraphDescription is a component (rather than a
|
|
33880
|
+
// function that returns a string) is because it needs to use a
|
|
33881
|
+
// hook: `usePerseusI18n`.
|
|
33882
|
+
const i18n = usePerseusI18n();
|
|
33883
|
+
const strings = describeCircleGraph(state, i18n);
|
|
33884
|
+
return strings.srCircleInteractiveElement;
|
|
33885
|
+
}
|
|
33886
|
+
|
|
33887
|
+
// Exported for testing
|
|
33888
|
+
function describeCircleGraph(state, i18n) {
|
|
33889
|
+
const {
|
|
33890
|
+
strings,
|
|
33891
|
+
locale
|
|
33892
|
+
} = i18n;
|
|
33893
|
+
const {
|
|
33894
|
+
center,
|
|
33895
|
+
radiusPoint
|
|
33896
|
+
} = state;
|
|
33897
|
+
const radius = getRadius(state);
|
|
33898
|
+
|
|
33899
|
+
// Aria label strings
|
|
33900
|
+
const srCircleGraph = strings.srCircleGraph;
|
|
33901
|
+
const srCircleShape = strings.srCircleShape({
|
|
33902
|
+
centerX: srFormatNumber(center[0], locale),
|
|
33903
|
+
centerY: srFormatNumber(center[1], locale)
|
|
33904
|
+
});
|
|
33905
|
+
const srCircleRadiusPoint = strings.srCircleRadiusPoint({
|
|
33906
|
+
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
33907
|
+
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
33908
|
+
});
|
|
33909
|
+
const srCircleRadius = strings.srCircleRadius({
|
|
33910
|
+
radius
|
|
33911
|
+
});
|
|
33912
|
+
const srCircleOuterPoints = strings.srCircleOuterPoints({
|
|
33913
|
+
point1X: srFormatNumber(center[0] + radius, locale),
|
|
33914
|
+
point1Y: srFormatNumber(center[1], locale),
|
|
33915
|
+
point2X: srFormatNumber(center[0], locale),
|
|
33916
|
+
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
33917
|
+
point3X: srFormatNumber(center[0] - radius, locale),
|
|
33918
|
+
point3Y: srFormatNumber(center[1], locale),
|
|
33919
|
+
point4X: srFormatNumber(center[0], locale),
|
|
33920
|
+
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33921
|
+
});
|
|
33922
|
+
const srCircleInteractiveElement = strings.srInteractiveElements({
|
|
33923
|
+
elements: [srCircleShape, srCircleRadius].join(" ")
|
|
33924
|
+
});
|
|
33925
|
+
return {
|
|
33926
|
+
srCircleGraph,
|
|
33927
|
+
srCircleShape,
|
|
33928
|
+
srCircleRadiusPoint,
|
|
33929
|
+
srCircleRadius,
|
|
33930
|
+
srCircleOuterPoints,
|
|
33931
|
+
srCircleInteractiveElement
|
|
33932
|
+
};
|
|
33933
|
+
}
|
|
34843
33934
|
|
|
34844
33935
|
function renderLinearGraph(state, dispatch) {
|
|
34845
33936
|
return {
|
|
@@ -35833,7 +34924,9 @@ const MafsGraph = props => {
|
|
|
35833
34924
|
labels,
|
|
35834
34925
|
disableKeyboardInteraction: readOnly || !!props.static
|
|
35835
34926
|
}
|
|
35836
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View,
|
|
34927
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34928
|
+
className: "mafs-graph-container"
|
|
34929
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
35837
34930
|
className: "mafs-graph",
|
|
35838
34931
|
style: {
|
|
35839
34932
|
position: "relative",
|
|
@@ -35876,7 +34969,7 @@ const MafsGraph = props => {
|
|
|
35876
34969
|
bottom: 0,
|
|
35877
34970
|
left: 0
|
|
35878
34971
|
}
|
|
35879
|
-
}, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
|
|
34972
|
+
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
|
|
35880
34973
|
preserveAspectRatio: false,
|
|
35881
34974
|
viewBox: {
|
|
35882
34975
|
x: state.range[X],
|
|
@@ -35896,7 +34989,7 @@ const MafsGraph = props => {
|
|
|
35896
34989
|
height: height
|
|
35897
34990
|
})),
|
|
35898
34991
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
35899
|
-
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
34992
|
+
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
35900
34993
|
lockedFigures: props.lockedFigures,
|
|
35901
34994
|
range: state.range
|
|
35902
34995
|
}))), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
@@ -41426,7 +40519,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41426
40519
|
const solutionSize = getMatrixSize(solution);
|
|
41427
40520
|
const suppliedSize = getMatrixSize(supplied);
|
|
41428
40521
|
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
41429
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40522
|
+
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
41430
40523
|
let message = null;
|
|
41431
40524
|
let incorrect = false;
|
|
41432
40525
|
___default["default"](suppliedSize[0]).times(row => {
|
|
@@ -41436,7 +40529,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41436
40529
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
41437
40530
|
solution[row][col], {
|
|
41438
40531
|
simplify: true
|
|
41439
|
-
}
|
|
40532
|
+
});
|
|
41440
40533
|
const result = validator(supplied[row][col]);
|
|
41441
40534
|
if (result.message) {
|
|
41442
40535
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
@@ -42588,10 +41681,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
42588
41681
|
const firstChar = smiles[0];
|
|
42589
41682
|
const rest = smiles.slice(1);
|
|
42590
41683
|
if (firstChar === "=") {
|
|
42591
|
-
return parse$
|
|
41684
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
42592
41685
|
}
|
|
42593
41686
|
if (firstChar === "#") {
|
|
42594
|
-
return parse$
|
|
41687
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
42595
41688
|
}
|
|
42596
41689
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
42597
41690
|
}
|
|
@@ -42652,8 +41745,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
42652
41745
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
42653
41746
|
parens: newCtx.parens.concat("(")
|
|
42654
41747
|
};
|
|
42655
|
-
const parenExpr = parse$
|
|
42656
|
-
const remainder = parse$
|
|
41748
|
+
const parenExpr = parse$2(rest, parenCtx);
|
|
41749
|
+
const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
42657
41750
|
return [parenExpr].concat(remainder);
|
|
42658
41751
|
}
|
|
42659
41752
|
if (firstChar === ")") {
|
|
@@ -42717,7 +41810,7 @@ function parseAtom(smiles, ctx) {
|
|
|
42717
41810
|
|
|
42718
41811
|
// increment the atom counter and reset the branch counter
|
|
42719
41812
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
42720
|
-
let restOfMolecule = parse$
|
|
41813
|
+
let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
42721
41814
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
42722
41815
|
//TODO(colin): fix this awkwardness.
|
|
42723
41816
|
restOfMolecule = [restOfMolecule];
|
|
@@ -42756,7 +41849,7 @@ function isModifierChar(s) {
|
|
|
42756
41849
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
42757
41850
|
* yet implemented.
|
|
42758
41851
|
*/
|
|
42759
|
-
function parse$
|
|
41852
|
+
function parse$2(smiles, ctx) {
|
|
42760
41853
|
if (!validate(smiles)) {
|
|
42761
41854
|
throw new ParseError$1("Invalid input.");
|
|
42762
41855
|
}
|
|
@@ -42781,7 +41874,7 @@ function parse$1(smiles, ctx) {
|
|
|
42781
41874
|
return parseParenthesizedExpression(smiles, ctx);
|
|
42782
41875
|
}
|
|
42783
41876
|
var SmilesParser = {
|
|
42784
|
-
parse: parse$
|
|
41877
|
+
parse: parse$2,
|
|
42785
41878
|
ParseError: ParseError$1
|
|
42786
41879
|
};
|
|
42787
41880
|
|
|
@@ -42789,7 +41882,7 @@ var SmilesParser = {
|
|
|
42789
41882
|
const {
|
|
42790
41883
|
layout
|
|
42791
41884
|
} = MoleculeLayout;
|
|
42792
|
-
const parse = SmilesParser.parse;
|
|
41885
|
+
const parse$1 = SmilesParser.parse;
|
|
42793
41886
|
const ParseError = SmilesParser.ParseError;
|
|
42794
41887
|
const borderSize = 30;
|
|
42795
41888
|
class Molecule extends React__namespace.Component {
|
|
@@ -42816,7 +41909,7 @@ class Molecule extends React__namespace.Component {
|
|
|
42816
41909
|
try {
|
|
42817
41910
|
this.setState({
|
|
42818
41911
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
42819
|
-
parsedSmiles: parse(smiles),
|
|
41912
|
+
parsedSmiles: parse$1(smiles),
|
|
42820
41913
|
error: null
|
|
42821
41914
|
});
|
|
42822
41915
|
} catch (e) {
|
|
@@ -43029,15 +42122,15 @@ const _label = (graphie, labelStyle, pos, value, base) => {
|
|
|
43029
42122
|
return graphie.label([pos, -0.53], Math.round(value * 100) / 100, "center");
|
|
43030
42123
|
}
|
|
43031
42124
|
if (labelStyle === "improper") {
|
|
43032
|
-
const frac = KhanMath.toFraction(value);
|
|
42125
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43033
42126
|
return graphie.label([pos, -0.17], formatImproper(frac[0], frac[1]), "below");
|
|
43034
42127
|
}
|
|
43035
42128
|
if (labelStyle === "mixed") {
|
|
43036
|
-
const frac = KhanMath.toFraction(value);
|
|
42129
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43037
42130
|
return graphie.label([pos, -0.17], formatMixed(frac[0], frac[1]), "below");
|
|
43038
42131
|
}
|
|
43039
42132
|
if (labelStyle === "non-reduced") {
|
|
43040
|
-
const frac = KhanMath.toFraction(value);
|
|
42133
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43041
42134
|
return graphie.label([pos, -0.17], formatNonReduced(frac[0], frac[1], base), "below");
|
|
43042
42135
|
}
|
|
43043
42136
|
};
|
|
@@ -43073,7 +42166,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
43073
42166
|
}
|
|
43074
42167
|
const getDenom = x => kmath.number.toFraction(x)[1];
|
|
43075
42168
|
const denoms = ___default["default"].map(fractions, getDenom);
|
|
43076
|
-
base = ___default["default"].reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
42169
|
+
base = ___default["default"].reduce(denoms, (x, y) => kmath.KhanMath.getLCM(x, y));
|
|
43077
42170
|
} else {
|
|
43078
42171
|
base = undefined;
|
|
43079
42172
|
}
|
|
@@ -44609,7 +43702,7 @@ class Plotter extends React__namespace.Component {
|
|
|
44609
43702
|
// If we have isMobile, we skip the 0 label.
|
|
44610
43703
|
const initialY = isMobile ? c.scaleY : 0;
|
|
44611
43704
|
for (let y = initialY; y <= c.dimY; y += c.scaleY) {
|
|
44612
|
-
graphie.label([0, y], KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
|
|
43705
|
+
graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
|
|
44613
43706
|
|
|
44614
43707
|
graphie.style({
|
|
44615
43708
|
stroke: isMobile ? "#e9ebec" : "#000",
|
|
@@ -45477,7 +44570,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45477
44570
|
message: null
|
|
45478
44571
|
};
|
|
45479
44572
|
}
|
|
45480
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44573
|
+
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
45481
44574
|
let message = null;
|
|
45482
44575
|
const allCorrect = solution.every(function (rowSolution) {
|
|
45483
44576
|
for (let i = 0; i < supplied.length; i++) {
|
|
@@ -45486,7 +44579,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45486
44579
|
const cellSolution = rowSolution[i];
|
|
45487
44580
|
const validator = createValidator(cellSolution, {
|
|
45488
44581
|
simplify: true
|
|
45489
|
-
}
|
|
44582
|
+
});
|
|
45490
44583
|
const result = validator(cellSupplied);
|
|
45491
44584
|
if (result.message) {
|
|
45492
44585
|
message = result.message;
|
|
@@ -45909,7 +45002,7 @@ const init = function () {
|
|
|
45909
45002
|
|
|
45910
45003
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45911
45004
|
const libName = "@khanacademy/perseus";
|
|
45912
|
-
const libVersion = "
|
|
45005
|
+
const libVersion = "50.1.0";
|
|
45913
45006
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45914
45007
|
|
|
45915
45008
|
const apiVersion = {
|
|
@@ -46849,7 +45942,7 @@ const traverseRenderer$1 = function (rendererOptions, contentCallback, deepWidge
|
|
|
46849
45942
|
newContent = modifiedContent;
|
|
46850
45943
|
}
|
|
46851
45944
|
}
|
|
46852
|
-
const newWidgets = mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
45945
|
+
const newWidgets = perseusCore.mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
46853
45946
|
// Widgets without info or a type are empty widgets, and
|
|
46854
45947
|
// should always be renderable. It's also annoying to write
|
|
46855
45948
|
// checks for this everywhere, so we just filter them out once and
|
|
@@ -48000,6 +47093,69 @@ function all(results) {
|
|
|
48000
47093
|
return success(values);
|
|
48001
47094
|
}
|
|
48002
47095
|
|
|
47096
|
+
class ErrorTrackingParseContext {
|
|
47097
|
+
constructor(path) {
|
|
47098
|
+
this.path = path;
|
|
47099
|
+
}
|
|
47100
|
+
failure(expected, badValue) {
|
|
47101
|
+
return failure([{
|
|
47102
|
+
expected: wrapInArray(expected),
|
|
47103
|
+
badValue,
|
|
47104
|
+
path: this.path
|
|
47105
|
+
}]);
|
|
47106
|
+
}
|
|
47107
|
+
forSubtree(key) {
|
|
47108
|
+
return new ErrorTrackingParseContext([...this.path, key]);
|
|
47109
|
+
}
|
|
47110
|
+
success(value) {
|
|
47111
|
+
return success(value);
|
|
47112
|
+
}
|
|
47113
|
+
}
|
|
47114
|
+
function wrapInArray(a) {
|
|
47115
|
+
return Array.isArray(a) ? a : [a];
|
|
47116
|
+
}
|
|
47117
|
+
|
|
47118
|
+
function formatPath(path) {
|
|
47119
|
+
return "(root)" + path.map(formatPathSegment).join("");
|
|
47120
|
+
}
|
|
47121
|
+
function formatPathSegment(segment) {
|
|
47122
|
+
if (typeof segment === "string") {
|
|
47123
|
+
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
47124
|
+
}
|
|
47125
|
+
return `[${segment.toString()}]`;
|
|
47126
|
+
}
|
|
47127
|
+
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
47128
|
+
|
|
47129
|
+
function message(failure) {
|
|
47130
|
+
const expected = conjoin(failure.expected);
|
|
47131
|
+
const path = formatPath(failure.path);
|
|
47132
|
+
const badValue = JSON.stringify(failure.badValue);
|
|
47133
|
+
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
47134
|
+
}
|
|
47135
|
+
function conjoin(items) {
|
|
47136
|
+
switch (items.length) {
|
|
47137
|
+
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
47138
|
+
case 1:
|
|
47139
|
+
return items[0];
|
|
47140
|
+
case 2:
|
|
47141
|
+
return items.join(" or ");
|
|
47142
|
+
default:
|
|
47143
|
+
{
|
|
47144
|
+
const allButLast = items.slice(0, items.length - 1);
|
|
47145
|
+
const last = items[items.length - 1];
|
|
47146
|
+
return allButLast.join(", ") + ", or " + last;
|
|
47147
|
+
}
|
|
47148
|
+
}
|
|
47149
|
+
}
|
|
47150
|
+
|
|
47151
|
+
function parse(value, parser) {
|
|
47152
|
+
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
47153
|
+
if (isFailure(result)) {
|
|
47154
|
+
return failure(result.detail.map(message).join("; "));
|
|
47155
|
+
}
|
|
47156
|
+
return result;
|
|
47157
|
+
}
|
|
47158
|
+
|
|
48003
47159
|
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48004
47160
|
|
|
48005
47161
|
function array(elementParser) {
|
|
@@ -48285,7 +47441,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
48285
47441
|
})),
|
|
48286
47442
|
showEditor: boolean,
|
|
48287
47443
|
showButtons: boolean,
|
|
48288
|
-
width: number,
|
|
48289
47444
|
height: number,
|
|
48290
47445
|
static: defaulted(boolean, () => false)
|
|
48291
47446
|
}));
|
|
@@ -48474,25 +47629,6 @@ function migrateV0ToV1(widget) {
|
|
|
48474
47629
|
}
|
|
48475
47630
|
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
48476
47631
|
|
|
48477
|
-
const parsePerseusRenderer = defaulted(object({
|
|
48478
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
48479
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
48480
|
-
content: defaulted(string, () => ""),
|
|
48481
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
48482
|
-
// `group` widget can contain another renderer.
|
|
48483
|
-
// The anonymous function below ensures that we don't try to access
|
|
48484
|
-
// parseWidgetsMap before it's defined.
|
|
48485
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48486
|
-
metadata: optional(array(string)),
|
|
48487
|
-
images: parseImages
|
|
48488
|
-
}),
|
|
48489
|
-
// Default value
|
|
48490
|
-
() => ({
|
|
48491
|
-
content: "",
|
|
48492
|
-
widgets: {},
|
|
48493
|
-
images: {}
|
|
48494
|
-
}));
|
|
48495
|
-
|
|
48496
47632
|
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
48497
47633
|
const parseGradedGroupWidgetOptions = object({
|
|
48498
47634
|
title: defaulted(string, () => ""),
|
|
@@ -48563,27 +47699,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
|
48563
47699
|
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
48564
47700
|
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
48565
47701
|
type: constant("absolute_value"),
|
|
48566
|
-
coords: pairOfPoints
|
|
47702
|
+
coords: nullable(pairOfPoints)
|
|
48567
47703
|
})).withBranch("exponential", object({
|
|
48568
47704
|
type: constant("exponential"),
|
|
48569
47705
|
asymptote: pairOfPoints,
|
|
48570
|
-
coords: pairOfPoints
|
|
47706
|
+
coords: nullable(pairOfPoints)
|
|
48571
47707
|
})).withBranch("linear", object({
|
|
48572
47708
|
type: constant("linear"),
|
|
48573
|
-
coords:
|
|
47709
|
+
coords: nullable(pairOfPoints)
|
|
48574
47710
|
})).withBranch("logarithm", object({
|
|
48575
47711
|
type: constant("logarithm"),
|
|
48576
47712
|
asymptote: pairOfPoints,
|
|
48577
|
-
coords: pairOfPoints
|
|
47713
|
+
coords: nullable(pairOfPoints)
|
|
48578
47714
|
})).withBranch("quadratic", object({
|
|
48579
47715
|
type: constant("quadratic"),
|
|
48580
|
-
coords: pairOfPoints
|
|
47716
|
+
coords: nullable(pairOfPoints)
|
|
48581
47717
|
})).withBranch("sinusoid", object({
|
|
48582
47718
|
type: constant("sinusoid"),
|
|
48583
|
-
coords: pairOfPoints
|
|
47719
|
+
coords: nullable(pairOfPoints)
|
|
48584
47720
|
})).withBranch("tangent", object({
|
|
48585
47721
|
type: constant("tangent"),
|
|
48586
|
-
coords: pairOfPoints
|
|
47722
|
+
coords: nullable(pairOfPoints)
|
|
48587
47723
|
})).parser,
|
|
48588
47724
|
graph: object({
|
|
48589
47725
|
backgroundImage: object({
|
|
@@ -48619,13 +47755,13 @@ const parseGroupWidget = parseWidget(constant("group"),
|
|
|
48619
47755
|
|
|
48620
47756
|
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
48621
47757
|
url: string,
|
|
48622
|
-
settings: array(object({
|
|
47758
|
+
settings: optional(array(object({
|
|
48623
47759
|
name: string,
|
|
48624
47760
|
value: string
|
|
48625
|
-
})),
|
|
47761
|
+
}))),
|
|
48626
47762
|
width: union(number).or(string).parser,
|
|
48627
47763
|
height: union(number).or(string).parser,
|
|
48628
|
-
allowFullScreen: boolean,
|
|
47764
|
+
allowFullScreen: defaulted(boolean, () => false),
|
|
48629
47765
|
allowTopNavigation: optional(boolean),
|
|
48630
47766
|
static: defaulted(boolean, () => false)
|
|
48631
47767
|
}));
|
|
@@ -48698,10 +47834,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48698
47834
|
|
|
48699
47835
|
const pairOfNumbers$1 = pair(number, number);
|
|
48700
47836
|
const stringOrEmpty = defaulted(string, () => "");
|
|
48701
|
-
const
|
|
47837
|
+
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
48702
47838
|
const parseFunctionElement = object({
|
|
48703
|
-
type:
|
|
48704
|
-
key:
|
|
47839
|
+
type: constant("function"),
|
|
47840
|
+
key: parseKey,
|
|
48705
47841
|
options: object({
|
|
48706
47842
|
value: string,
|
|
48707
47843
|
funcName: string,
|
|
@@ -48712,10 +47848,9 @@ const parseFunctionElement = object({
|
|
|
48712
47848
|
strokeWidth: number
|
|
48713
47849
|
})
|
|
48714
47850
|
});
|
|
48715
|
-
const parseLabelType = constant("label");
|
|
48716
47851
|
const parseLabelElement = object({
|
|
48717
|
-
type:
|
|
48718
|
-
key:
|
|
47852
|
+
type: constant("label"),
|
|
47853
|
+
key: parseKey,
|
|
48719
47854
|
options: object({
|
|
48720
47855
|
label: string,
|
|
48721
47856
|
color: string,
|
|
@@ -48723,10 +47858,9 @@ const parseLabelElement = object({
|
|
|
48723
47858
|
coordY: string
|
|
48724
47859
|
})
|
|
48725
47860
|
});
|
|
48726
|
-
const parseLineType = constant("line");
|
|
48727
47861
|
const parseLineElement = object({
|
|
48728
|
-
type:
|
|
48729
|
-
key:
|
|
47862
|
+
type: constant("line"),
|
|
47863
|
+
key: parseKey,
|
|
48730
47864
|
options: object({
|
|
48731
47865
|
color: string,
|
|
48732
47866
|
startX: string,
|
|
@@ -48738,10 +47872,9 @@ const parseLineElement = object({
|
|
|
48738
47872
|
arrows: string
|
|
48739
47873
|
})
|
|
48740
47874
|
});
|
|
48741
|
-
const parseMovableLineType = constant("movable-line");
|
|
48742
47875
|
const parseMovableLineElement = object({
|
|
48743
|
-
type:
|
|
48744
|
-
key:
|
|
47876
|
+
type: constant("movable-line"),
|
|
47877
|
+
key: parseKey,
|
|
48745
47878
|
options: object({
|
|
48746
47879
|
startX: string,
|
|
48747
47880
|
startY: string,
|
|
@@ -48758,10 +47891,9 @@ const parseMovableLineElement = object({
|
|
|
48758
47891
|
constraintYMax: string
|
|
48759
47892
|
})
|
|
48760
47893
|
});
|
|
48761
|
-
const parseMovablePointType = constant("movable-point");
|
|
48762
47894
|
const parseMovablePointElement = object({
|
|
48763
|
-
type:
|
|
48764
|
-
key:
|
|
47895
|
+
type: constant("movable-point"),
|
|
47896
|
+
key: parseKey,
|
|
48765
47897
|
options: object({
|
|
48766
47898
|
startX: string,
|
|
48767
47899
|
startY: string,
|
|
@@ -48775,10 +47907,9 @@ const parseMovablePointElement = object({
|
|
|
48775
47907
|
constraintYMax: stringOrEmpty
|
|
48776
47908
|
})
|
|
48777
47909
|
});
|
|
48778
|
-
const parseParametricType = constant("parametric");
|
|
48779
47910
|
const parseParametricElement = object({
|
|
48780
|
-
type:
|
|
48781
|
-
key:
|
|
47911
|
+
type: constant("parametric"),
|
|
47912
|
+
key: parseKey,
|
|
48782
47913
|
options: object({
|
|
48783
47914
|
x: string,
|
|
48784
47915
|
y: string,
|
|
@@ -48789,20 +47920,18 @@ const parseParametricElement = object({
|
|
|
48789
47920
|
strokeWidth: number
|
|
48790
47921
|
})
|
|
48791
47922
|
});
|
|
48792
|
-
const parsePointType = constant("point");
|
|
48793
47923
|
const parsePointElement = object({
|
|
48794
|
-
type:
|
|
48795
|
-
key:
|
|
47924
|
+
type: constant("point"),
|
|
47925
|
+
key: parseKey,
|
|
48796
47926
|
options: object({
|
|
48797
47927
|
color: string,
|
|
48798
47928
|
coordX: string,
|
|
48799
47929
|
coordY: string
|
|
48800
47930
|
})
|
|
48801
47931
|
});
|
|
48802
|
-
const parseRectangleType = constant("rectangle");
|
|
48803
47932
|
const parseRectangleElement = object({
|
|
48804
|
-
type:
|
|
48805
|
-
key:
|
|
47933
|
+
type: constant("rectangle"),
|
|
47934
|
+
key: parseKey,
|
|
48806
47935
|
options: object({
|
|
48807
47936
|
color: string,
|
|
48808
47937
|
coordX: string,
|
|
@@ -48923,7 +48052,7 @@ const parsePerseusGraphTypeSinusoid = object({
|
|
|
48923
48052
|
coord: optional(pairOfNumbers)
|
|
48924
48053
|
});
|
|
48925
48054
|
const parsePerseusGraphType = discriminatedUnionOn("type").withBranch("angle", parsePerseusGraphTypeAngle).withBranch("circle", parsePerseusGraphTypeCircle).withBranch("linear", parsePerseusGraphTypeLinear).withBranch("linear-system", parsePerseusGraphTypeLinearSystem).withBranch("none", parsePerseusGraphTypeNone).withBranch("point", parsePerseusGraphTypePoint).withBranch("polygon", parsePerseusGraphTypePolygon).withBranch("quadratic", parsePerseusGraphTypeQuadratic).withBranch("ray", parsePerseusGraphTypeRay).withBranch("segment", parsePerseusGraphTypeSegment).withBranch("sinusoid", parsePerseusGraphTypeSinusoid).parser;
|
|
48926
|
-
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48055
|
+
const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
|
|
48927
48056
|
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48928
48057
|
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48929
48058
|
const parseLockedLabelType = object({
|
|
@@ -48948,8 +48077,8 @@ const parseLockedLineType = object({
|
|
|
48948
48077
|
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48949
48078
|
color: parseLockedFigureColor,
|
|
48950
48079
|
lineStyle: parseLockedLineStyle,
|
|
48951
|
-
showPoint1: boolean,
|
|
48952
|
-
showPoint2: boolean,
|
|
48080
|
+
showPoint1: defaulted(boolean, () => false),
|
|
48081
|
+
showPoint2: defaulted(boolean, () => false),
|
|
48953
48082
|
// TODO(benchristel): default labels to empty array?
|
|
48954
48083
|
labels: optional(array(parseLockedLabelType)),
|
|
48955
48084
|
ariaLabel: optional(string)
|
|
@@ -48996,7 +48125,7 @@ const parseLockedFunctionType = object({
|
|
|
48996
48125
|
labels: optional(array(parseLockedLabelType)),
|
|
48997
48126
|
ariaLabel: optional(string)
|
|
48998
48127
|
});
|
|
48999
|
-
const parseLockedFigure =
|
|
48128
|
+
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;
|
|
49000
48129
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
49001
48130
|
step: pairOfNumbers,
|
|
49002
48131
|
// TODO(benchristel): rather than making gridStep and snapStep
|
|
@@ -49064,7 +48193,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
|
|
|
49064
48193
|
}));
|
|
49065
48194
|
|
|
49066
48195
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
49067
|
-
image
|
|
48196
|
+
// The default value for image comes from measurer.tsx.
|
|
48197
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48198
|
+
// defaults here.
|
|
48199
|
+
image: defaulted(parsePerseusImageBackground, () => ({
|
|
48200
|
+
url: null,
|
|
48201
|
+
top: 0,
|
|
48202
|
+
left: 0
|
|
48203
|
+
})),
|
|
49068
48204
|
showProtractor: boolean,
|
|
49069
48205
|
showRuler: boolean,
|
|
49070
48206
|
rulerLabel: string,
|
|
@@ -49173,11 +48309,17 @@ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), objec
|
|
|
49173
48309
|
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
49174
48310
|
labels: array(string),
|
|
49175
48311
|
categories: array(string),
|
|
49176
|
-
type: enumeration(...plotterPlotTypes),
|
|
48312
|
+
type: enumeration(...perseusCore.plotterPlotTypes),
|
|
49177
48313
|
maxY: number,
|
|
49178
|
-
scaleY
|
|
48314
|
+
// The default value for scaleY comes from plotter.tsx.
|
|
48315
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48316
|
+
// defaults here.
|
|
48317
|
+
scaleY: defaulted(number, () => 1),
|
|
49179
48318
|
labelInterval: optional(nullable(number)),
|
|
49180
|
-
snapsPerLine
|
|
48319
|
+
// The default value for snapsPerLine comes from plotter.tsx.
|
|
48320
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48321
|
+
// defaults here.
|
|
48322
|
+
snapsPerLine: defaulted(number, () => 2),
|
|
49181
48323
|
starting: array(number),
|
|
49182
48324
|
correct: array(number),
|
|
49183
48325
|
picUrl: optional(nullable(string)),
|
|
@@ -49255,12 +48397,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
49255
48397
|
return ctx.success(widgetsMap);
|
|
49256
48398
|
};
|
|
49257
48399
|
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
49258
|
-
let [
|
|
49259
|
-
const
|
|
49260
|
-
if (isFailure(
|
|
49261
|
-
return
|
|
48400
|
+
let [id, widget] = _ref;
|
|
48401
|
+
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
48402
|
+
if (isFailure(idComponentsResult)) {
|
|
48403
|
+
return idComponentsResult;
|
|
49262
48404
|
}
|
|
49263
|
-
const [type,
|
|
48405
|
+
const [type, n] = idComponentsResult.value;
|
|
49264
48406
|
function parseAndAssign(key, parse) {
|
|
49265
48407
|
const widgetResult = parse(widget, ctx);
|
|
49266
48408
|
if (isFailure(widgetResult)) {
|
|
@@ -49271,88 +48413,88 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
|
49271
48413
|
}
|
|
49272
48414
|
switch (type) {
|
|
49273
48415
|
case "categorizer":
|
|
49274
|
-
return parseAndAssign(`categorizer ${
|
|
48416
|
+
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
49275
48417
|
case "cs-program":
|
|
49276
|
-
return parseAndAssign(`cs-program ${
|
|
48418
|
+
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
49277
48419
|
case "definition":
|
|
49278
|
-
return parseAndAssign(`definition ${
|
|
48420
|
+
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
49279
48421
|
case "dropdown":
|
|
49280
|
-
return parseAndAssign(`dropdown ${
|
|
48422
|
+
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
49281
48423
|
case "explanation":
|
|
49282
|
-
return parseAndAssign(`explanation ${
|
|
48424
|
+
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
49283
48425
|
case "expression":
|
|
49284
|
-
return parseAndAssign(`expression ${
|
|
48426
|
+
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
49285
48427
|
case "grapher":
|
|
49286
|
-
return parseAndAssign(`grapher ${
|
|
48428
|
+
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
49287
48429
|
case "group":
|
|
49288
|
-
return parseAndAssign(`group ${
|
|
48430
|
+
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
49289
48431
|
case "graded-group":
|
|
49290
|
-
return parseAndAssign(`graded-group ${
|
|
48432
|
+
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
49291
48433
|
case "graded-group-set":
|
|
49292
|
-
return parseAndAssign(`graded-group-set ${
|
|
48434
|
+
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
49293
48435
|
case "iframe":
|
|
49294
|
-
return parseAndAssign(`iframe ${
|
|
48436
|
+
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
49295
48437
|
case "image":
|
|
49296
|
-
return parseAndAssign(`image ${
|
|
48438
|
+
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
49297
48439
|
case "input-number":
|
|
49298
|
-
return parseAndAssign(`input-number ${
|
|
48440
|
+
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
49299
48441
|
case "interaction":
|
|
49300
|
-
return parseAndAssign(`interaction ${
|
|
48442
|
+
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
49301
48443
|
case "interactive-graph":
|
|
49302
|
-
return parseAndAssign(`interactive-graph ${
|
|
48444
|
+
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
49303
48445
|
case "label-image":
|
|
49304
|
-
return parseAndAssign(`label-image ${
|
|
48446
|
+
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
49305
48447
|
case "matcher":
|
|
49306
|
-
return parseAndAssign(`matcher ${
|
|
48448
|
+
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
49307
48449
|
case "matrix":
|
|
49308
|
-
return parseAndAssign(`matrix ${
|
|
48450
|
+
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
49309
48451
|
case "measurer":
|
|
49310
|
-
return parseAndAssign(`measurer ${
|
|
48452
|
+
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
49311
48453
|
case "molecule-renderer":
|
|
49312
|
-
return parseAndAssign(`molecule-renderer ${
|
|
48454
|
+
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
49313
48455
|
case "number-line":
|
|
49314
|
-
return parseAndAssign(`number-line ${
|
|
48456
|
+
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
49315
48457
|
case "numeric-input":
|
|
49316
|
-
return parseAndAssign(`numeric-input ${
|
|
48458
|
+
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
49317
48459
|
case "orderer":
|
|
49318
|
-
return parseAndAssign(`orderer ${
|
|
48460
|
+
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
49319
48461
|
case "passage":
|
|
49320
|
-
return parseAndAssign(`passage ${
|
|
48462
|
+
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
49321
48463
|
case "passage-ref":
|
|
49322
|
-
return parseAndAssign(`passage-ref ${
|
|
48464
|
+
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
49323
48465
|
case "passage-ref-target":
|
|
49324
48466
|
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
49325
48467
|
// used in test content. See:
|
|
49326
48468
|
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
49327
|
-
return parseAndAssign(`passage-ref-target ${
|
|
48469
|
+
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
49328
48470
|
case "phet-simulation":
|
|
49329
|
-
return parseAndAssign(`phet-simulation ${
|
|
48471
|
+
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
49330
48472
|
case "plotter":
|
|
49331
|
-
return parseAndAssign(`plotter ${
|
|
48473
|
+
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
49332
48474
|
case "python-program":
|
|
49333
|
-
return parseAndAssign(`python-program ${
|
|
48475
|
+
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
49334
48476
|
case "radio":
|
|
49335
|
-
return parseAndAssign(`radio ${
|
|
48477
|
+
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
49336
48478
|
case "sorter":
|
|
49337
|
-
return parseAndAssign(`sorter ${
|
|
48479
|
+
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
49338
48480
|
case "table":
|
|
49339
|
-
return parseAndAssign(`table ${
|
|
48481
|
+
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
49340
48482
|
case "video":
|
|
49341
|
-
return parseAndAssign(`video ${
|
|
48483
|
+
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
49342
48484
|
case "sequence":
|
|
49343
48485
|
// sequence is a deprecated widget type, and the corresponding
|
|
49344
48486
|
// widget component no longer exists.
|
|
49345
|
-
return parseAndAssign(`sequence ${
|
|
48487
|
+
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
49346
48488
|
case "lights-puzzle":
|
|
49347
|
-
return parseAndAssign(`lights-puzzle ${
|
|
48489
|
+
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
49348
48490
|
case "simulator":
|
|
49349
|
-
return parseAndAssign(`simulator ${
|
|
48491
|
+
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
49350
48492
|
case "transformer":
|
|
49351
|
-
return parseAndAssign(`transformer ${
|
|
48493
|
+
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
49352
48494
|
default:
|
|
49353
48495
|
if (getWidget(type)) {
|
|
49354
48496
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
49355
|
-
return parseAndAssign(`${type} ${
|
|
48497
|
+
return parseAndAssign(`${type} ${n}`, any);
|
|
49356
48498
|
}
|
|
49357
48499
|
return ctx.failure("a valid widget type", type);
|
|
49358
48500
|
}
|
|
@@ -49364,11 +48506,32 @@ const parseDeprecatedWidget = parseWidget(
|
|
|
49364
48506
|
object({}));
|
|
49365
48507
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
49366
48508
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
49367
|
-
return ctx.failure("
|
|
48509
|
+
return ctx.failure("a string representing a positive integer", rawValue);
|
|
49368
48510
|
}
|
|
49369
48511
|
return ctx.success(+rawValue);
|
|
49370
48512
|
};
|
|
49371
|
-
const
|
|
48513
|
+
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
48514
|
+
|
|
48515
|
+
const parsePerseusRenderer = defaulted(object({
|
|
48516
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
48517
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
48518
|
+
content: defaulted(string, () => ""),
|
|
48519
|
+
// This module has an import cycle with parseWidgetsMap, because the
|
|
48520
|
+
// `group` widget can contain another renderer.
|
|
48521
|
+
// The anonymous function below ensures that we don't try to access
|
|
48522
|
+
// parseWidgetsMap before it's defined.
|
|
48523
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48524
|
+
metadata: optional(array(string)),
|
|
48525
|
+
images: parseImages
|
|
48526
|
+
}),
|
|
48527
|
+
// Default value
|
|
48528
|
+
() => ({
|
|
48529
|
+
content: "",
|
|
48530
|
+
widgets: {},
|
|
48531
|
+
images: {}
|
|
48532
|
+
}));
|
|
48533
|
+
|
|
48534
|
+
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
49372
48535
|
|
|
49373
48536
|
const parseHint = object({
|
|
49374
48537
|
replace: optional(boolean),
|
|
@@ -49378,10 +48541,10 @@ const parseHint = object({
|
|
|
49378
48541
|
images: parseImages
|
|
49379
48542
|
});
|
|
49380
48543
|
|
|
49381
|
-
object({
|
|
48544
|
+
const parsePerseusItem$1 = object({
|
|
49382
48545
|
question: parsePerseusRenderer,
|
|
49383
48546
|
hints: defaulted(array(parseHint), () => []),
|
|
49384
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
48547
|
+
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
|
|
49385
48548
|
itemDataVersion: optional(object({
|
|
49386
48549
|
major: number,
|
|
49387
48550
|
minor: number
|
|
@@ -49417,6 +48580,7 @@ function migrateAnswerArea(rawValue, ctx) {
|
|
|
49417
48580
|
* Why not just use JSON.parse? We want:
|
|
49418
48581
|
* - To make sure types are correct
|
|
49419
48582
|
* - To give us a central place to validate/transform output if needed
|
|
48583
|
+
* @deprecated - use parseAndMigratePerseusItem instead
|
|
49420
48584
|
* @param {string} json - the stringified PerseusItem JSON
|
|
49421
48585
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
49422
48586
|
*/
|
|
@@ -49428,6 +48592,60 @@ function parsePerseusItem(json) {
|
|
|
49428
48592
|
}
|
|
49429
48593
|
throw new Error("Something went wrong.");
|
|
49430
48594
|
}
|
|
48595
|
+
/**
|
|
48596
|
+
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
48597
|
+
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
48598
|
+
* data.
|
|
48599
|
+
*
|
|
48600
|
+
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
48601
|
+
* failure, it will contain an error message describing where in the tree
|
|
48602
|
+
* parsing failed.
|
|
48603
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48604
|
+
*/
|
|
48605
|
+
function parseAndMigratePerseusItem(json) {
|
|
48606
|
+
throwErrorIfCheatingDetected();
|
|
48607
|
+
const object = JSON.parse(json);
|
|
48608
|
+
const result = parse(object, parsePerseusItem$1);
|
|
48609
|
+
if (isFailure(result)) {
|
|
48610
|
+
return failure({
|
|
48611
|
+
message: result.detail,
|
|
48612
|
+
invalidObject: object
|
|
48613
|
+
});
|
|
48614
|
+
}
|
|
48615
|
+
return result;
|
|
48616
|
+
}
|
|
48617
|
+
|
|
48618
|
+
/**
|
|
48619
|
+
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
48620
|
+
* latest schema, and runtime-typechecks the result.
|
|
48621
|
+
*
|
|
48622
|
+
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
48623
|
+
* failure, it will contain an error message describing where in the tree
|
|
48624
|
+
* parsing failed.
|
|
48625
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48626
|
+
*/
|
|
48627
|
+
function parseAndMigratePerseusArticle(json) {
|
|
48628
|
+
throwErrorIfCheatingDetected();
|
|
48629
|
+
const object = JSON.parse(json);
|
|
48630
|
+
const result = parse(object, parsePerseusArticle);
|
|
48631
|
+
if (isFailure(result)) {
|
|
48632
|
+
return failure({
|
|
48633
|
+
message: result.detail,
|
|
48634
|
+
invalidObject: object
|
|
48635
|
+
});
|
|
48636
|
+
}
|
|
48637
|
+
return result;
|
|
48638
|
+
}
|
|
48639
|
+
|
|
48640
|
+
/**
|
|
48641
|
+
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
48642
|
+
*/
|
|
48643
|
+
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
48644
|
+
function throwErrorIfCheatingDetected() {
|
|
48645
|
+
if (!isRealJSONParse(JSON.parse)) {
|
|
48646
|
+
throw new Error("Something went wrong.");
|
|
48647
|
+
}
|
|
48648
|
+
}
|
|
49431
48649
|
|
|
49432
48650
|
const genericPerseusItemData = {
|
|
49433
48651
|
question: {
|
|
@@ -49666,15 +48884,12 @@ exports.HintRenderer = HintRenderer;
|
|
|
49666
48884
|
exports.HintsRenderer = HintsRenderer;
|
|
49667
48885
|
exports.InputNumber = InputNumber$1;
|
|
49668
48886
|
exports.InteractiveGraphWidget = InteractiveGraph$1;
|
|
49669
|
-
exports.ItemExtras = ItemExtras;
|
|
49670
48887
|
exports.JiptParagraphs = JiptParagraphs;
|
|
49671
48888
|
exports.KhanColors = KhanColors;
|
|
49672
|
-
exports.KhanMath = KhanMath;
|
|
49673
48889
|
exports.LoadingContext = context;
|
|
49674
48890
|
exports.Log = Log;
|
|
49675
48891
|
exports.MatrixWidget = Matrix$1;
|
|
49676
48892
|
exports.NumericInput = NumericInput$1;
|
|
49677
|
-
exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
|
|
49678
48893
|
exports.PerseusI18nContext = PerseusI18nContext;
|
|
49679
48894
|
exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
|
|
49680
48895
|
exports.PerseusMarkdown = PerseusMarkdown;
|
|
@@ -49730,20 +48945,21 @@ exports.iconTrash = iconTrash;
|
|
|
49730
48945
|
exports.init = init;
|
|
49731
48946
|
exports.injectWidgets = injectWidgets;
|
|
49732
48947
|
exports.interactiveSizes = interactiveSizes$1;
|
|
48948
|
+
exports.isFailure = isFailure;
|
|
49733
48949
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
48950
|
+
exports.isSuccess = isSuccess;
|
|
49734
48951
|
exports.isWidgetIdInContent = isWidgetIdInContent;
|
|
49735
48952
|
exports.isWrongAnswerSupported = isWrongAnswerSupported;
|
|
49736
48953
|
exports.itemDataVersion = itemDataVersion;
|
|
49737
48954
|
exports.itemVersion = ItemVersion;
|
|
49738
48955
|
exports.libVersion = libVersion;
|
|
49739
|
-
exports.lockedFigureColors = lockedFigureColors;
|
|
49740
|
-
exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
49741
48956
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49742
48957
|
exports.mathOnlyParser = mathOnlyParser;
|
|
49743
48958
|
exports.mediaQueries = mediaQueries;
|
|
48959
|
+
exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
|
|
48960
|
+
exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
|
|
49744
48961
|
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
49745
48962
|
exports.parsePerseusItem = parsePerseusItem;
|
|
49746
|
-
exports.plotterPlotTypes = plotterPlotTypes;
|
|
49747
48963
|
exports.preprocessTex = preprocessTex;
|
|
49748
48964
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
49749
48965
|
exports.scorePerseusItem = scorePerseusItem;
|