@khanacademy/perseus 49.2.1 → 50.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/article-renderer.d.ts +2 -6
- package/dist/components/graph.d.ts +1 -1
- package/dist/components/graphie.d.ts +1 -1
- package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +1 -4
- package/dist/components/highlighting/ui/highlighting-ui.d.ts +1 -4
- package/dist/components/i18n-context.d.ts +1 -1
- package/dist/components/input-with-examples.d.ts +1 -4
- package/dist/components/math-input.d.ts +2 -6
- package/dist/components/number-input.d.ts +2 -5
- package/dist/components/svg-image.d.ts +1 -1
- package/dist/es/index.js +547 -1380
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +40 -85
- package/dist/es/strings.js.map +1 -1
- package/dist/hint-renderer.d.ts +1 -4
- package/dist/hints-renderer.d.ts +2 -5
- package/dist/index.d.ts +5 -7
- package/dist/index.js +588 -1407
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +1 -1
- package/dist/renderer.d.ts +2 -5
- package/dist/server-item-renderer.d.ts +3 -7
- package/dist/strings.d.ts +8 -0
- package/dist/strings.js +40 -84
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/util/extract-perseus-data.d.ts +1 -1
- package/dist/util/parse-perseus-json/index.d.ts +33 -4
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +3 -3
- package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +1 -1
- package/dist/util/test-utils.d.ts +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/validation.types.d.ts +9 -3
- package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/prompt-types.d.ts +2 -1
- package/dist/widget-container.d.ts +1 -1
- package/dist/widget-type-utils.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +2 -5
- package/dist/widgets/cs-program/cs-program.d.ts +1 -1
- package/dist/widgets/definition/definition.d.ts +2 -5
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -4
- package/dist/widgets/dropdown/dropdown.d.ts +2 -5
- package/dist/widgets/explanation/explanation.d.ts +2 -5
- package/dist/widgets/expression/expression.d.ts +5 -8
- package/dist/widgets/graded-group/graded-group-answer-bar.d.ts +1 -4
- package/dist/widgets/graded-group/graded-group.d.ts +2 -5
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
- package/dist/widgets/grapher/grapher.d.ts +2 -2
- package/dist/widgets/group/group.d.ts +2 -5
- package/dist/widgets/iframe/iframe.d.ts +1 -1
- package/dist/widgets/image/image.d.ts +2 -5
- package/dist/widgets/input-number/input-number.d.ts +2 -6
- package/dist/widgets/interaction/interaction.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +4 -6
- package/dist/widgets/interactive-graphs/backgrounds/grid.d.ts +2 -2
- package/dist/widgets/interactive-graphs/backgrounds/legacy-grid.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/circle.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/components/angle-indicators.d.ts +1 -1
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +2 -2
- package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-label.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +1 -1
- package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +2 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
- package/dist/widgets/label-image/label-image.d.ts +2 -5
- package/dist/widgets/label-image/marker.d.ts +1 -4
- package/dist/widgets/matcher/matcher.d.ts +2 -5
- package/dist/widgets/matrix/matrix.d.ts +2 -5
- package/dist/widgets/measurer/measurer.d.ts +1 -1
- package/dist/widgets/mock-widgets/index.d.ts +2 -0
- package/dist/widgets/mock-widgets/mock-asset-loading-widget.d.ts +17 -0
- package/dist/widgets/mock-widgets/mock-widget.d.ts +47 -0
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +5 -0
- package/dist/widgets/molecule/molecule.d.ts +1 -1
- package/dist/widgets/number-line/number-line.d.ts +1 -4
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -6
- package/dist/widgets/orderer/orderer.d.ts +1 -1
- package/dist/widgets/passage/passage.d.ts +2 -5
- package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -5
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -5
- package/dist/widgets/plotter/plotter.d.ts +2 -5
- package/dist/widgets/radio/base-radio.d.ts +1 -1
- package/dist/widgets/radio/radio-component.d.ts +2 -5
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -1
- package/dist/widgets/table/table.d.ts +2 -5
- package/dist/widgets/video/video.d.ts +2 -5
- package/dist/widgets.d.ts +1 -1
- package/package.json +46 -45
- package/dist/interactive2/objective_.d.ts +0 -34
- package/dist/perseus-types.d.ts +0 -980
- package/dist/util/answer-types.d.ts +0 -69
- package/dist/util/math.d.ts +0 -29
package/dist/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,16 @@ const mockStrings = {
|
|
|
497
675
|
// The above strings are used for interactive graph SR descriptions.
|
|
498
676
|
};
|
|
499
677
|
|
|
678
|
+
({
|
|
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
|
+
|
|
500
688
|
/**
|
|
501
689
|
* PerseusI18nContext provides a way to set the strings and locale that are
|
|
502
690
|
* used inside the Perseus package.
|
|
@@ -1464,918 +1652,6 @@ const Log = {
|
|
|
1464
1652
|
}
|
|
1465
1653
|
};
|
|
1466
1654
|
|
|
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
1655
|
/**
|
|
2380
1656
|
* Checks user input from the expression widget to see if it is scorable.
|
|
2381
1657
|
*
|
|
@@ -2439,10 +1715,10 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2439
1715
|
});
|
|
2440
1716
|
return null;
|
|
2441
1717
|
}
|
|
2442
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
1718
|
+
return perseusScore.KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
2443
1719
|
simplify: answer.simplify,
|
|
2444
1720
|
form: answer.form
|
|
2445
|
-
})
|
|
1721
|
+
}));
|
|
2446
1722
|
};
|
|
2447
1723
|
|
|
2448
1724
|
// Find the first answer form that matches the user's input and that
|
|
@@ -3108,14 +2384,14 @@ const split = "x".split(/(.)/g).length ? function (str, r) {
|
|
|
3108
2384
|
function firstNumericalParse$1(text, strings) {
|
|
3109
2385
|
// TODO(alpert): This is sort of hacky...
|
|
3110
2386
|
let first;
|
|
3111
|
-
const val = KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
|
|
2387
|
+
const val = perseusScore.KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
|
|
3112
2388
|
first = ans;
|
|
3113
2389
|
return true; /* break */
|
|
3114
2390
|
}, {
|
|
3115
2391
|
simplify: "optional",
|
|
3116
2392
|
inexact: true,
|
|
3117
2393
|
forms: "integer, proper, improper, pi, log, mixed, decimal"
|
|
3118
|
-
}
|
|
2394
|
+
});
|
|
3119
2395
|
val(text);
|
|
3120
2396
|
return first;
|
|
3121
2397
|
}
|
|
@@ -9128,7 +8404,7 @@ var Tex$1 = {
|
|
|
9128
8404
|
text = text != null ? text + "" : "";
|
|
9129
8405
|
|
|
9130
8406
|
// Attempt to clean up some of the math
|
|
9131
|
-
text = KhanMath.cleanMath(text);
|
|
8407
|
+
text = kmath.KhanMath.cleanMath(text);
|
|
9132
8408
|
|
|
9133
8409
|
// Store the formula that we're using
|
|
9134
8410
|
$elem.attr("data-math-formula", text);
|
|
@@ -9815,7 +9091,7 @@ class Graphie$1 {
|
|
|
9815
9091
|
return "z";
|
|
9816
9092
|
}
|
|
9817
9093
|
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
9818
|
-
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
9094
|
+
return (i === 0 ? "M" : "L") + kmath.KhanMath.bound(scaled[0]) + " " + kmath.KhanMath.bound(scaled[1]);
|
|
9819
9095
|
}).join("");
|
|
9820
9096
|
};
|
|
9821
9097
|
svgParabolaPath = (a, b, c) => {
|
|
@@ -9851,7 +9127,7 @@ class Graphie$1 {
|
|
|
9851
9127
|
|
|
9852
9128
|
// Scale and bound
|
|
9853
9129
|
const points = [left, control, right].map(this.scalePoint);
|
|
9854
|
-
const values = points.flat().map(KhanMath.bound);
|
|
9130
|
+
const values = points.flat().map(kmath.KhanMath.bound);
|
|
9855
9131
|
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
9856
9132
|
};
|
|
9857
9133
|
svgSinusoidPath = (a, b, c, d) => {
|
|
@@ -10901,7 +10177,7 @@ class Arrowhead$1 extends WrappedPath {
|
|
|
10901
10177
|
}
|
|
10902
10178
|
toCoordAtAngle(coord, angle) {
|
|
10903
10179
|
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)");
|
|
10180
|
+
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
10181
|
}
|
|
10906
10182
|
}
|
|
10907
10183
|
|
|
@@ -11025,9 +10301,9 @@ class WrappedLine extends WrappedDrawing {
|
|
|
11025
10301
|
// Compute transformation parameters
|
|
11026
10302
|
const polarDiff = kmath.vector.polarDegFromCart(kmath.vector.subtract(scaledEnd, scaledStart));
|
|
11027
10303
|
const lineLength = polarDiff[0];
|
|
11028
|
-
const angle = KhanMath.bound(polarDiff[1]);
|
|
10304
|
+
const angle = kmath.KhanMath.bound(polarDiff[1]);
|
|
11029
10305
|
const delta = kmath.vector.subtract(scaledStart, this.initialPoint);
|
|
11030
|
-
const scale = KhanMath.bound(lineLength / this.initialLength);
|
|
10306
|
+
const scale = kmath.KhanMath.bound(lineLength / this.initialLength);
|
|
11031
10307
|
|
|
11032
10308
|
// Construct and apply transformation string
|
|
11033
10309
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
@@ -11133,7 +10409,7 @@ function clockwise$1(points) {
|
|
|
11133
10409
|
const p2 = segment[1];
|
|
11134
10410
|
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
11135
10411
|
});
|
|
11136
|
-
return sum
|
|
10412
|
+
return kmath.sum(areas) > 0;
|
|
11137
10413
|
}
|
|
11138
10414
|
function magnitude(v) {
|
|
11139
10415
|
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
@@ -11380,7 +10656,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11380
10656
|
const match = text.match(/\$deg(\d)?/);
|
|
11381
10657
|
if (match) {
|
|
11382
10658
|
const precision = match[1] || 1;
|
|
11383
|
-
text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
10659
|
+
text = text.replace(match[0], kmath.KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
11384
10660
|
}
|
|
11385
10661
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
11386
10662
|
const sPosition = kmath.vector.add(sVertex, sOffset);
|
|
@@ -11473,7 +10749,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11473
10749
|
if (match) {
|
|
11474
10750
|
const distance = GraphUtils.getDistance(p1, p2);
|
|
11475
10751
|
const precision = match[1] || 1;
|
|
11476
|
-
text = text.replace(match[0], KhanMath.toFixedApprox(distance, precision));
|
|
10752
|
+
text = text.replace(match[0], kmath.KhanMath.toFixedApprox(distance, precision));
|
|
11477
10753
|
}
|
|
11478
10754
|
const sOffset = 20;
|
|
11479
10755
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
@@ -11696,8 +10972,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11696
10972
|
// convert back to coordinates relative to graphie canvas
|
|
11697
10973
|
mouseX = mouseXrel + centerX;
|
|
11698
10974
|
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]);
|
|
10975
|
+
coordX = kmath.KhanMath.roundTo(5, mouseX / graph.scale[0] + graph.range[0][0]);
|
|
10976
|
+
coordY = kmath.KhanMath.roundTo(5, graph.range[1][1] - mouseY / graph.scale[1]);
|
|
11701
10977
|
}
|
|
11702
10978
|
const result = movablePoint.applyConstraint([coordX, coordY]);
|
|
11703
10979
|
return result;
|
|
@@ -14869,54 +14145,6 @@ var MovableLineOptions = {
|
|
|
14869
14145
|
}
|
|
14870
14146
|
};
|
|
14871
14147
|
|
|
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
14148
|
/**
|
|
14921
14149
|
* MovableLine
|
|
14922
14150
|
*/
|
|
@@ -14981,7 +14209,7 @@ ___default["default"].extend(MovableLine$4.prototype, {
|
|
|
14981
14209
|
// - are objects, not primitives (and need a deeper copy)
|
|
14982
14210
|
// - they don't need getters created for them
|
|
14983
14211
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
14984
|
-
pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14212
|
+
perseusCore.pluck(MovableLineOptions, "standard")), DEFAULT_PROPS$2);
|
|
14985
14213
|
},
|
|
14986
14214
|
/**
|
|
14987
14215
|
* Resets the object to its state as if it were constructed with
|
|
@@ -15488,7 +14716,7 @@ class MovablePoint$5 {
|
|
|
15488
14716
|
// - are objects, not primitives (and need a deeper copy)
|
|
15489
14717
|
// - they don't need getters created for them
|
|
15490
14718
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
15491
|
-
pluck(MovablePointOptions, "standard")
|
|
14719
|
+
perseusCore.pluck(MovablePointOptions, "standard")
|
|
15492
14720
|
// We only update props here, because we want things on state to
|
|
15493
14721
|
// be persistent, and updated appropriately in modify()
|
|
15494
14722
|
),
|
|
@@ -16389,7 +15617,7 @@ ___default["default"].extend(MovablePolygon.prototype, {
|
|
|
16389
15617
|
// - are objects, not primitives (and need a deeper copy)
|
|
16390
15618
|
// - they don't need getters created for them
|
|
16391
15619
|
// TODO(jack): Consider "default" once we es3ify perseus
|
|
16392
|
-
pluck(MovablePolygonOptions, "standard")
|
|
15620
|
+
perseusCore.pluck(MovablePolygonOptions, "standard")
|
|
16393
15621
|
|
|
16394
15622
|
// We only update props here, because we want things on state to
|
|
16395
15623
|
// be persistent, and updated appropriately in modify()
|
|
@@ -18774,7 +18002,7 @@ const inlineNodeTypes = {
|
|
|
18774
18002
|
|
|
18775
18003
|
// @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
18004
|
const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
|
|
18777
|
-
const parse$
|
|
18005
|
+
const parse$4 = (source, state) => {
|
|
18778
18006
|
const paragraphedSource = source + "\n\n";
|
|
18779
18007
|
return builtParser$1(paragraphedSource, {
|
|
18780
18008
|
...state,
|
|
@@ -18859,14 +18087,14 @@ const getContent = ast => {
|
|
|
18859
18087
|
*/
|
|
18860
18088
|
const characterCount = source => {
|
|
18861
18089
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
18862
|
-
const ast = parse$
|
|
18090
|
+
const ast = parse$4(source);
|
|
18863
18091
|
const content = getContent(ast).join("");
|
|
18864
18092
|
return content.length;
|
|
18865
18093
|
};
|
|
18866
18094
|
var PerseusMarkdown = {
|
|
18867
18095
|
characterCount: characterCount,
|
|
18868
18096
|
traverseContent: traverseContent,
|
|
18869
|
-
parse: parse$
|
|
18097
|
+
parse: parse$4,
|
|
18870
18098
|
parseInline: inlineParser,
|
|
18871
18099
|
reactFor: SimpleMarkdown__default["default"].reactFor,
|
|
18872
18100
|
ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
|
|
@@ -19569,7 +18797,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19569
18797
|
|
|
19570
18798
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
19571
18799
|
// @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) => {
|
|
18800
|
+
return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
19573
18801
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19574
18802
|
const newValues = {};
|
|
19575
18803
|
if (!widgetInfo.type) {
|
|
@@ -20295,7 +19523,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20295
19523
|
* state from that instead of the current widget props.
|
|
20296
19524
|
*/
|
|
20297
19525
|
getSerializedState = widgetProps => {
|
|
20298
|
-
return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
19526
|
+
return perseusCore.mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
20299
19527
|
const widget = this.getWidgetInstance(widgetId);
|
|
20300
19528
|
if (widget && widget.getSerializedState) {
|
|
20301
19529
|
return widget.getSerializedState();
|
|
@@ -20331,7 +19559,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20331
19559
|
}
|
|
20332
19560
|
};
|
|
20333
19561
|
this.setState({
|
|
20334
|
-
widgetProps: mapObject(serializedState, (props, widgetId) => {
|
|
19562
|
+
widgetProps: perseusCore.mapObject(serializedState, (props, widgetId) => {
|
|
20335
19563
|
const widget = this.getWidgetInstance(widgetId);
|
|
20336
19564
|
if (widget && widget.restoreSerializedState) {
|
|
20337
19565
|
// Note that we probably can't call
|
|
@@ -22118,12 +21346,12 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
22118
21346
|
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
22119
21347
|
// expected here
|
|
22120
21348
|
const stringValue = `${rubric.value}`;
|
|
22121
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21349
|
+
const val = perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
22122
21350
|
simplify: rubric.simplify,
|
|
22123
21351
|
inexact: rubric.inexact || undefined,
|
|
22124
21352
|
maxError: rubric.maxError,
|
|
22125
21353
|
forms: answerTypes[rubric.answerType].forms
|
|
22126
|
-
}
|
|
21354
|
+
});
|
|
22127
21355
|
|
|
22128
21356
|
// We may have received TeX; try to parse it before grading.
|
|
22129
21357
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
@@ -22450,14 +21678,14 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22450
21678
|
if (!answer.strict || validatorForms.length === 0) {
|
|
22451
21679
|
validatorForms.push(...defaultAnswerForms);
|
|
22452
21680
|
}
|
|
22453
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
21681
|
+
return perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
22454
21682
|
message: answer.message,
|
|
22455
21683
|
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
22456
21684
|
inexact: true,
|
|
22457
21685
|
// TODO(merlob) backfill / delete
|
|
22458
21686
|
maxError: answer.maxError,
|
|
22459
21687
|
forms: validatorForms
|
|
22460
|
-
}
|
|
21688
|
+
});
|
|
22461
21689
|
};
|
|
22462
21690
|
|
|
22463
21691
|
// We may have received TeX; try to parse it before grading.
|
|
@@ -22760,11 +21988,11 @@ var NumericInput$1 = {
|
|
|
22760
21988
|
}
|
|
22761
21989
|
|
|
22762
21990
|
// @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);
|
|
21991
|
+
let answerString = kmath.KhanMath.toNumericString(answer.value, format);
|
|
22764
21992
|
if (answer.maxError) {
|
|
22765
21993
|
answerString += " \u00B1 " +
|
|
22766
21994
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
|
|
22767
|
-
KhanMath.toNumericString(answer.maxError, format);
|
|
21995
|
+
kmath.KhanMath.toNumericString(answer.maxError, format);
|
|
22768
21996
|
}
|
|
22769
21997
|
return answerString;
|
|
22770
21998
|
});
|
|
@@ -24567,7 +23795,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24567
23795
|
// TODO(LEMS-2656): remove TS suppression
|
|
24568
23796
|
// @ts-expect-error: State is not assignable to ParseState
|
|
24569
23797
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
24570
|
-
const parse$
|
|
23798
|
+
const parse$3 = (source, state) => {
|
|
24571
23799
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24572
23800
|
state = state || {};
|
|
24573
23801
|
const paragraphedSource = source + "\n\n";
|
|
@@ -24577,7 +23805,7 @@ const parse$2 = (source, state) => {
|
|
|
24577
23805
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24578
23806
|
const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
|
|
24579
23807
|
var PassageMarkdown = {
|
|
24580
|
-
parse: parse$
|
|
23808
|
+
parse: parse$3,
|
|
24581
23809
|
output,
|
|
24582
23810
|
START_REF_PREFIX,
|
|
24583
23811
|
END_REF_PREFIX,
|
|
@@ -29254,6 +28482,9 @@ const typeToButton = type => {
|
|
|
29254
28482
|
};
|
|
29255
28483
|
|
|
29256
28484
|
function getCoefficientsByType(data) {
|
|
28485
|
+
if (data.coords == null) {
|
|
28486
|
+
return undefined;
|
|
28487
|
+
}
|
|
29257
28488
|
if (data.type === "exponential" || data.type === "logarithm") {
|
|
29258
28489
|
const grader = functionForType(data.type);
|
|
29259
28490
|
return grader.getCoefficients(data.coords, data.asymptote);
|
|
@@ -32043,9 +31274,10 @@ const AxisTicks = () => {
|
|
|
32043
31274
|
* @param {number} axisIndex which axis we're getting options for
|
|
32044
31275
|
*/
|
|
32045
31276
|
const axisOptions = (props, axisIndex) => {
|
|
31277
|
+
const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
|
|
32046
31278
|
return {
|
|
32047
|
-
axis: props.markings === "graph",
|
|
32048
|
-
lines:
|
|
31279
|
+
axis: props.markings === "graph" || props.markings === "axes",
|
|
31280
|
+
lines: lines,
|
|
32049
31281
|
labels: false
|
|
32050
31282
|
};
|
|
32051
31283
|
};
|
|
@@ -32332,8 +31564,8 @@ const {
|
|
|
32332
31564
|
firstNumericalParse,
|
|
32333
31565
|
captureScratchpadTouchStart
|
|
32334
31566
|
} = Util;
|
|
32335
|
-
const toNumericString = KhanMath.toNumericString;
|
|
32336
|
-
const getNumericFormat = KhanMath.getNumericFormat;
|
|
31567
|
+
const toNumericString = kmath.KhanMath.toNumericString;
|
|
31568
|
+
const getNumericFormat = kmath.KhanMath.getNumericFormat;
|
|
32337
31569
|
|
|
32338
31570
|
/**
|
|
32339
31571
|
* An input box that accepts only numeric strings
|
|
@@ -32628,231 +31860,6 @@ const LegacyGrid = _ref => {
|
|
|
32628
31860
|
return null;
|
|
32629
31861
|
};
|
|
32630
31862
|
|
|
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
31863
|
function LockedLabel(props) {
|
|
32857
31864
|
const {
|
|
32858
31865
|
coord,
|
|
@@ -32872,7 +31879,7 @@ function LockedLabel(props) {
|
|
|
32872
31879
|
position: "absolute",
|
|
32873
31880
|
left: x,
|
|
32874
31881
|
top: y,
|
|
32875
|
-
color: lockedFigureColors[color],
|
|
31882
|
+
color: perseusCore.lockedFigureColors[color],
|
|
32876
31883
|
fontSize: wonderBlocksTokens.font.size[size],
|
|
32877
31884
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
32878
31885
|
},
|
|
@@ -32921,16 +31928,16 @@ const LockedEllipse = props => {
|
|
|
32921
31928
|
center: center,
|
|
32922
31929
|
radius: radius,
|
|
32923
31930
|
angle: angle,
|
|
32924
|
-
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
31931
|
+
fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
|
|
32925
31932
|
strokeStyle: strokeStyle,
|
|
32926
|
-
color: lockedFigureColors[color]
|
|
31933
|
+
color: perseusCore.lockedFigureColors[color]
|
|
32927
31934
|
// We need to override the svg props if we want to have a
|
|
32928
31935
|
// different fill color than the stroke color (specifically,
|
|
32929
31936
|
// in the case where the fillStyle is "white").
|
|
32930
31937
|
,
|
|
32931
31938
|
svgEllipseProps: {
|
|
32932
31939
|
style: {
|
|
32933
|
-
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
|
|
31940
|
+
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
|
|
32934
31941
|
}
|
|
32935
31942
|
}
|
|
32936
31943
|
}));
|
|
@@ -32945,7 +31952,7 @@ const LockedFunction = props => {
|
|
|
32945
31952
|
domain
|
|
32946
31953
|
} = props;
|
|
32947
31954
|
const plotProps = {
|
|
32948
|
-
color: lockedFigureColors[color],
|
|
31955
|
+
color: perseusCore.lockedFigureColors[color],
|
|
32949
31956
|
style: strokeStyle,
|
|
32950
31957
|
domain
|
|
32951
31958
|
};
|
|
@@ -33087,7 +32094,7 @@ const LockedLine = props => {
|
|
|
33087
32094
|
line = /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
33088
32095
|
tail: point1.coord,
|
|
33089
32096
|
tip: extendedPoint,
|
|
33090
|
-
color: lockedFigureColors[color],
|
|
32097
|
+
color: perseusCore.lockedFigureColors[color],
|
|
33091
32098
|
style: {
|
|
33092
32099
|
strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
|
|
33093
32100
|
}
|
|
@@ -33102,19 +32109,19 @@ const LockedLine = props => {
|
|
|
33102
32109
|
const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
|
|
33103
32110
|
angle: angle,
|
|
33104
32111
|
tip: arrowTip,
|
|
33105
|
-
color: lockedFigureColors[color]
|
|
32112
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33106
32113
|
});
|
|
33107
32114
|
arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
|
|
33108
32115
|
angle = angle > 180 ? angle - 180 : angle + 180;
|
|
33109
32116
|
const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
|
|
33110
32117
|
angle: angle,
|
|
33111
32118
|
tip: arrowTip,
|
|
33112
|
-
color: lockedFigureColors[color]
|
|
32119
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33113
32120
|
});
|
|
33114
32121
|
line = /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, startArrowHead, /*#__PURE__*/React__namespace.createElement(LineType, {
|
|
33115
32122
|
point1: point1.coord,
|
|
33116
32123
|
point2: point2.coord,
|
|
33117
|
-
color: lockedFigureColors[color],
|
|
32124
|
+
color: perseusCore.lockedFigureColors[color],
|
|
33118
32125
|
style: lineStyle
|
|
33119
32126
|
}), endArrowHead);
|
|
33120
32127
|
}
|
|
@@ -33127,8 +32134,8 @@ const LockedLine = props => {
|
|
|
33127
32134
|
y: point1.coord[Y],
|
|
33128
32135
|
svgCircleProps: {
|
|
33129
32136
|
style: {
|
|
33130
|
-
fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
|
|
33131
|
-
stroke: lockedFigureColors[point1.color],
|
|
32137
|
+
fill: point1.filled ? perseusCore.lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
|
|
32138
|
+
stroke: perseusCore.lockedFigureColors[point1.color],
|
|
33132
32139
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33133
32140
|
}
|
|
33134
32141
|
}
|
|
@@ -33137,8 +32144,8 @@ const LockedLine = props => {
|
|
|
33137
32144
|
y: point2.coord[Y],
|
|
33138
32145
|
svgCircleProps: {
|
|
33139
32146
|
style: {
|
|
33140
|
-
fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
|
|
33141
|
-
stroke: lockedFigureColors[point2.color],
|
|
32147
|
+
fill: point2.filled ? perseusCore.lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
|
|
32148
|
+
stroke: perseusCore.lockedFigureColors[point2.color],
|
|
33142
32149
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33143
32150
|
}
|
|
33144
32151
|
}
|
|
@@ -33163,8 +32170,8 @@ const LockedPoint = props => {
|
|
|
33163
32170
|
y: y,
|
|
33164
32171
|
svgCircleProps: {
|
|
33165
32172
|
style: {
|
|
33166
|
-
fill: filled ? lockedFigureColors[color] : wonderBlocksTokens.color.white,
|
|
33167
|
-
stroke: lockedFigureColors[color],
|
|
32173
|
+
fill: filled ? perseusCore.lockedFigureColors[color] : wonderBlocksTokens.color.white,
|
|
32174
|
+
stroke: perseusCore.lockedFigureColors[color],
|
|
33168
32175
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
33169
32176
|
}
|
|
33170
32177
|
}
|
|
@@ -33186,23 +32193,23 @@ const LockedPolygon = props => {
|
|
|
33186
32193
|
"aria-hidden": !hasAria
|
|
33187
32194
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
33188
32195
|
points: [...points],
|
|
33189
|
-
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
32196
|
+
fillOpacity: perseusCore.lockedFigureFillStyles[fillStyle],
|
|
33190
32197
|
strokeStyle: strokeStyle,
|
|
33191
|
-
color: lockedFigureColors[color]
|
|
32198
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33192
32199
|
// We need to override the svg props if we want to have a
|
|
33193
32200
|
// different fill color than the stroke color (specifically,
|
|
33194
32201
|
// in the case where the fillStyle is "white").
|
|
33195
32202
|
,
|
|
33196
32203
|
svgPolygonProps: {
|
|
33197
32204
|
style: {
|
|
33198
|
-
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
|
|
32205
|
+
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : perseusCore.lockedFigureColors[color]
|
|
33199
32206
|
}
|
|
33200
32207
|
}
|
|
33201
32208
|
}), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
33202
32209
|
key: `locked-polygon-point-${index}`,
|
|
33203
32210
|
x: point[X],
|
|
33204
32211
|
y: point[Y],
|
|
33205
|
-
color: lockedFigureColors[color]
|
|
32212
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33206
32213
|
})));
|
|
33207
32214
|
};
|
|
33208
32215
|
|
|
@@ -33221,7 +32228,7 @@ const LockedVector = props => {
|
|
|
33221
32228
|
}, /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
33222
32229
|
tail: tail,
|
|
33223
32230
|
tip: tip,
|
|
33224
|
-
color: lockedFigureColors[color]
|
|
32231
|
+
color: perseusCore.lockedFigureColors[color]
|
|
33225
32232
|
}));
|
|
33226
32233
|
};
|
|
33227
32234
|
|
|
@@ -34657,7 +33664,9 @@ function renderCircleGraph(state, dispatch) {
|
|
|
34657
33664
|
graphState: state,
|
|
34658
33665
|
dispatch: dispatch
|
|
34659
33666
|
}),
|
|
34660
|
-
interactiveElementsDescription:
|
|
33667
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(CircleGraphDescription, {
|
|
33668
|
+
state: state
|
|
33669
|
+
})
|
|
34661
33670
|
};
|
|
34662
33671
|
}
|
|
34663
33672
|
function CircleGraph(props) {
|
|
@@ -34681,38 +33690,26 @@ function CircleGraph(props) {
|
|
|
34681
33690
|
const outerPointsId = id + "-outer-points";
|
|
34682
33691
|
|
|
34683
33692
|
// 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)
|
|
33693
|
+
const {
|
|
33694
|
+
srCircleGraph,
|
|
33695
|
+
srCircleShape,
|
|
33696
|
+
srCircleRadiusPoint,
|
|
33697
|
+
srCircleRadius,
|
|
33698
|
+
srCircleOuterPoints
|
|
33699
|
+
} = describeCircleGraph(graphState, {
|
|
33700
|
+
strings,
|
|
33701
|
+
locale
|
|
34705
33702
|
});
|
|
34706
33703
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34707
33704
|
// Outer circle minimal description
|
|
34708
|
-
"aria-label":
|
|
33705
|
+
"aria-label": srCircleGraph,
|
|
34709
33706
|
"aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
|
|
34710
33707
|
}, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
|
|
34711
33708
|
id: circleId
|
|
34712
33709
|
// Focusable circle aria label reads with every update
|
|
34713
33710
|
// because of the aria-live property in the circle <g>.
|
|
34714
33711
|
,
|
|
34715
|
-
ariaLabel:
|
|
33712
|
+
ariaLabel: srCircleShape
|
|
34716
33713
|
// Aria-describedby describes additional info on focus.
|
|
34717
33714
|
,
|
|
34718
33715
|
ariaDescribedBy: `${radiusId} ${outerPointsId}`,
|
|
@@ -34725,7 +33722,7 @@ function CircleGraph(props) {
|
|
|
34725
33722
|
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1
|
|
34726
33723
|
// Radius point aria label reads with every update.
|
|
34727
33724
|
, {
|
|
34728
|
-
ariaLabel: `${
|
|
33725
|
+
ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
|
|
34729
33726
|
// Aria-describedby describes additional info on focus.
|
|
34730
33727
|
,
|
|
34731
33728
|
ariaDescribedBy: `${outerPointsId}`
|
|
@@ -34748,12 +33745,12 @@ function CircleGraph(props) {
|
|
|
34748
33745
|
style: {
|
|
34749
33746
|
display: "hidden"
|
|
34750
33747
|
}
|
|
34751
|
-
},
|
|
33748
|
+
}, srCircleRadius), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34752
33749
|
id: outerPointsId,
|
|
34753
33750
|
style: {
|
|
34754
33751
|
display: "hidden"
|
|
34755
33752
|
}
|
|
34756
|
-
},
|
|
33753
|
+
}, srCircleOuterPoints));
|
|
34757
33754
|
}
|
|
34758
33755
|
function MovableCircle(props) {
|
|
34759
33756
|
const {
|
|
@@ -34840,6 +33837,65 @@ function crossProduct(as, bs) {
|
|
|
34840
33837
|
}
|
|
34841
33838
|
return result;
|
|
34842
33839
|
}
|
|
33840
|
+
function CircleGraphDescription(_ref) {
|
|
33841
|
+
let {
|
|
33842
|
+
state
|
|
33843
|
+
} = _ref;
|
|
33844
|
+
// The reason that CircleGraphDescription is a component (rather than a
|
|
33845
|
+
// function that returns a string) is because it needs to use a
|
|
33846
|
+
// hook: `usePerseusI18n`.
|
|
33847
|
+
const i18n = usePerseusI18n();
|
|
33848
|
+
const strings = describeCircleGraph(state, i18n);
|
|
33849
|
+
return strings.srCircleInteractiveElement;
|
|
33850
|
+
}
|
|
33851
|
+
|
|
33852
|
+
// Exported for testing
|
|
33853
|
+
function describeCircleGraph(state, i18n) {
|
|
33854
|
+
const {
|
|
33855
|
+
strings,
|
|
33856
|
+
locale
|
|
33857
|
+
} = i18n;
|
|
33858
|
+
const {
|
|
33859
|
+
center,
|
|
33860
|
+
radiusPoint
|
|
33861
|
+
} = state;
|
|
33862
|
+
const radius = getRadius(state);
|
|
33863
|
+
|
|
33864
|
+
// Aria label strings
|
|
33865
|
+
const srCircleGraph = strings.srCircleGraph;
|
|
33866
|
+
const srCircleShape = strings.srCircleShape({
|
|
33867
|
+
centerX: srFormatNumber(center[0], locale),
|
|
33868
|
+
centerY: srFormatNumber(center[1], locale)
|
|
33869
|
+
});
|
|
33870
|
+
const srCircleRadiusPoint = strings.srCircleRadiusPoint({
|
|
33871
|
+
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
33872
|
+
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
33873
|
+
});
|
|
33874
|
+
const srCircleRadius = strings.srCircleRadius({
|
|
33875
|
+
radius
|
|
33876
|
+
});
|
|
33877
|
+
const srCircleOuterPoints = strings.srCircleOuterPoints({
|
|
33878
|
+
point1X: srFormatNumber(center[0] + radius, locale),
|
|
33879
|
+
point1Y: srFormatNumber(center[1], locale),
|
|
33880
|
+
point2X: srFormatNumber(center[0], locale),
|
|
33881
|
+
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
33882
|
+
point3X: srFormatNumber(center[0] - radius, locale),
|
|
33883
|
+
point3Y: srFormatNumber(center[1], locale),
|
|
33884
|
+
point4X: srFormatNumber(center[0], locale),
|
|
33885
|
+
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33886
|
+
});
|
|
33887
|
+
const srCircleInteractiveElement = strings.srInteractiveElements({
|
|
33888
|
+
elements: [srCircleShape, srCircleRadius].join(" ")
|
|
33889
|
+
});
|
|
33890
|
+
return {
|
|
33891
|
+
srCircleGraph,
|
|
33892
|
+
srCircleShape,
|
|
33893
|
+
srCircleRadiusPoint,
|
|
33894
|
+
srCircleRadius,
|
|
33895
|
+
srCircleOuterPoints,
|
|
33896
|
+
srCircleInteractiveElement
|
|
33897
|
+
};
|
|
33898
|
+
}
|
|
34843
33899
|
|
|
34844
33900
|
function renderLinearGraph(state, dispatch) {
|
|
34845
33901
|
return {
|
|
@@ -35833,7 +34889,9 @@ const MafsGraph = props => {
|
|
|
35833
34889
|
labels,
|
|
35834
34890
|
disableKeyboardInteraction: readOnly || !!props.static
|
|
35835
34891
|
}
|
|
35836
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View,
|
|
34892
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34893
|
+
className: "mafs-graph-container"
|
|
34894
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
35837
34895
|
className: "mafs-graph",
|
|
35838
34896
|
style: {
|
|
35839
34897
|
position: "relative",
|
|
@@ -35876,7 +34934,7 @@ const MafsGraph = props => {
|
|
|
35876
34934
|
bottom: 0,
|
|
35877
34935
|
left: 0
|
|
35878
34936
|
}
|
|
35879
|
-
}, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
|
|
34937
|
+
}, (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
34938
|
preserveAspectRatio: false,
|
|
35881
34939
|
viewBox: {
|
|
35882
34940
|
x: state.range[X],
|
|
@@ -35896,7 +34954,7 @@ const MafsGraph = props => {
|
|
|
35896
34954
|
height: height
|
|
35897
34955
|
})),
|
|
35898
34956
|
// 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, {
|
|
34957
|
+
(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
34958
|
lockedFigures: props.lockedFigures,
|
|
35901
34959
|
range: state.range
|
|
35902
34960
|
}))), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
@@ -41426,7 +40484,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41426
40484
|
const solutionSize = getMatrixSize(solution);
|
|
41427
40485
|
const suppliedSize = getMatrixSize(supplied);
|
|
41428
40486
|
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
41429
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40487
|
+
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
41430
40488
|
let message = null;
|
|
41431
40489
|
let incorrect = false;
|
|
41432
40490
|
___default["default"](suppliedSize[0]).times(row => {
|
|
@@ -41436,7 +40494,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41436
40494
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
41437
40495
|
solution[row][col], {
|
|
41438
40496
|
simplify: true
|
|
41439
|
-
}
|
|
40497
|
+
});
|
|
41440
40498
|
const result = validator(supplied[row][col]);
|
|
41441
40499
|
if (result.message) {
|
|
41442
40500
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
@@ -42588,10 +41646,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
42588
41646
|
const firstChar = smiles[0];
|
|
42589
41647
|
const rest = smiles.slice(1);
|
|
42590
41648
|
if (firstChar === "=") {
|
|
42591
|
-
return parse$
|
|
41649
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
42592
41650
|
}
|
|
42593
41651
|
if (firstChar === "#") {
|
|
42594
|
-
return parse$
|
|
41652
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
42595
41653
|
}
|
|
42596
41654
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
42597
41655
|
}
|
|
@@ -42652,8 +41710,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
42652
41710
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
42653
41711
|
parens: newCtx.parens.concat("(")
|
|
42654
41712
|
};
|
|
42655
|
-
const parenExpr = parse$
|
|
42656
|
-
const remainder = parse$
|
|
41713
|
+
const parenExpr = parse$2(rest, parenCtx);
|
|
41714
|
+
const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
42657
41715
|
return [parenExpr].concat(remainder);
|
|
42658
41716
|
}
|
|
42659
41717
|
if (firstChar === ")") {
|
|
@@ -42717,7 +41775,7 @@ function parseAtom(smiles, ctx) {
|
|
|
42717
41775
|
|
|
42718
41776
|
// increment the atom counter and reset the branch counter
|
|
42719
41777
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
42720
|
-
let restOfMolecule = parse$
|
|
41778
|
+
let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
42721
41779
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
42722
41780
|
//TODO(colin): fix this awkwardness.
|
|
42723
41781
|
restOfMolecule = [restOfMolecule];
|
|
@@ -42756,7 +41814,7 @@ function isModifierChar(s) {
|
|
|
42756
41814
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
42757
41815
|
* yet implemented.
|
|
42758
41816
|
*/
|
|
42759
|
-
function parse$
|
|
41817
|
+
function parse$2(smiles, ctx) {
|
|
42760
41818
|
if (!validate(smiles)) {
|
|
42761
41819
|
throw new ParseError$1("Invalid input.");
|
|
42762
41820
|
}
|
|
@@ -42781,7 +41839,7 @@ function parse$1(smiles, ctx) {
|
|
|
42781
41839
|
return parseParenthesizedExpression(smiles, ctx);
|
|
42782
41840
|
}
|
|
42783
41841
|
var SmilesParser = {
|
|
42784
|
-
parse: parse$
|
|
41842
|
+
parse: parse$2,
|
|
42785
41843
|
ParseError: ParseError$1
|
|
42786
41844
|
};
|
|
42787
41845
|
|
|
@@ -42789,7 +41847,7 @@ var SmilesParser = {
|
|
|
42789
41847
|
const {
|
|
42790
41848
|
layout
|
|
42791
41849
|
} = MoleculeLayout;
|
|
42792
|
-
const parse = SmilesParser.parse;
|
|
41850
|
+
const parse$1 = SmilesParser.parse;
|
|
42793
41851
|
const ParseError = SmilesParser.ParseError;
|
|
42794
41852
|
const borderSize = 30;
|
|
42795
41853
|
class Molecule extends React__namespace.Component {
|
|
@@ -42816,7 +41874,7 @@ class Molecule extends React__namespace.Component {
|
|
|
42816
41874
|
try {
|
|
42817
41875
|
this.setState({
|
|
42818
41876
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
42819
|
-
parsedSmiles: parse(smiles),
|
|
41877
|
+
parsedSmiles: parse$1(smiles),
|
|
42820
41878
|
error: null
|
|
42821
41879
|
});
|
|
42822
41880
|
} catch (e) {
|
|
@@ -43029,15 +42087,15 @@ const _label = (graphie, labelStyle, pos, value, base) => {
|
|
|
43029
42087
|
return graphie.label([pos, -0.53], Math.round(value * 100) / 100, "center");
|
|
43030
42088
|
}
|
|
43031
42089
|
if (labelStyle === "improper") {
|
|
43032
|
-
const frac = KhanMath.toFraction(value);
|
|
42090
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43033
42091
|
return graphie.label([pos, -0.17], formatImproper(frac[0], frac[1]), "below");
|
|
43034
42092
|
}
|
|
43035
42093
|
if (labelStyle === "mixed") {
|
|
43036
|
-
const frac = KhanMath.toFraction(value);
|
|
42094
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43037
42095
|
return graphie.label([pos, -0.17], formatMixed(frac[0], frac[1]), "below");
|
|
43038
42096
|
}
|
|
43039
42097
|
if (labelStyle === "non-reduced") {
|
|
43040
|
-
const frac = KhanMath.toFraction(value);
|
|
42098
|
+
const frac = kmath.KhanMath.toFraction(value);
|
|
43041
42099
|
return graphie.label([pos, -0.17], formatNonReduced(frac[0], frac[1], base), "below");
|
|
43042
42100
|
}
|
|
43043
42101
|
};
|
|
@@ -43073,7 +42131,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
43073
42131
|
}
|
|
43074
42132
|
const getDenom = x => kmath.number.toFraction(x)[1];
|
|
43075
42133
|
const denoms = ___default["default"].map(fractions, getDenom);
|
|
43076
|
-
base = ___default["default"].reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
42134
|
+
base = ___default["default"].reduce(denoms, (x, y) => kmath.KhanMath.getLCM(x, y));
|
|
43077
42135
|
} else {
|
|
43078
42136
|
base = undefined;
|
|
43079
42137
|
}
|
|
@@ -44609,7 +43667,7 @@ class Plotter extends React__namespace.Component {
|
|
|
44609
43667
|
// If we have isMobile, we skip the 0 label.
|
|
44610
43668
|
const initialY = isMobile ? c.scaleY : 0;
|
|
44611
43669
|
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 */);
|
|
43670
|
+
graphie.label([0, y], kmath.KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
|
|
44613
43671
|
|
|
44614
43672
|
graphie.style({
|
|
44615
43673
|
stroke: isMobile ? "#e9ebec" : "#000",
|
|
@@ -45477,7 +44535,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45477
44535
|
message: null
|
|
45478
44536
|
};
|
|
45479
44537
|
}
|
|
45480
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44538
|
+
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
45481
44539
|
let message = null;
|
|
45482
44540
|
const allCorrect = solution.every(function (rowSolution) {
|
|
45483
44541
|
for (let i = 0; i < supplied.length; i++) {
|
|
@@ -45486,7 +44544,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45486
44544
|
const cellSolution = rowSolution[i];
|
|
45487
44545
|
const validator = createValidator(cellSolution, {
|
|
45488
44546
|
simplify: true
|
|
45489
|
-
}
|
|
44547
|
+
});
|
|
45490
44548
|
const result = validator(cellSupplied);
|
|
45491
44549
|
if (result.message) {
|
|
45492
44550
|
message = result.message;
|
|
@@ -45909,7 +44967,7 @@ const init = function () {
|
|
|
45909
44967
|
|
|
45910
44968
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45911
44969
|
const libName = "@khanacademy/perseus";
|
|
45912
|
-
const libVersion = "
|
|
44970
|
+
const libVersion = "50.0.0";
|
|
45913
44971
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45914
44972
|
|
|
45915
44973
|
const apiVersion = {
|
|
@@ -46849,7 +45907,7 @@ const traverseRenderer$1 = function (rendererOptions, contentCallback, deepWidge
|
|
|
46849
45907
|
newContent = modifiedContent;
|
|
46850
45908
|
}
|
|
46851
45909
|
}
|
|
46852
|
-
const newWidgets = mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
45910
|
+
const newWidgets = perseusCore.mapObject(rendererOptions.widgets || {}, function (widgetInfo, widgetId) {
|
|
46853
45911
|
// Widgets without info or a type are empty widgets, and
|
|
46854
45912
|
// should always be renderable. It's also annoying to write
|
|
46855
45913
|
// checks for this everywhere, so we just filter them out once and
|
|
@@ -48000,6 +47058,69 @@ function all(results) {
|
|
|
48000
47058
|
return success(values);
|
|
48001
47059
|
}
|
|
48002
47060
|
|
|
47061
|
+
class ErrorTrackingParseContext {
|
|
47062
|
+
constructor(path) {
|
|
47063
|
+
this.path = path;
|
|
47064
|
+
}
|
|
47065
|
+
failure(expected, badValue) {
|
|
47066
|
+
return failure([{
|
|
47067
|
+
expected: wrapInArray(expected),
|
|
47068
|
+
badValue,
|
|
47069
|
+
path: this.path
|
|
47070
|
+
}]);
|
|
47071
|
+
}
|
|
47072
|
+
forSubtree(key) {
|
|
47073
|
+
return new ErrorTrackingParseContext([...this.path, key]);
|
|
47074
|
+
}
|
|
47075
|
+
success(value) {
|
|
47076
|
+
return success(value);
|
|
47077
|
+
}
|
|
47078
|
+
}
|
|
47079
|
+
function wrapInArray(a) {
|
|
47080
|
+
return Array.isArray(a) ? a : [a];
|
|
47081
|
+
}
|
|
47082
|
+
|
|
47083
|
+
function formatPath(path) {
|
|
47084
|
+
return "(root)" + path.map(formatPathSegment).join("");
|
|
47085
|
+
}
|
|
47086
|
+
function formatPathSegment(segment) {
|
|
47087
|
+
if (typeof segment === "string") {
|
|
47088
|
+
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
47089
|
+
}
|
|
47090
|
+
return `[${segment.toString()}]`;
|
|
47091
|
+
}
|
|
47092
|
+
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
47093
|
+
|
|
47094
|
+
function message(failure) {
|
|
47095
|
+
const expected = conjoin(failure.expected);
|
|
47096
|
+
const path = formatPath(failure.path);
|
|
47097
|
+
const badValue = JSON.stringify(failure.badValue);
|
|
47098
|
+
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
47099
|
+
}
|
|
47100
|
+
function conjoin(items) {
|
|
47101
|
+
switch (items.length) {
|
|
47102
|
+
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
47103
|
+
case 1:
|
|
47104
|
+
return items[0];
|
|
47105
|
+
case 2:
|
|
47106
|
+
return items.join(" or ");
|
|
47107
|
+
default:
|
|
47108
|
+
{
|
|
47109
|
+
const allButLast = items.slice(0, items.length - 1);
|
|
47110
|
+
const last = items[items.length - 1];
|
|
47111
|
+
return allButLast.join(", ") + ", or " + last;
|
|
47112
|
+
}
|
|
47113
|
+
}
|
|
47114
|
+
}
|
|
47115
|
+
|
|
47116
|
+
function parse(value, parser) {
|
|
47117
|
+
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
47118
|
+
if (isFailure(result)) {
|
|
47119
|
+
return failure(result.detail.map(message).join("; "));
|
|
47120
|
+
}
|
|
47121
|
+
return result;
|
|
47122
|
+
}
|
|
47123
|
+
|
|
48003
47124
|
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48004
47125
|
|
|
48005
47126
|
function array(elementParser) {
|
|
@@ -48285,7 +47406,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
48285
47406
|
})),
|
|
48286
47407
|
showEditor: boolean,
|
|
48287
47408
|
showButtons: boolean,
|
|
48288
|
-
width: number,
|
|
48289
47409
|
height: number,
|
|
48290
47410
|
static: defaulted(boolean, () => false)
|
|
48291
47411
|
}));
|
|
@@ -48474,25 +47594,6 @@ function migrateV0ToV1(widget) {
|
|
|
48474
47594
|
}
|
|
48475
47595
|
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
48476
47596
|
|
|
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
47597
|
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
48497
47598
|
const parseGradedGroupWidgetOptions = object({
|
|
48498
47599
|
title: defaulted(string, () => ""),
|
|
@@ -48563,27 +47664,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
|
48563
47664
|
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
48564
47665
|
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
48565
47666
|
type: constant("absolute_value"),
|
|
48566
|
-
coords: pairOfPoints
|
|
47667
|
+
coords: nullable(pairOfPoints)
|
|
48567
47668
|
})).withBranch("exponential", object({
|
|
48568
47669
|
type: constant("exponential"),
|
|
48569
47670
|
asymptote: pairOfPoints,
|
|
48570
|
-
coords: pairOfPoints
|
|
47671
|
+
coords: nullable(pairOfPoints)
|
|
48571
47672
|
})).withBranch("linear", object({
|
|
48572
47673
|
type: constant("linear"),
|
|
48573
|
-
coords:
|
|
47674
|
+
coords: nullable(pairOfPoints)
|
|
48574
47675
|
})).withBranch("logarithm", object({
|
|
48575
47676
|
type: constant("logarithm"),
|
|
48576
47677
|
asymptote: pairOfPoints,
|
|
48577
|
-
coords: pairOfPoints
|
|
47678
|
+
coords: nullable(pairOfPoints)
|
|
48578
47679
|
})).withBranch("quadratic", object({
|
|
48579
47680
|
type: constant("quadratic"),
|
|
48580
|
-
coords: pairOfPoints
|
|
47681
|
+
coords: nullable(pairOfPoints)
|
|
48581
47682
|
})).withBranch("sinusoid", object({
|
|
48582
47683
|
type: constant("sinusoid"),
|
|
48583
|
-
coords: pairOfPoints
|
|
47684
|
+
coords: nullable(pairOfPoints)
|
|
48584
47685
|
})).withBranch("tangent", object({
|
|
48585
47686
|
type: constant("tangent"),
|
|
48586
|
-
coords: pairOfPoints
|
|
47687
|
+
coords: nullable(pairOfPoints)
|
|
48587
47688
|
})).parser,
|
|
48588
47689
|
graph: object({
|
|
48589
47690
|
backgroundImage: object({
|
|
@@ -48619,13 +47720,13 @@ const parseGroupWidget = parseWidget(constant("group"),
|
|
|
48619
47720
|
|
|
48620
47721
|
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
48621
47722
|
url: string,
|
|
48622
|
-
settings: array(object({
|
|
47723
|
+
settings: optional(array(object({
|
|
48623
47724
|
name: string,
|
|
48624
47725
|
value: string
|
|
48625
|
-
})),
|
|
47726
|
+
}))),
|
|
48626
47727
|
width: union(number).or(string).parser,
|
|
48627
47728
|
height: union(number).or(string).parser,
|
|
48628
|
-
allowFullScreen: boolean,
|
|
47729
|
+
allowFullScreen: defaulted(boolean, () => false),
|
|
48629
47730
|
allowTopNavigation: optional(boolean),
|
|
48630
47731
|
static: defaulted(boolean, () => false)
|
|
48631
47732
|
}));
|
|
@@ -48698,10 +47799,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48698
47799
|
|
|
48699
47800
|
const pairOfNumbers$1 = pair(number, number);
|
|
48700
47801
|
const stringOrEmpty = defaulted(string, () => "");
|
|
48701
|
-
const
|
|
47802
|
+
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
48702
47803
|
const parseFunctionElement = object({
|
|
48703
|
-
type:
|
|
48704
|
-
key:
|
|
47804
|
+
type: constant("function"),
|
|
47805
|
+
key: parseKey,
|
|
48705
47806
|
options: object({
|
|
48706
47807
|
value: string,
|
|
48707
47808
|
funcName: string,
|
|
@@ -48712,10 +47813,9 @@ const parseFunctionElement = object({
|
|
|
48712
47813
|
strokeWidth: number
|
|
48713
47814
|
})
|
|
48714
47815
|
});
|
|
48715
|
-
const parseLabelType = constant("label");
|
|
48716
47816
|
const parseLabelElement = object({
|
|
48717
|
-
type:
|
|
48718
|
-
key:
|
|
47817
|
+
type: constant("label"),
|
|
47818
|
+
key: parseKey,
|
|
48719
47819
|
options: object({
|
|
48720
47820
|
label: string,
|
|
48721
47821
|
color: string,
|
|
@@ -48723,10 +47823,9 @@ const parseLabelElement = object({
|
|
|
48723
47823
|
coordY: string
|
|
48724
47824
|
})
|
|
48725
47825
|
});
|
|
48726
|
-
const parseLineType = constant("line");
|
|
48727
47826
|
const parseLineElement = object({
|
|
48728
|
-
type:
|
|
48729
|
-
key:
|
|
47827
|
+
type: constant("line"),
|
|
47828
|
+
key: parseKey,
|
|
48730
47829
|
options: object({
|
|
48731
47830
|
color: string,
|
|
48732
47831
|
startX: string,
|
|
@@ -48738,10 +47837,9 @@ const parseLineElement = object({
|
|
|
48738
47837
|
arrows: string
|
|
48739
47838
|
})
|
|
48740
47839
|
});
|
|
48741
|
-
const parseMovableLineType = constant("movable-line");
|
|
48742
47840
|
const parseMovableLineElement = object({
|
|
48743
|
-
type:
|
|
48744
|
-
key:
|
|
47841
|
+
type: constant("movable-line"),
|
|
47842
|
+
key: parseKey,
|
|
48745
47843
|
options: object({
|
|
48746
47844
|
startX: string,
|
|
48747
47845
|
startY: string,
|
|
@@ -48758,10 +47856,9 @@ const parseMovableLineElement = object({
|
|
|
48758
47856
|
constraintYMax: string
|
|
48759
47857
|
})
|
|
48760
47858
|
});
|
|
48761
|
-
const parseMovablePointType = constant("movable-point");
|
|
48762
47859
|
const parseMovablePointElement = object({
|
|
48763
|
-
type:
|
|
48764
|
-
key:
|
|
47860
|
+
type: constant("movable-point"),
|
|
47861
|
+
key: parseKey,
|
|
48765
47862
|
options: object({
|
|
48766
47863
|
startX: string,
|
|
48767
47864
|
startY: string,
|
|
@@ -48775,10 +47872,9 @@ const parseMovablePointElement = object({
|
|
|
48775
47872
|
constraintYMax: stringOrEmpty
|
|
48776
47873
|
})
|
|
48777
47874
|
});
|
|
48778
|
-
const parseParametricType = constant("parametric");
|
|
48779
47875
|
const parseParametricElement = object({
|
|
48780
|
-
type:
|
|
48781
|
-
key:
|
|
47876
|
+
type: constant("parametric"),
|
|
47877
|
+
key: parseKey,
|
|
48782
47878
|
options: object({
|
|
48783
47879
|
x: string,
|
|
48784
47880
|
y: string,
|
|
@@ -48789,20 +47885,18 @@ const parseParametricElement = object({
|
|
|
48789
47885
|
strokeWidth: number
|
|
48790
47886
|
})
|
|
48791
47887
|
});
|
|
48792
|
-
const parsePointType = constant("point");
|
|
48793
47888
|
const parsePointElement = object({
|
|
48794
|
-
type:
|
|
48795
|
-
key:
|
|
47889
|
+
type: constant("point"),
|
|
47890
|
+
key: parseKey,
|
|
48796
47891
|
options: object({
|
|
48797
47892
|
color: string,
|
|
48798
47893
|
coordX: string,
|
|
48799
47894
|
coordY: string
|
|
48800
47895
|
})
|
|
48801
47896
|
});
|
|
48802
|
-
const parseRectangleType = constant("rectangle");
|
|
48803
47897
|
const parseRectangleElement = object({
|
|
48804
|
-
type:
|
|
48805
|
-
key:
|
|
47898
|
+
type: constant("rectangle"),
|
|
47899
|
+
key: parseKey,
|
|
48806
47900
|
options: object({
|
|
48807
47901
|
color: string,
|
|
48808
47902
|
coordX: string,
|
|
@@ -48923,7 +48017,7 @@ const parsePerseusGraphTypeSinusoid = object({
|
|
|
48923
48017
|
coord: optional(pairOfNumbers)
|
|
48924
48018
|
});
|
|
48925
48019
|
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);
|
|
48020
|
+
const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
|
|
48927
48021
|
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48928
48022
|
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48929
48023
|
const parseLockedLabelType = object({
|
|
@@ -48948,8 +48042,8 @@ const parseLockedLineType = object({
|
|
|
48948
48042
|
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48949
48043
|
color: parseLockedFigureColor,
|
|
48950
48044
|
lineStyle: parseLockedLineStyle,
|
|
48951
|
-
showPoint1: boolean,
|
|
48952
|
-
showPoint2: boolean,
|
|
48045
|
+
showPoint1: defaulted(boolean, () => false),
|
|
48046
|
+
showPoint2: defaulted(boolean, () => false),
|
|
48953
48047
|
// TODO(benchristel): default labels to empty array?
|
|
48954
48048
|
labels: optional(array(parseLockedLabelType)),
|
|
48955
48049
|
ariaLabel: optional(string)
|
|
@@ -48996,7 +48090,7 @@ const parseLockedFunctionType = object({
|
|
|
48996
48090
|
labels: optional(array(parseLockedLabelType)),
|
|
48997
48091
|
ariaLabel: optional(string)
|
|
48998
48092
|
});
|
|
48999
|
-
const parseLockedFigure =
|
|
48093
|
+
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
48094
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
49001
48095
|
step: pairOfNumbers,
|
|
49002
48096
|
// TODO(benchristel): rather than making gridStep and snapStep
|
|
@@ -49064,7 +48158,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
|
|
|
49064
48158
|
}));
|
|
49065
48159
|
|
|
49066
48160
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
49067
|
-
image
|
|
48161
|
+
// The default value for image comes from measurer.tsx.
|
|
48162
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48163
|
+
// defaults here.
|
|
48164
|
+
image: defaulted(parsePerseusImageBackground, () => ({
|
|
48165
|
+
url: null,
|
|
48166
|
+
top: 0,
|
|
48167
|
+
left: 0
|
|
48168
|
+
})),
|
|
49068
48169
|
showProtractor: boolean,
|
|
49069
48170
|
showRuler: boolean,
|
|
49070
48171
|
rulerLabel: string,
|
|
@@ -49173,11 +48274,17 @@ const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), objec
|
|
|
49173
48274
|
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
49174
48275
|
labels: array(string),
|
|
49175
48276
|
categories: array(string),
|
|
49176
|
-
type: enumeration(...plotterPlotTypes),
|
|
48277
|
+
type: enumeration(...perseusCore.plotterPlotTypes),
|
|
49177
48278
|
maxY: number,
|
|
49178
|
-
scaleY
|
|
48279
|
+
// The default value for scaleY comes from plotter.tsx.
|
|
48280
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48281
|
+
// defaults here.
|
|
48282
|
+
scaleY: defaulted(number, () => 1),
|
|
49179
48283
|
labelInterval: optional(nullable(number)),
|
|
49180
|
-
snapsPerLine
|
|
48284
|
+
// The default value for snapsPerLine comes from plotter.tsx.
|
|
48285
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
48286
|
+
// defaults here.
|
|
48287
|
+
snapsPerLine: defaulted(number, () => 2),
|
|
49181
48288
|
starting: array(number),
|
|
49182
48289
|
correct: array(number),
|
|
49183
48290
|
picUrl: optional(nullable(string)),
|
|
@@ -49255,12 +48362,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
49255
48362
|
return ctx.success(widgetsMap);
|
|
49256
48363
|
};
|
|
49257
48364
|
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
49258
|
-
let [
|
|
49259
|
-
const
|
|
49260
|
-
if (isFailure(
|
|
49261
|
-
return
|
|
48365
|
+
let [id, widget] = _ref;
|
|
48366
|
+
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
48367
|
+
if (isFailure(idComponentsResult)) {
|
|
48368
|
+
return idComponentsResult;
|
|
49262
48369
|
}
|
|
49263
|
-
const [type,
|
|
48370
|
+
const [type, n] = idComponentsResult.value;
|
|
49264
48371
|
function parseAndAssign(key, parse) {
|
|
49265
48372
|
const widgetResult = parse(widget, ctx);
|
|
49266
48373
|
if (isFailure(widgetResult)) {
|
|
@@ -49271,88 +48378,88 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
|
49271
48378
|
}
|
|
49272
48379
|
switch (type) {
|
|
49273
48380
|
case "categorizer":
|
|
49274
|
-
return parseAndAssign(`categorizer ${
|
|
48381
|
+
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
49275
48382
|
case "cs-program":
|
|
49276
|
-
return parseAndAssign(`cs-program ${
|
|
48383
|
+
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
49277
48384
|
case "definition":
|
|
49278
|
-
return parseAndAssign(`definition ${
|
|
48385
|
+
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
49279
48386
|
case "dropdown":
|
|
49280
|
-
return parseAndAssign(`dropdown ${
|
|
48387
|
+
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
49281
48388
|
case "explanation":
|
|
49282
|
-
return parseAndAssign(`explanation ${
|
|
48389
|
+
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
49283
48390
|
case "expression":
|
|
49284
|
-
return parseAndAssign(`expression ${
|
|
48391
|
+
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
49285
48392
|
case "grapher":
|
|
49286
|
-
return parseAndAssign(`grapher ${
|
|
48393
|
+
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
49287
48394
|
case "group":
|
|
49288
|
-
return parseAndAssign(`group ${
|
|
48395
|
+
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
49289
48396
|
case "graded-group":
|
|
49290
|
-
return parseAndAssign(`graded-group ${
|
|
48397
|
+
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
49291
48398
|
case "graded-group-set":
|
|
49292
|
-
return parseAndAssign(`graded-group-set ${
|
|
48399
|
+
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
49293
48400
|
case "iframe":
|
|
49294
|
-
return parseAndAssign(`iframe ${
|
|
48401
|
+
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
49295
48402
|
case "image":
|
|
49296
|
-
return parseAndAssign(`image ${
|
|
48403
|
+
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
49297
48404
|
case "input-number":
|
|
49298
|
-
return parseAndAssign(`input-number ${
|
|
48405
|
+
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
49299
48406
|
case "interaction":
|
|
49300
|
-
return parseAndAssign(`interaction ${
|
|
48407
|
+
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
49301
48408
|
case "interactive-graph":
|
|
49302
|
-
return parseAndAssign(`interactive-graph ${
|
|
48409
|
+
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
49303
48410
|
case "label-image":
|
|
49304
|
-
return parseAndAssign(`label-image ${
|
|
48411
|
+
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
49305
48412
|
case "matcher":
|
|
49306
|
-
return parseAndAssign(`matcher ${
|
|
48413
|
+
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
49307
48414
|
case "matrix":
|
|
49308
|
-
return parseAndAssign(`matrix ${
|
|
48415
|
+
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
49309
48416
|
case "measurer":
|
|
49310
|
-
return parseAndAssign(`measurer ${
|
|
48417
|
+
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
49311
48418
|
case "molecule-renderer":
|
|
49312
|
-
return parseAndAssign(`molecule-renderer ${
|
|
48419
|
+
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
49313
48420
|
case "number-line":
|
|
49314
|
-
return parseAndAssign(`number-line ${
|
|
48421
|
+
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
49315
48422
|
case "numeric-input":
|
|
49316
|
-
return parseAndAssign(`numeric-input ${
|
|
48423
|
+
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
49317
48424
|
case "orderer":
|
|
49318
|
-
return parseAndAssign(`orderer ${
|
|
48425
|
+
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
49319
48426
|
case "passage":
|
|
49320
|
-
return parseAndAssign(`passage ${
|
|
48427
|
+
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
49321
48428
|
case "passage-ref":
|
|
49322
|
-
return parseAndAssign(`passage-ref ${
|
|
48429
|
+
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
49323
48430
|
case "passage-ref-target":
|
|
49324
48431
|
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
49325
48432
|
// used in test content. See:
|
|
49326
48433
|
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
49327
|
-
return parseAndAssign(`passage-ref-target ${
|
|
48434
|
+
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
49328
48435
|
case "phet-simulation":
|
|
49329
|
-
return parseAndAssign(`phet-simulation ${
|
|
48436
|
+
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
49330
48437
|
case "plotter":
|
|
49331
|
-
return parseAndAssign(`plotter ${
|
|
48438
|
+
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
49332
48439
|
case "python-program":
|
|
49333
|
-
return parseAndAssign(`python-program ${
|
|
48440
|
+
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
49334
48441
|
case "radio":
|
|
49335
|
-
return parseAndAssign(`radio ${
|
|
48442
|
+
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
49336
48443
|
case "sorter":
|
|
49337
|
-
return parseAndAssign(`sorter ${
|
|
48444
|
+
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
49338
48445
|
case "table":
|
|
49339
|
-
return parseAndAssign(`table ${
|
|
48446
|
+
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
49340
48447
|
case "video":
|
|
49341
|
-
return parseAndAssign(`video ${
|
|
48448
|
+
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
49342
48449
|
case "sequence":
|
|
49343
48450
|
// sequence is a deprecated widget type, and the corresponding
|
|
49344
48451
|
// widget component no longer exists.
|
|
49345
|
-
return parseAndAssign(`sequence ${
|
|
48452
|
+
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
49346
48453
|
case "lights-puzzle":
|
|
49347
|
-
return parseAndAssign(`lights-puzzle ${
|
|
48454
|
+
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
49348
48455
|
case "simulator":
|
|
49349
|
-
return parseAndAssign(`simulator ${
|
|
48456
|
+
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
49350
48457
|
case "transformer":
|
|
49351
|
-
return parseAndAssign(`transformer ${
|
|
48458
|
+
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
49352
48459
|
default:
|
|
49353
48460
|
if (getWidget(type)) {
|
|
49354
48461
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
49355
|
-
return parseAndAssign(`${type} ${
|
|
48462
|
+
return parseAndAssign(`${type} ${n}`, any);
|
|
49356
48463
|
}
|
|
49357
48464
|
return ctx.failure("a valid widget type", type);
|
|
49358
48465
|
}
|
|
@@ -49364,11 +48471,32 @@ const parseDeprecatedWidget = parseWidget(
|
|
|
49364
48471
|
object({}));
|
|
49365
48472
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
49366
48473
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
49367
|
-
return ctx.failure("
|
|
48474
|
+
return ctx.failure("a string representing a positive integer", rawValue);
|
|
49368
48475
|
}
|
|
49369
48476
|
return ctx.success(+rawValue);
|
|
49370
48477
|
};
|
|
49371
|
-
const
|
|
48478
|
+
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
48479
|
+
|
|
48480
|
+
const parsePerseusRenderer = defaulted(object({
|
|
48481
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
48482
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
48483
|
+
content: defaulted(string, () => ""),
|
|
48484
|
+
// This module has an import cycle with parseWidgetsMap, because the
|
|
48485
|
+
// `group` widget can contain another renderer.
|
|
48486
|
+
// The anonymous function below ensures that we don't try to access
|
|
48487
|
+
// parseWidgetsMap before it's defined.
|
|
48488
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48489
|
+
metadata: optional(array(string)),
|
|
48490
|
+
images: parseImages
|
|
48491
|
+
}),
|
|
48492
|
+
// Default value
|
|
48493
|
+
() => ({
|
|
48494
|
+
content: "",
|
|
48495
|
+
widgets: {},
|
|
48496
|
+
images: {}
|
|
48497
|
+
}));
|
|
48498
|
+
|
|
48499
|
+
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
49372
48500
|
|
|
49373
48501
|
const parseHint = object({
|
|
49374
48502
|
replace: optional(boolean),
|
|
@@ -49378,10 +48506,10 @@ const parseHint = object({
|
|
|
49378
48506
|
images: parseImages
|
|
49379
48507
|
});
|
|
49380
48508
|
|
|
49381
|
-
object({
|
|
48509
|
+
const parsePerseusItem$1 = object({
|
|
49382
48510
|
question: parsePerseusRenderer,
|
|
49383
48511
|
hints: defaulted(array(parseHint), () => []),
|
|
49384
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
48512
|
+
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
|
|
49385
48513
|
itemDataVersion: optional(object({
|
|
49386
48514
|
major: number,
|
|
49387
48515
|
minor: number
|
|
@@ -49417,6 +48545,7 @@ function migrateAnswerArea(rawValue, ctx) {
|
|
|
49417
48545
|
* Why not just use JSON.parse? We want:
|
|
49418
48546
|
* - To make sure types are correct
|
|
49419
48547
|
* - To give us a central place to validate/transform output if needed
|
|
48548
|
+
* @deprecated - use parseAndMigratePerseusItem instead
|
|
49420
48549
|
* @param {string} json - the stringified PerseusItem JSON
|
|
49421
48550
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
49422
48551
|
*/
|
|
@@ -49428,6 +48557,60 @@ function parsePerseusItem(json) {
|
|
|
49428
48557
|
}
|
|
49429
48558
|
throw new Error("Something went wrong.");
|
|
49430
48559
|
}
|
|
48560
|
+
/**
|
|
48561
|
+
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
48562
|
+
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
48563
|
+
* data.
|
|
48564
|
+
*
|
|
48565
|
+
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
48566
|
+
* failure, it will contain an error message describing where in the tree
|
|
48567
|
+
* parsing failed.
|
|
48568
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48569
|
+
*/
|
|
48570
|
+
function parseAndMigratePerseusItem(json) {
|
|
48571
|
+
throwErrorIfCheatingDetected();
|
|
48572
|
+
const object = JSON.parse(json);
|
|
48573
|
+
const result = parse(object, parsePerseusItem$1);
|
|
48574
|
+
if (isFailure(result)) {
|
|
48575
|
+
return failure({
|
|
48576
|
+
message: result.detail,
|
|
48577
|
+
invalidObject: object
|
|
48578
|
+
});
|
|
48579
|
+
}
|
|
48580
|
+
return result;
|
|
48581
|
+
}
|
|
48582
|
+
|
|
48583
|
+
/**
|
|
48584
|
+
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
48585
|
+
* latest schema, and runtime-typechecks the result.
|
|
48586
|
+
*
|
|
48587
|
+
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
48588
|
+
* failure, it will contain an error message describing where in the tree
|
|
48589
|
+
* parsing failed.
|
|
48590
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48591
|
+
*/
|
|
48592
|
+
function parseAndMigratePerseusArticle(json) {
|
|
48593
|
+
throwErrorIfCheatingDetected();
|
|
48594
|
+
const object = JSON.parse(json);
|
|
48595
|
+
const result = parse(object, parsePerseusArticle);
|
|
48596
|
+
if (isFailure(result)) {
|
|
48597
|
+
return failure({
|
|
48598
|
+
message: result.detail,
|
|
48599
|
+
invalidObject: object
|
|
48600
|
+
});
|
|
48601
|
+
}
|
|
48602
|
+
return result;
|
|
48603
|
+
}
|
|
48604
|
+
|
|
48605
|
+
/**
|
|
48606
|
+
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
48607
|
+
*/
|
|
48608
|
+
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
48609
|
+
function throwErrorIfCheatingDetected() {
|
|
48610
|
+
if (!isRealJSONParse(JSON.parse)) {
|
|
48611
|
+
throw new Error("Something went wrong.");
|
|
48612
|
+
}
|
|
48613
|
+
}
|
|
49431
48614
|
|
|
49432
48615
|
const genericPerseusItemData = {
|
|
49433
48616
|
question: {
|
|
@@ -49666,15 +48849,12 @@ exports.HintRenderer = HintRenderer;
|
|
|
49666
48849
|
exports.HintsRenderer = HintsRenderer;
|
|
49667
48850
|
exports.InputNumber = InputNumber$1;
|
|
49668
48851
|
exports.InteractiveGraphWidget = InteractiveGraph$1;
|
|
49669
|
-
exports.ItemExtras = ItemExtras;
|
|
49670
48852
|
exports.JiptParagraphs = JiptParagraphs;
|
|
49671
48853
|
exports.KhanColors = KhanColors;
|
|
49672
|
-
exports.KhanMath = KhanMath;
|
|
49673
48854
|
exports.LoadingContext = context;
|
|
49674
48855
|
exports.Log = Log;
|
|
49675
48856
|
exports.MatrixWidget = Matrix$1;
|
|
49676
48857
|
exports.NumericInput = NumericInput$1;
|
|
49677
|
-
exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
|
|
49678
48858
|
exports.PerseusI18nContext = PerseusI18nContext;
|
|
49679
48859
|
exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
|
|
49680
48860
|
exports.PerseusMarkdown = PerseusMarkdown;
|
|
@@ -49730,20 +48910,21 @@ exports.iconTrash = iconTrash;
|
|
|
49730
48910
|
exports.init = init;
|
|
49731
48911
|
exports.injectWidgets = injectWidgets;
|
|
49732
48912
|
exports.interactiveSizes = interactiveSizes$1;
|
|
48913
|
+
exports.isFailure = isFailure;
|
|
49733
48914
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
48915
|
+
exports.isSuccess = isSuccess;
|
|
49734
48916
|
exports.isWidgetIdInContent = isWidgetIdInContent;
|
|
49735
48917
|
exports.isWrongAnswerSupported = isWrongAnswerSupported;
|
|
49736
48918
|
exports.itemDataVersion = itemDataVersion;
|
|
49737
48919
|
exports.itemVersion = ItemVersion;
|
|
49738
48920
|
exports.libVersion = libVersion;
|
|
49739
|
-
exports.lockedFigureColors = lockedFigureColors;
|
|
49740
|
-
exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
49741
48921
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49742
48922
|
exports.mathOnlyParser = mathOnlyParser;
|
|
49743
48923
|
exports.mediaQueries = mediaQueries;
|
|
48924
|
+
exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
|
|
48925
|
+
exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
|
|
49744
48926
|
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
49745
48927
|
exports.parsePerseusItem = parsePerseusItem;
|
|
49746
|
-
exports.plotterPlotTypes = plotterPlotTypes;
|
|
49747
48928
|
exports.preprocessTex = preprocessTex;
|
|
49748
48929
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
49749
48930
|
exports.scorePerseusItem = scorePerseusItem;
|