@khanacademy/perseus 49.2.2 → 50.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/article-renderer.d.ts +2 -6
- package/dist/components/graph.d.ts +1 -1
- package/dist/components/graphie.d.ts +1 -1
- package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +1 -4
- package/dist/components/highlighting/ui/highlighting-ui.d.ts +1 -4
- package/dist/components/i18n-context.d.ts +1 -1
- package/dist/components/input-with-examples.d.ts +1 -4
- package/dist/components/math-input.d.ts +2 -6
- package/dist/components/number-input.d.ts +2 -5
- package/dist/components/svg-image.d.ts +1 -1
- package/dist/es/index.js +573 -1354
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +19 -1
- package/dist/es/strings.js.map +1 -1
- package/dist/hint-renderer.d.ts +1 -4
- package/dist/hints-renderer.d.ts +2 -5
- package/dist/index.d.ts +5 -7
- package/dist/index.js +631 -1415
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +1 -1
- package/dist/renderer.d.ts +2 -5
- package/dist/server-item-renderer.d.ts +3 -7
- package/dist/strings.d.ts +8 -0
- package/dist/strings.js +19 -0
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +11 -2
- package/dist/util/extract-perseus-data.d.ts +1 -1
- package/dist/util/parse-perseus-json/index.d.ts +33 -4
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +3 -3
- package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +1 -1
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +1 -1
- package/dist/util/test-utils.d.ts +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/validation.types.d.ts +9 -3
- package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/prompt-types.d.ts +2 -1
- package/dist/widget-container.d.ts +1 -1
- package/dist/widget-type-utils.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +4 -5
- package/dist/widgets/categorizer/categorizer.util.d.ts +17 -0
- package/dist/widgets/cs-program/cs-program.d.ts +1 -1
- package/dist/widgets/definition/definition.d.ts +2 -5
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -4
- package/dist/widgets/dropdown/dropdown.d.ts +2 -5
- package/dist/widgets/explanation/explanation.d.ts +2 -5
- package/dist/widgets/expression/expression.d.ts +5 -8
- package/dist/widgets/graded-group/graded-group-answer-bar.d.ts +1 -4
- package/dist/widgets/graded-group/graded-group.d.ts +2 -5
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
- package/dist/widgets/grapher/grapher.d.ts +2 -2
- package/dist/widgets/group/group.d.ts +2 -5
- package/dist/widgets/iframe/iframe.d.ts +1 -1
- package/dist/widgets/image/image.d.ts +2 -5
- package/dist/widgets/input-number/input-number.d.ts +2 -6
- package/dist/widgets/interaction/interaction.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +4 -6
- package/dist/widgets/interactive-graphs/backgrounds/grid.d.ts +2 -2
- package/dist/widgets/interactive-graphs/backgrounds/legacy-grid.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-labels-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/circle.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/components/angle-indicators.d.ts +1 -1
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +2 -2
- package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-label.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +1 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +1 -1
- package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +2 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
- package/dist/widgets/label-image/label-image.d.ts +2 -5
- package/dist/widgets/label-image/marker.d.ts +1 -4
- package/dist/widgets/matcher/matcher.d.ts +2 -5
- package/dist/widgets/matrix/matrix.d.ts +2 -5
- package/dist/widgets/measurer/measurer.d.ts +1 -1
- package/dist/widgets/mock-widgets/index.d.ts +2 -0
- package/dist/widgets/mock-widgets/mock-asset-loading-widget.d.ts +17 -0
- package/dist/widgets/mock-widgets/mock-widget.d.ts +47 -0
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +5 -0
- package/dist/widgets/molecule/molecule.d.ts +1 -1
- package/dist/widgets/number-line/number-line.d.ts +1 -4
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -6
- package/dist/widgets/orderer/orderer.d.ts +1 -1
- package/dist/widgets/passage/passage.d.ts +2 -5
- package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -5
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -5
- package/dist/widgets/plotter/plotter.d.ts +2 -5
- package/dist/widgets/radio/base-radio.d.ts +1 -1
- package/dist/widgets/radio/radio-component.d.ts +2 -5
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -1
- package/dist/widgets/table/table.d.ts +2 -5
- package/dist/widgets/video/video.d.ts +2 -5
- package/dist/widgets.d.ts +3 -2
- package/package.json +47 -46
- package/dist/interactive2/objective_.d.ts +0 -34
- package/dist/perseus-types.d.ts +0 -980
- package/dist/util/answer-types.d.ts +0 -69
- package/dist/util/math.d.ts +0 -29
package/dist/es/index.js
CHANGED
|
@@ -12,16 +12,17 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
|
|
|
12
12
|
import * as ReactDOM from 'react-dom';
|
|
13
13
|
import ReactDOM__default from 'react-dom';
|
|
14
14
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
15
|
+
import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
|
|
15
16
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
16
17
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
17
18
|
import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
18
19
|
import $ from 'jquery';
|
|
19
20
|
import { v4 } from 'uuid';
|
|
20
|
-
import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
21
|
+
import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
21
22
|
import PropTypes from 'prop-types';
|
|
22
|
-
import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
+
import { point, KhanMath, number as number$1, vector as vector$1, sum as sum$1, line } from '@khanacademy/kmath';
|
|
25
26
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
26
27
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
27
28
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -57,6 +58,183 @@ function _extends() {
|
|
|
57
58
|
* The translated strings that are used to render Perseus.
|
|
58
59
|
*/
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Untranslated strings used in Perseus. To be used by an external
|
|
63
|
+
* translator to produce translated strings, passed in as `PerseusStrings`.
|
|
64
|
+
* !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
|
|
65
|
+
*/
|
|
66
|
+
const strings = {
|
|
67
|
+
closeKeypad: "close math keypad",
|
|
68
|
+
openKeypad: "open math keypad",
|
|
69
|
+
mathInputBox: "Math input box",
|
|
70
|
+
removeHighlight: "Remove highlight",
|
|
71
|
+
addHighlight: "Add highlight",
|
|
72
|
+
hintPos: "Hint #%(pos)s",
|
|
73
|
+
errorRendering: "Error rendering: %(error)s",
|
|
74
|
+
APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "12 pi or " + "2/3 pi",
|
|
75
|
+
EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
|
|
76
|
+
NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
|
|
77
|
+
MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
|
|
78
|
+
MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
|
|
79
|
+
WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
|
|
80
|
+
WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
|
|
81
|
+
invalidSelection: "Make sure you select something for every row.",
|
|
82
|
+
ERROR_TITLE: "Oops!",
|
|
83
|
+
ERROR_MESSAGE: "Sorry, I don't understand that!",
|
|
84
|
+
hints: "Hints",
|
|
85
|
+
getAnotherHint: "Get another hint",
|
|
86
|
+
deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
|
|
87
|
+
keepTrying: "Keep trying",
|
|
88
|
+
tryAgain: "Try again",
|
|
89
|
+
check: "Check",
|
|
90
|
+
correctExcited: "Correct!",
|
|
91
|
+
nextQuestion: "Next question",
|
|
92
|
+
skipToTitle: "Skip to %(title)s",
|
|
93
|
+
current: "Current",
|
|
94
|
+
correct: "Correct",
|
|
95
|
+
correctSelected: "Correct (selected)",
|
|
96
|
+
correctCrossedOut: "Correct (but you crossed it out)",
|
|
97
|
+
incorrect: "Incorrect",
|
|
98
|
+
incorrectSelected: "Incorrect (selected)",
|
|
99
|
+
hideExplanation: "Hide explanation",
|
|
100
|
+
explain: "Explain",
|
|
101
|
+
INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
|
|
102
|
+
DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
|
|
103
|
+
DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
|
|
104
|
+
integerExample: "an integer, like $6$",
|
|
105
|
+
properExample: "a *proper* fraction, like $1/2$ or $6/10$",
|
|
106
|
+
simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
|
|
107
|
+
improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
|
|
108
|
+
simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
|
|
109
|
+
mixedExample: "a mixed number, like $1\\\\ 3/4$",
|
|
110
|
+
decimalExample: "an *exact* decimal, like $0.75$",
|
|
111
|
+
percentExample: "a percent, like $12.34\\\\%$",
|
|
112
|
+
piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
|
|
113
|
+
yourAnswer: "**Your answer should be** ",
|
|
114
|
+
yourAnswerLabel: "Your answer:",
|
|
115
|
+
addPoints: "Click to add points",
|
|
116
|
+
addVertices: "Click to add vertices",
|
|
117
|
+
tapMultiple: "Tap each dot on the image to select all answers that apply.",
|
|
118
|
+
tapSingle: "Tap each dot on the image to select an answer.",
|
|
119
|
+
clickMultiple: "Click each dot on the image to select all answers that apply.",
|
|
120
|
+
clickSingle: "Click each dot on the image to select an answer.",
|
|
121
|
+
choices: "Choices:",
|
|
122
|
+
answers: {
|
|
123
|
+
one: "%(num)s answer",
|
|
124
|
+
other: "%(num)s answers"
|
|
125
|
+
},
|
|
126
|
+
hideAnswersToggleLabel: "Hide answer choices",
|
|
127
|
+
moves: {
|
|
128
|
+
one: "Moves: %(num)s",
|
|
129
|
+
other: "Moves: %(num)s"
|
|
130
|
+
},
|
|
131
|
+
clickTiles: "Click on the tiles to change the lights.",
|
|
132
|
+
turnOffLights: "You must turn on all of the lights to continue.",
|
|
133
|
+
fillAllCells: "Make sure you fill in all cells in the matrix.",
|
|
134
|
+
molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
|
|
135
|
+
switchDirection: "Switch direction",
|
|
136
|
+
circleOpen: "Make circle open",
|
|
137
|
+
circleFilled: "Make circle filled",
|
|
138
|
+
numDivisions: "Number of divisions:",
|
|
139
|
+
divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
|
|
140
|
+
lineRange: "lines %(lineRange)s",
|
|
141
|
+
lineNumber: "line %(lineNumber)s",
|
|
142
|
+
symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
|
|
143
|
+
symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
|
|
144
|
+
lineLabel: {
|
|
145
|
+
context: "a label next to a reading passage to denote the line number",
|
|
146
|
+
message: "Line"
|
|
147
|
+
},
|
|
148
|
+
beginningPassage: "Beginning of reading passage.",
|
|
149
|
+
beginningFootnotes: "Beginning of reading passage footnotes.",
|
|
150
|
+
endPassage: "End of reading passage.",
|
|
151
|
+
questionMarker: "[Marker for question %(number)s]",
|
|
152
|
+
circleMarker: "[Circle marker %(number)s]",
|
|
153
|
+
sentenceMarker: "[Sentence %(number)s]",
|
|
154
|
+
dragHandles: "Drag handles to make graph",
|
|
155
|
+
tapAddPoints: "Tap to add points",
|
|
156
|
+
false: "False",
|
|
157
|
+
true: "True",
|
|
158
|
+
no: "No",
|
|
159
|
+
yes: "Yes",
|
|
160
|
+
chooseCorrectNum: "Please choose the correct number of answers.",
|
|
161
|
+
notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
|
|
162
|
+
noneOfTheAbove: "None of the above",
|
|
163
|
+
chooseNumAnswers: "Choose %(numCorrect)s answers:",
|
|
164
|
+
chooseAllAnswers: "Choose all answers that apply:",
|
|
165
|
+
chooseOneAnswer: "Choose 1 answer:",
|
|
166
|
+
choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
|
|
167
|
+
choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
|
|
168
|
+
choiceCorrect: "(Choice %(letter)s, Correct)",
|
|
169
|
+
choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
|
|
170
|
+
choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
|
|
171
|
+
choiceIncorrect: "(Choice %(letter)s, Incorrect)",
|
|
172
|
+
choiceChecked: "(Choice %(letter)s, Checked)",
|
|
173
|
+
choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
|
|
174
|
+
choice: "(Choice %(letter)s)",
|
|
175
|
+
crossOut: "Cross out",
|
|
176
|
+
crossOutOption: "Cross out option",
|
|
177
|
+
crossOutChoice: "Cross out Choice %(letter)s",
|
|
178
|
+
bringBack: "Bring back",
|
|
179
|
+
openMenuForChoice: "Open menu for Choice %(letter)s",
|
|
180
|
+
letters: {
|
|
181
|
+
context: "This is a list of single-character labels that will appear in front of multiple-choice options. For instance, a multiple-choice question with three options would display (A) first option (B) second option (C) third option. There must be spaces between each of the different characters. The characters will show up next to options in the order that they are listed here. Most multiple choice questions have 5 or fewer options.",
|
|
182
|
+
message: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
|
|
183
|
+
},
|
|
184
|
+
rightArrow: "Reaction arrow pointing to the right.",
|
|
185
|
+
dontUnderstandUnits: "I couldn't understand those units.",
|
|
186
|
+
checkSigFigs: "Check your significant figures.",
|
|
187
|
+
answerNumericallyIncorrect: "That answer is numerically incorrect.",
|
|
188
|
+
checkUnits: "Check your units.",
|
|
189
|
+
dontUnderstand: "I don't understand that",
|
|
190
|
+
loading: "Loading...",
|
|
191
|
+
videoTranscript: "See video transcript",
|
|
192
|
+
somethingWrong: "Something went wrong.",
|
|
193
|
+
videoWrapper: "Khan Academy video wrapper",
|
|
194
|
+
mathInputTitle: "mathematics keyboard",
|
|
195
|
+
mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
|
|
196
|
+
sin: "sin",
|
|
197
|
+
cos: "cos",
|
|
198
|
+
tan: "tan",
|
|
199
|
+
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
200
|
+
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
201
|
+
selectAnAnswer: "Select an answer",
|
|
202
|
+
// The following strings are used for interactive graph SR descriptions.
|
|
203
|
+
addPoint: "Add Point",
|
|
204
|
+
removePoint: "Remove Point",
|
|
205
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
206
|
+
closePolygon: "Close shape",
|
|
207
|
+
openPolygon: "Re-open shape",
|
|
208
|
+
srInteractiveElements: "Interactive elements: %(elements)s",
|
|
209
|
+
srNoInteractiveElements: "No interactive elements",
|
|
210
|
+
// TODO(LEMS-2660): The following strings are ones that will need
|
|
211
|
+
// translation tickets after all interactive graph SR strings have
|
|
212
|
+
// been finalized. Remove this comment after the tickets have been
|
|
213
|
+
// created.
|
|
214
|
+
srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
|
|
215
|
+
srCircleGraph: "A circle on a coordinate plane.",
|
|
216
|
+
srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
|
|
217
|
+
srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
|
|
218
|
+
srCircleRadius: "Circle radius is %(radius)s.",
|
|
219
|
+
srCircleOuterPoints: "Points on the circle at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, %(point3X)s comma %(point3Y)s, %(point4X)s comma %(point4Y)s.",
|
|
220
|
+
srLinearGraph: "A line on a coordinate plane.",
|
|
221
|
+
srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
222
|
+
srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
|
|
223
|
+
srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
|
|
224
|
+
srLinearGraphSlopeHorizontal: "Its slope is zero.",
|
|
225
|
+
srLinearGraphSlopeVertical: "Its slope is undefined.",
|
|
226
|
+
srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
|
|
227
|
+
srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
|
|
228
|
+
srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
|
|
229
|
+
srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
|
|
230
|
+
srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
231
|
+
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
232
|
+
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
|
+
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
|
+
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s"
|
|
235
|
+
// The above strings are used for interactive graph SR descriptions.
|
|
236
|
+
};
|
|
237
|
+
|
|
60
238
|
/**
|
|
61
239
|
* Mock strings for the Perseus package, to be used for tests and Storybook.
|
|
62
240
|
*/
|
|
@@ -326,6 +504,22 @@ const mockStrings = {
|
|
|
326
504
|
// The above strings are used for interactive graph SR descriptions.
|
|
327
505
|
};
|
|
328
506
|
|
|
507
|
+
const errorToString = {
|
|
508
|
+
[ErrorCodes.MISSING_PERCENT_ERROR]: strings.MISSING_PERCENT_ERROR,
|
|
509
|
+
[ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR]: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
510
|
+
[ErrorCodes.APPROXIMATED_PI_ERROR]: strings.APPROXIMATED_PI_ERROR,
|
|
511
|
+
[ErrorCodes.EXTRA_SYMBOLS_ERROR]: strings.EXTRA_SYMBOLS_ERROR,
|
|
512
|
+
[ErrorCodes.WRONG_CASE_ERROR]: strings.WRONG_CASE_ERROR,
|
|
513
|
+
[ErrorCodes.WRONG_LETTER_ERROR]: strings.WRONG_LETTER_ERROR,
|
|
514
|
+
[ErrorCodes.MULTIPLICATION_SIGN_ERROR]: strings.MULTIPLICATION_SIGN_ERROR
|
|
515
|
+
};
|
|
516
|
+
function mapErrorToString(err) {
|
|
517
|
+
if (!err) {
|
|
518
|
+
return err;
|
|
519
|
+
}
|
|
520
|
+
return errorToString[err] || err;
|
|
521
|
+
}
|
|
522
|
+
|
|
329
523
|
/**
|
|
330
524
|
* PerseusI18nContext provides a way to set the strings and locale that are
|
|
331
525
|
* used inside the Perseus package.
|
|
@@ -1304,917 +1498,6 @@ const Log = {
|
|
|
1304
1498
|
}
|
|
1305
1499
|
};
|
|
1306
1500
|
|
|
1307
|
-
const KhanMath = {
|
|
1308
|
-
// Simplify formulas before display
|
|
1309
|
-
cleanMath: function (expr) {
|
|
1310
|
-
return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
|
|
1311
|
-
},
|
|
1312
|
-
// Bound a number by 1e-6 and 1e20 to avoid exponents after toString
|
|
1313
|
-
bound: function (num) {
|
|
1314
|
-
if (num === 0) {
|
|
1315
|
-
return num;
|
|
1316
|
-
}
|
|
1317
|
-
if (num < 0) {
|
|
1318
|
-
return -KhanMath.bound(-num);
|
|
1319
|
-
}
|
|
1320
|
-
return Math.max(1e-6, Math.min(num, 1e20));
|
|
1321
|
-
},
|
|
1322
|
-
factorial: function (x) {
|
|
1323
|
-
if (x <= 1) {
|
|
1324
|
-
return x;
|
|
1325
|
-
}
|
|
1326
|
-
return x * KhanMath.factorial(x - 1);
|
|
1327
|
-
},
|
|
1328
|
-
getGCD: function (a, b) {
|
|
1329
|
-
if (arguments.length > 2) {
|
|
1330
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1331
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1332
|
-
const rest = [].slice.call(arguments, 1);
|
|
1333
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1334
|
-
return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
|
|
1335
|
-
}
|
|
1336
|
-
let mod;
|
|
1337
|
-
a = Math.abs(a);
|
|
1338
|
-
b = Math.abs(b);
|
|
1339
|
-
while (b) {
|
|
1340
|
-
mod = a % b;
|
|
1341
|
-
a = b;
|
|
1342
|
-
b = mod;
|
|
1343
|
-
}
|
|
1344
|
-
return a;
|
|
1345
|
-
},
|
|
1346
|
-
getLCM: function (a, b) {
|
|
1347
|
-
if (arguments.length > 2) {
|
|
1348
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
1349
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1350
|
-
const rest = [].slice.call(arguments, 1);
|
|
1351
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1352
|
-
return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
|
|
1353
|
-
}
|
|
1354
|
-
return Math.abs(a * b) / KhanMath.getGCD(a, b);
|
|
1355
|
-
},
|
|
1356
|
-
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],
|
|
1357
|
-
isPrime: function (n) {
|
|
1358
|
-
if (n <= 1) {
|
|
1359
|
-
return false;
|
|
1360
|
-
}
|
|
1361
|
-
if (n < 101) {
|
|
1362
|
-
return !!$.grep(KhanMath.primes, function (p, i) {
|
|
1363
|
-
return Math.abs(p - n) <= 0.5;
|
|
1364
|
-
}).length;
|
|
1365
|
-
}
|
|
1366
|
-
if (n <= 1 || n > 2 && n % 2 === 0) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
|
|
1370
|
-
if (n % i === 0) {
|
|
1371
|
-
return false;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
return true;
|
|
1375
|
-
},
|
|
1376
|
-
// @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
|
|
1377
|
-
getPrimeFactorization: function (number) {
|
|
1378
|
-
if (number === 1) {
|
|
1379
|
-
return [];
|
|
1380
|
-
}
|
|
1381
|
-
if (KhanMath.isPrime(number)) {
|
|
1382
|
-
return [number];
|
|
1383
|
-
}
|
|
1384
|
-
const maxf = Math.sqrt(number);
|
|
1385
|
-
for (let f = 2; f <= maxf; f++) {
|
|
1386
|
-
if (number % f === 0) {
|
|
1387
|
-
return $.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
},
|
|
1391
|
-
// Round a number to the nearest increment
|
|
1392
|
-
// E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
|
|
1393
|
-
// num = 45, return 60.
|
|
1394
|
-
roundToNearest: function (increment, num) {
|
|
1395
|
-
return Math.round(num / increment) * increment;
|
|
1396
|
-
},
|
|
1397
|
-
// Round a number to a certain number of decimal places
|
|
1398
|
-
roundTo: function (precision, num) {
|
|
1399
|
-
const factor = Math.pow(10, precision).toFixed(5);
|
|
1400
|
-
// @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.
|
|
1401
|
-
return Math.round((num * factor).toFixed(5)) / factor;
|
|
1402
|
-
},
|
|
1403
|
-
/**
|
|
1404
|
-
* Return a string of num rounded to a fixed precision decimal places,
|
|
1405
|
-
* with an approx symbol if num had to be rounded, and trailing 0s
|
|
1406
|
-
*/
|
|
1407
|
-
toFixedApprox: function (num, precision) {
|
|
1408
|
-
// TODO(aria): Make this locale-dependent
|
|
1409
|
-
const fixedStr = num.toFixed(precision);
|
|
1410
|
-
if (number$1.equal(+fixedStr, num)) {
|
|
1411
|
-
return fixedStr;
|
|
1412
|
-
}
|
|
1413
|
-
return "\\approx " + fixedStr;
|
|
1414
|
-
},
|
|
1415
|
-
/**
|
|
1416
|
-
* Return a string of num rounded to precision decimal places, with an
|
|
1417
|
-
* approx symbol if num had to be rounded, but no trailing 0s if it was
|
|
1418
|
-
* not rounded.
|
|
1419
|
-
*/
|
|
1420
|
-
roundToApprox: function (num, precision) {
|
|
1421
|
-
const fixed = KhanMath.roundTo(precision, num);
|
|
1422
|
-
if (number$1.equal(fixed, num)) {
|
|
1423
|
-
return String(fixed);
|
|
1424
|
-
}
|
|
1425
|
-
return KhanMath.toFixedApprox(num, precision);
|
|
1426
|
-
},
|
|
1427
|
-
// toFraction(4/8) => [1, 2]
|
|
1428
|
-
// toFraction(0.666) => [333, 500]
|
|
1429
|
-
// toFraction(0.666, 0.001) => [2, 3]
|
|
1430
|
-
//
|
|
1431
|
-
// tolerance can't be bigger than 1, sorry
|
|
1432
|
-
toFraction: function (decimal, tolerance) {
|
|
1433
|
-
if (tolerance == null) {
|
|
1434
|
-
tolerance = Math.pow(2, -46);
|
|
1435
|
-
}
|
|
1436
|
-
if (decimal < 0 || decimal > 1) {
|
|
1437
|
-
let fract = decimal % 1;
|
|
1438
|
-
fract += fract < 0 ? 1 : 0;
|
|
1439
|
-
const nd = KhanMath.toFraction(fract, tolerance);
|
|
1440
|
-
nd[0] += Math.round(decimal - fract) * nd[1];
|
|
1441
|
-
return nd;
|
|
1442
|
-
}
|
|
1443
|
-
if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
|
|
1444
|
-
return [Math.round(decimal), 1];
|
|
1445
|
-
}
|
|
1446
|
-
let loN = 0;
|
|
1447
|
-
let loD = 1;
|
|
1448
|
-
let hiN = 1;
|
|
1449
|
-
let hiD = 1;
|
|
1450
|
-
let midN = 1;
|
|
1451
|
-
let midD = 2;
|
|
1452
|
-
|
|
1453
|
-
// eslint-disable-next-line no-constant-condition
|
|
1454
|
-
while (true) {
|
|
1455
|
-
if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
|
|
1456
|
-
return [midN, midD];
|
|
1457
|
-
}
|
|
1458
|
-
if (midN / midD < decimal) {
|
|
1459
|
-
loN = midN;
|
|
1460
|
-
loD = midD;
|
|
1461
|
-
} else {
|
|
1462
|
-
hiN = midN;
|
|
1463
|
-
hiD = midD;
|
|
1464
|
-
}
|
|
1465
|
-
midN = loN + hiN;
|
|
1466
|
-
midD = loD + hiD;
|
|
1467
|
-
}
|
|
1468
|
-
},
|
|
1469
|
-
// Returns the format (string) of a given numeric string
|
|
1470
|
-
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
1471
|
-
// That is, it is not necessarily true that interpreted input are numeric
|
|
1472
|
-
getNumericFormat: function (text) {
|
|
1473
|
-
text = $.trim(text);
|
|
1474
|
-
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
1475
|
-
if (text.match(/^[+-]?\d+$/)) {
|
|
1476
|
-
return "integer";
|
|
1477
|
-
}
|
|
1478
|
-
if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
|
|
1479
|
-
return "mixed";
|
|
1480
|
-
}
|
|
1481
|
-
const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
|
|
1482
|
-
if (fraction) {
|
|
1483
|
-
return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
|
|
1484
|
-
}
|
|
1485
|
-
if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
|
|
1486
|
-
return "decimal";
|
|
1487
|
-
}
|
|
1488
|
-
if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
|
|
1489
|
-
return "pi";
|
|
1490
|
-
}
|
|
1491
|
-
return null;
|
|
1492
|
-
},
|
|
1493
|
-
// Returns a string of the number in a specified format
|
|
1494
|
-
toNumericString: function (number, format) {
|
|
1495
|
-
if (number == null) {
|
|
1496
|
-
return "";
|
|
1497
|
-
}
|
|
1498
|
-
if (number === 0) {
|
|
1499
|
-
return "0"; // otherwise it might end up as 0% or 0pi
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
if (format === "percent") {
|
|
1503
|
-
return number * 100 + "%";
|
|
1504
|
-
}
|
|
1505
|
-
if (format === "pi") {
|
|
1506
|
-
const fraction = number$1.toFraction(number / Math.PI);
|
|
1507
|
-
const numerator = Math.abs(fraction[0]);
|
|
1508
|
-
const denominator = fraction[1];
|
|
1509
|
-
if (number$1.isInteger(numerator)) {
|
|
1510
|
-
const sign = number < 0 ? "-" : "";
|
|
1511
|
-
const pi = "\u03C0";
|
|
1512
|
-
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
1516
|
-
const fraction = number$1.toFraction(number);
|
|
1517
|
-
const numerator = Math.abs(fraction[0]);
|
|
1518
|
-
const denominator = fraction[1];
|
|
1519
|
-
const sign = number < 0 ? "-" : "";
|
|
1520
|
-
if (denominator === 1) {
|
|
1521
|
-
return sign + numerator; // for integers, irrational, d > 1000
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
if (format === "mixed") {
|
|
1525
|
-
const modulus = numerator % denominator;
|
|
1526
|
-
const integer = (numerator - modulus) / denominator;
|
|
1527
|
-
return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
|
|
1528
|
-
} // otherwise proper, improper, or fraction
|
|
1529
|
-
return sign + numerator + "/" + denominator;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// otherwise (decimal, float, long long)
|
|
1533
|
-
return String(number);
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
function sum$1(array) {
|
|
1537
|
-
return array.reduce(add$3, 0);
|
|
1538
|
-
}
|
|
1539
|
-
function add$3(a, b) {
|
|
1540
|
-
return a + b;
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
/* eslint-disable no-useless-escape */
|
|
1544
|
-
const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
1545
|
-
|
|
1546
|
-
// TOOD(kevinb): Figure out how this relates to KEScore in
|
|
1547
|
-
// perseus-all-package/types.js and see if there's a way to
|
|
1548
|
-
// unify these types.
|
|
1549
|
-
/**
|
|
1550
|
-
* Answer types
|
|
1551
|
-
*
|
|
1552
|
-
* Utility for creating answerable questions displayed in exercises
|
|
1553
|
-
*
|
|
1554
|
-
* Different answer types produce different kinds of input displays, and do
|
|
1555
|
-
* different kinds of checking on the solutions.
|
|
1556
|
-
*
|
|
1557
|
-
* Each of the objects contain two functions, setup and createValidator.
|
|
1558
|
-
*
|
|
1559
|
-
* The setup function takes a solutionarea and solution, and performs setup
|
|
1560
|
-
* within the solutionarea, and then returns an object which contains:
|
|
1561
|
-
*
|
|
1562
|
-
* answer: a function which, when called, will retrieve the current answer from
|
|
1563
|
-
* the solutionarea, which can then be validated using the validator
|
|
1564
|
-
* function
|
|
1565
|
-
* validator: a function returned from the createValidator function (defined
|
|
1566
|
-
* below)
|
|
1567
|
-
* solution: the correct answer to the problem
|
|
1568
|
-
* showGuess: a function which, when given a guess, shows the guess within the
|
|
1569
|
-
* provided solutionarea
|
|
1570
|
-
* showGuessCustom: a function which displays parts of a guess that are not
|
|
1571
|
-
* within the solutionarea; currently only used for custom
|
|
1572
|
-
* answers
|
|
1573
|
-
*
|
|
1574
|
-
* The createValidator function only takes a solution, and it returns a
|
|
1575
|
-
* function which can be used to validate an answer.
|
|
1576
|
-
*
|
|
1577
|
-
* The resulting validator function returns:
|
|
1578
|
-
* - true: if the answer is fully correct
|
|
1579
|
-
* - false: if the answer is incorrect
|
|
1580
|
-
* - "" (the empty string): if no answer has been provided (e.g. the answer box
|
|
1581
|
-
* is left unfilled)
|
|
1582
|
-
* - a string: if there is some slight error
|
|
1583
|
-
*
|
|
1584
|
-
* In most cases, setup and createValidator don't really need the solution DOM
|
|
1585
|
-
* element so we have setupFunctional and createValidatorFunctional for them
|
|
1586
|
-
* which take only $solution.text() and $solution.data(). This makes it easier
|
|
1587
|
-
* to reuse specific answer types.
|
|
1588
|
-
*
|
|
1589
|
-
* TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
|
|
1590
|
-
*
|
|
1591
|
-
*/
|
|
1592
|
-
const KhanAnswerTypes = {
|
|
1593
|
-
/*
|
|
1594
|
-
* predicate answer type
|
|
1595
|
-
*
|
|
1596
|
-
* performs simple predicate-based checking of a numeric solution, with
|
|
1597
|
-
* different kinds of number formats
|
|
1598
|
-
*
|
|
1599
|
-
* Uses the data-forms option on the solution to choose which number formats
|
|
1600
|
-
* are acceptable. Available data-forms:
|
|
1601
|
-
*
|
|
1602
|
-
* - integer: 3
|
|
1603
|
-
* - proper: 3/5
|
|
1604
|
-
* - improper: 5/3
|
|
1605
|
-
* - pi: 3 pi
|
|
1606
|
-
* - log: log(5)
|
|
1607
|
-
* - percent: 15%
|
|
1608
|
-
* - mixed: 1 1/3
|
|
1609
|
-
* - decimal: 1.7
|
|
1610
|
-
*
|
|
1611
|
-
* The solution should be a predicate of the form:
|
|
1612
|
-
*
|
|
1613
|
-
* function(guess, maxError) {
|
|
1614
|
-
* return abs(guess - 3) < maxError;
|
|
1615
|
-
* }
|
|
1616
|
-
*
|
|
1617
|
-
*/
|
|
1618
|
-
predicate: {
|
|
1619
|
-
defaultForms: "integer, proper, improper, mixed, decimal",
|
|
1620
|
-
createValidatorFunctional: function (predicate, options, strings) {
|
|
1621
|
-
// Extract the options from the given solution object
|
|
1622
|
-
options = _.extend({
|
|
1623
|
-
simplify: "required",
|
|
1624
|
-
ratio: false,
|
|
1625
|
-
forms: KhanAnswerTypes.predicate.defaultForms
|
|
1626
|
-
}, options);
|
|
1627
|
-
let acceptableForms;
|
|
1628
|
-
// this is maintaining backwards compatibility
|
|
1629
|
-
// TODO(merlob) fix all places that depend on this, then delete
|
|
1630
|
-
if (!_.isArray(options.forms)) {
|
|
1631
|
-
acceptableForms = options.forms.split(/\s*,\s*/);
|
|
1632
|
-
} else {
|
|
1633
|
-
acceptableForms = options.forms;
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// TODO(jack): remove options.inexact in favor of options.maxError
|
|
1637
|
-
if (options.inexact === undefined) {
|
|
1638
|
-
// If we aren't allowing inexact, ensure that we don't have a
|
|
1639
|
-
// large maxError as well.
|
|
1640
|
-
options.maxError = 0;
|
|
1641
|
-
}
|
|
1642
|
-
// Allow a small tolerance on maxError, to avoid numerical
|
|
1643
|
-
// representation issues (2.3 should be correct for a solution of
|
|
1644
|
-
// 2.45 with maxError=0.15).
|
|
1645
|
-
options.maxError = +options.maxError + MAXERROR_EPSILON;
|
|
1646
|
-
|
|
1647
|
-
// If percent is an acceptable form, make sure it's the last one
|
|
1648
|
-
// in the list so we don't prematurely complain about not having
|
|
1649
|
-
// a percent sign when the user entered the correct answer in a
|
|
1650
|
-
// different form (such as a decimal or fraction)
|
|
1651
|
-
if (_.contains(acceptableForms, "percent")) {
|
|
1652
|
-
acceptableForms = _.without(acceptableForms, "percent");
|
|
1653
|
-
acceptableForms.push("percent");
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
// Take text looking like a fraction, and turn it into a number
|
|
1657
|
-
const fractionTransformer = function fractionTransformer(text) {
|
|
1658
|
-
text = text
|
|
1659
|
-
// Replace unicode minus sign with hyphen
|
|
1660
|
-
.replace(/\u2212/, "-")
|
|
1661
|
-
// Remove space after +, -
|
|
1662
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1663
|
-
// Remove leading/trailing whitespace
|
|
1664
|
-
.replace(/(^\s*)|(\s*$)/gi, "");
|
|
1665
|
-
|
|
1666
|
-
// Extract numerator and denominator
|
|
1667
|
-
const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
|
|
1668
|
-
// Fractions are represented as "-\frac{numerator}{denominator}"
|
|
1669
|
-
// in Mobile device input instead of "numerator/denominator" as
|
|
1670
|
-
// in web-browser.
|
|
1671
|
-
const mobileDeviceMatch = text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);
|
|
1672
|
-
const parsedInt = parseInt(text, 10);
|
|
1673
|
-
if (match || mobileDeviceMatch) {
|
|
1674
|
-
let num;
|
|
1675
|
-
let denom;
|
|
1676
|
-
let simplified = true;
|
|
1677
|
-
if (match) {
|
|
1678
|
-
num = parseFloat(match[1]);
|
|
1679
|
-
denom = parseFloat(match[2]);
|
|
1680
|
-
} else {
|
|
1681
|
-
num = parseFloat(mobileDeviceMatch[2]);
|
|
1682
|
-
if (mobileDeviceMatch[1] === "-") {
|
|
1683
|
-
if (num < 0) {
|
|
1684
|
-
simplified = false;
|
|
1685
|
-
}
|
|
1686
|
-
num = -num;
|
|
1687
|
-
}
|
|
1688
|
-
denom = parseFloat(mobileDeviceMatch[3]);
|
|
1689
|
-
}
|
|
1690
|
-
simplified = simplified && denom > 0 && (options.ratio || denom !== 1) && KhanMath.getGCD(num, denom) === 1;
|
|
1691
|
-
return [{
|
|
1692
|
-
value: num / denom,
|
|
1693
|
-
exact: simplified
|
|
1694
|
-
}];
|
|
1695
|
-
}
|
|
1696
|
-
if (!isNaN(parsedInt) && "" + parsedInt === text) {
|
|
1697
|
-
return [{
|
|
1698
|
-
value: parsedInt,
|
|
1699
|
-
exact: true
|
|
1700
|
-
}];
|
|
1701
|
-
}
|
|
1702
|
-
return [];
|
|
1703
|
-
};
|
|
1704
|
-
|
|
1705
|
-
/*
|
|
1706
|
-
* Different forms of numbers
|
|
1707
|
-
*
|
|
1708
|
-
* Each function returns a list of objects of the form:
|
|
1709
|
-
*
|
|
1710
|
-
* {
|
|
1711
|
-
* value: numerical value,
|
|
1712
|
-
* exact: true/false
|
|
1713
|
-
* }
|
|
1714
|
-
*/
|
|
1715
|
-
const forms = {
|
|
1716
|
-
// integer, which is encompassed by decimal
|
|
1717
|
-
integer: function (text) {
|
|
1718
|
-
// Compare the decimal form to the decimal form rounded to
|
|
1719
|
-
// an integer. Only accept if the user actually entered an
|
|
1720
|
-
// integer.
|
|
1721
|
-
const decimal = forms.decimal(text);
|
|
1722
|
-
const rounded = forms.decimal(text, 1);
|
|
1723
|
-
if (decimal[0].value != null && decimal[0].value === rounded[0].value || decimal[1].value != null && decimal[1].value === rounded[1].value) {
|
|
1724
|
-
return decimal;
|
|
1725
|
-
}
|
|
1726
|
-
return [];
|
|
1727
|
-
},
|
|
1728
|
-
// A proper fraction
|
|
1729
|
-
proper: function (text) {
|
|
1730
|
-
return $.map(fractionTransformer(text), function (o) {
|
|
1731
|
-
// All fractions that are less than 1
|
|
1732
|
-
if (Math.abs(o.value) < 1) {
|
|
1733
|
-
return [o];
|
|
1734
|
-
}
|
|
1735
|
-
return [];
|
|
1736
|
-
});
|
|
1737
|
-
},
|
|
1738
|
-
// an improper fraction
|
|
1739
|
-
improper: function (text) {
|
|
1740
|
-
return $.map(fractionTransformer(text), function (o) {
|
|
1741
|
-
// All fractions that are greater than 1
|
|
1742
|
-
if (Math.abs(o.value) >= 1) {
|
|
1743
|
-
return [o];
|
|
1744
|
-
}
|
|
1745
|
-
return [];
|
|
1746
|
-
});
|
|
1747
|
-
},
|
|
1748
|
-
// pi-like numbers
|
|
1749
|
-
pi: function (text) {
|
|
1750
|
-
let match;
|
|
1751
|
-
let possibilities = [];
|
|
1752
|
-
|
|
1753
|
-
// Replace unicode minus sign with hyphen
|
|
1754
|
-
text = text.replace(/\u2212/, "-");
|
|
1755
|
-
|
|
1756
|
-
// - pi
|
|
1757
|
-
// (Note: we also support \pi (for TeX), p, tau (and \tau,
|
|
1758
|
-
// and t), pau.)
|
|
1759
|
-
if (match = text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1760
|
-
possibilities = [{
|
|
1761
|
-
value: parseFloat(match[1] + "1"),
|
|
1762
|
-
exact: true
|
|
1763
|
-
}];
|
|
1764
|
-
|
|
1765
|
-
// 5 / 6 pi
|
|
1766
|
-
} else if (match = text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1767
|
-
possibilities = fractionTransformer(match[1]);
|
|
1768
|
-
|
|
1769
|
-
// 4 5 / 6 pi
|
|
1770
|
-
} else if (match = text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1771
|
-
const sign = parseFloat(match[1] + "1");
|
|
1772
|
-
const integ = parseFloat(match[2]);
|
|
1773
|
-
const num = parseFloat(match[3]);
|
|
1774
|
-
const denom = parseFloat(match[4]);
|
|
1775
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
1776
|
-
possibilities = [{
|
|
1777
|
-
value: sign * (integ + num / denom),
|
|
1778
|
-
exact: simplified
|
|
1779
|
-
}];
|
|
1780
|
-
|
|
1781
|
-
// 5 pi / 6
|
|
1782
|
-
} else if (match = text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)) {
|
|
1783
|
-
possibilities = fractionTransformer(match[1] + "/" + match[3]);
|
|
1784
|
-
|
|
1785
|
-
// - pi / 4
|
|
1786
|
-
} else if (match = text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)) {
|
|
1787
|
-
possibilities = fractionTransformer(match[1] + "1/" + match[3]);
|
|
1788
|
-
|
|
1789
|
-
// 0
|
|
1790
|
-
} else if (text === "0") {
|
|
1791
|
-
possibilities = [{
|
|
1792
|
-
value: 0,
|
|
1793
|
-
exact: true
|
|
1794
|
-
}];
|
|
1795
|
-
|
|
1796
|
-
// 0.5 pi (fallback)
|
|
1797
|
-
} else if (match = text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)) {
|
|
1798
|
-
possibilities = forms.decimal(match[1]);
|
|
1799
|
-
} else {
|
|
1800
|
-
possibilities = _.reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/), function (memo, form) {
|
|
1801
|
-
return memo.concat(forms[form](text));
|
|
1802
|
-
}, []);
|
|
1803
|
-
|
|
1804
|
-
// If the answer is a floating point number that's
|
|
1805
|
-
// near a multiple of pi, mark is as being possibly
|
|
1806
|
-
// an approximation of pi. We actually check if
|
|
1807
|
-
// it's a plausible approximation of pi/12, since
|
|
1808
|
-
// sometimes the correct answer is like pi/3 or pi/4.
|
|
1809
|
-
// We also say it's a pi-approximation if it involves
|
|
1810
|
-
// x/7 (since 22/7 is an approximation of pi.)
|
|
1811
|
-
// Never mark an integer as being an approximation
|
|
1812
|
-
// of pi.
|
|
1813
|
-
let approximatesPi = false;
|
|
1814
|
-
const number = parseFloat(text);
|
|
1815
|
-
if (!isNaN(number) && number !== parseInt(text)) {
|
|
1816
|
-
const piMult = Math.PI / 12;
|
|
1817
|
-
const roundedNumber = piMult * Math.round(number / piMult);
|
|
1818
|
-
if (Math.abs(number - roundedNumber) < 0.01) {
|
|
1819
|
-
approximatesPi = true;
|
|
1820
|
-
}
|
|
1821
|
-
} else if (text.match(/\/\s*7/)) {
|
|
1822
|
-
approximatesPi = true;
|
|
1823
|
-
}
|
|
1824
|
-
if (approximatesPi) {
|
|
1825
|
-
_.each(possibilities, function (possibility) {
|
|
1826
|
-
possibility.piApprox = true;
|
|
1827
|
-
});
|
|
1828
|
-
}
|
|
1829
|
-
return possibilities;
|
|
1830
|
-
}
|
|
1831
|
-
let multiplier = Math.PI;
|
|
1832
|
-
if (text.match(/\\?tau|t|\u03c4/)) {
|
|
1833
|
-
multiplier = Math.PI * 2;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
// We're taking an early stand along side xkcd in the
|
|
1837
|
-
// inevitable ti vs. pau debate... http://xkcd.com/1292
|
|
1838
|
-
if (text.match(/pau/)) {
|
|
1839
|
-
multiplier = Math.PI * 1.5;
|
|
1840
|
-
}
|
|
1841
|
-
$.each(possibilities, function (ix, possibility) {
|
|
1842
|
-
possibility.value *= multiplier;
|
|
1843
|
-
});
|
|
1844
|
-
return possibilities;
|
|
1845
|
-
},
|
|
1846
|
-
// Converts '' to 1 and '-' to -1 so you can write "[___] x"
|
|
1847
|
-
// and accept sane things
|
|
1848
|
-
coefficient: function (text) {
|
|
1849
|
-
let possibilities = [];
|
|
1850
|
-
|
|
1851
|
-
// Replace unicode minus sign with hyphen
|
|
1852
|
-
text = text.replace(/\u2212/, "-");
|
|
1853
|
-
if (text === "") {
|
|
1854
|
-
possibilities = [{
|
|
1855
|
-
value: 1,
|
|
1856
|
-
exact: true
|
|
1857
|
-
}];
|
|
1858
|
-
} else if (text === "-") {
|
|
1859
|
-
possibilities = [{
|
|
1860
|
-
value: -1,
|
|
1861
|
-
exact: true
|
|
1862
|
-
}];
|
|
1863
|
-
}
|
|
1864
|
-
return possibilities;
|
|
1865
|
-
},
|
|
1866
|
-
// simple log(c) form
|
|
1867
|
-
log: function (text) {
|
|
1868
|
-
let match;
|
|
1869
|
-
let possibilities = [];
|
|
1870
|
-
|
|
1871
|
-
// Replace unicode minus sign with hyphen
|
|
1872
|
-
text = text.replace(/\u2212/, "-");
|
|
1873
|
-
text = text.replace(/[ \(\)]/g, "");
|
|
1874
|
-
if (match = text.match(/^log\s*(\S+)\s*$/i)) {
|
|
1875
|
-
// @ts-expect-error - TS2322 - Type '{ value: number | undefined; exact: boolean; }[]' is not assignable to type 'never[]'.
|
|
1876
|
-
possibilities = forms.decimal(match[1]);
|
|
1877
|
-
} else if (text === "0") {
|
|
1878
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'. | TS2322 - Type 'boolean' is not assignable to type 'never'.
|
|
1879
|
-
possibilities = [{
|
|
1880
|
-
value: 0,
|
|
1881
|
-
exact: true
|
|
1882
|
-
}];
|
|
1883
|
-
}
|
|
1884
|
-
return possibilities;
|
|
1885
|
-
},
|
|
1886
|
-
// Numbers with percent signs
|
|
1887
|
-
percent: function (text) {
|
|
1888
|
-
text = $.trim(text);
|
|
1889
|
-
// store whether or not there is a percent sign
|
|
1890
|
-
let hasPercentSign = false;
|
|
1891
|
-
if (text.indexOf("%") === text.length - 1) {
|
|
1892
|
-
text = $.trim(text.substring(0, text.length - 1));
|
|
1893
|
-
hasPercentSign = true;
|
|
1894
|
-
}
|
|
1895
|
-
const transformed = forms.decimal(text);
|
|
1896
|
-
$.each(transformed, function (ix, t) {
|
|
1897
|
-
t.exact = hasPercentSign;
|
|
1898
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
1899
|
-
t.value = t.value / 100;
|
|
1900
|
-
});
|
|
1901
|
-
return transformed;
|
|
1902
|
-
},
|
|
1903
|
-
// Mixed numbers, like 1 3/4
|
|
1904
|
-
mixed: function (text) {
|
|
1905
|
-
const match = text
|
|
1906
|
-
// Replace unicode minus sign with hyphen
|
|
1907
|
-
.replace(/\u2212/, "-")
|
|
1908
|
-
// Remove space after +, -
|
|
1909
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1910
|
-
// Extract integer, numerator and denominator
|
|
1911
|
-
.match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
|
|
1912
|
-
if (match) {
|
|
1913
|
-
const sign = parseFloat(match[1] + "1");
|
|
1914
|
-
const integ = parseFloat(match[2]);
|
|
1915
|
-
const num = parseFloat(match[3]);
|
|
1916
|
-
const denom = parseFloat(match[4]);
|
|
1917
|
-
const simplified = num < denom && KhanMath.getGCD(num, denom) === 1;
|
|
1918
|
-
return [{
|
|
1919
|
-
value: sign * (integ + num / denom),
|
|
1920
|
-
exact: simplified
|
|
1921
|
-
}];
|
|
1922
|
-
}
|
|
1923
|
-
return [];
|
|
1924
|
-
},
|
|
1925
|
-
// Decimal numbers -- compare entered text rounded to
|
|
1926
|
-
// 'precision' Reciprical of the precision against the correct
|
|
1927
|
-
// answer. We round to 1/1e10 by default, which is healthily
|
|
1928
|
-
// less than machine epsilon but should be more than any real
|
|
1929
|
-
// decimal answer would use. (The 'integer' answer type uses
|
|
1930
|
-
// precision == 1.)
|
|
1931
|
-
decimal: function (text, precision = 1e10) {
|
|
1932
|
-
const normal = function normal(text) {
|
|
1933
|
-
text = $.trim(text);
|
|
1934
|
-
const match = text
|
|
1935
|
-
// Replace unicode minus sign with hyphen
|
|
1936
|
-
.replace(/\u2212/, "-")
|
|
1937
|
-
// Remove space after +, -
|
|
1938
|
-
.replace(/([+-])\s+/g, "$1")
|
|
1939
|
-
// Extract integer, numerator and denominator. If
|
|
1940
|
-
// commas or spaces are used, they must be in the
|
|
1941
|
-
// "correct" places
|
|
1942
|
-
.match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);
|
|
1943
|
-
|
|
1944
|
-
// You can't start a number with `0,`, to prevent us
|
|
1945
|
-
// interpeting '0.342' as correct for '342'
|
|
1946
|
-
const badLeadingZero = text.match(/^0[0,]*,/);
|
|
1947
|
-
if (match && !badLeadingZero) {
|
|
1948
|
-
let x = parseFloat(match[1].replace(/[, ]/g, ""));
|
|
1949
|
-
if (options.inexact === undefined) {
|
|
1950
|
-
x = Math.round(x * precision) / precision;
|
|
1951
|
-
}
|
|
1952
|
-
return x;
|
|
1953
|
-
}
|
|
1954
|
-
};
|
|
1955
|
-
const commas = function commas(text) {
|
|
1956
|
-
text = text.replace(/([\.,])/g, function (_, c) {
|
|
1957
|
-
return c === "." ? "," : ".";
|
|
1958
|
-
});
|
|
1959
|
-
return normal(text);
|
|
1960
|
-
};
|
|
1961
|
-
return [{
|
|
1962
|
-
value: normal(text),
|
|
1963
|
-
exact: true
|
|
1964
|
-
}, {
|
|
1965
|
-
value: commas(text),
|
|
1966
|
-
exact: true
|
|
1967
|
-
}];
|
|
1968
|
-
}
|
|
1969
|
-
};
|
|
1970
|
-
|
|
1971
|
-
// validator function
|
|
1972
|
-
return function (guess) {
|
|
1973
|
-
// The fallback variable is used in place of the answer, if no
|
|
1974
|
-
// answer is provided (i.e. the field is left blank)
|
|
1975
|
-
const fallback = options.fallback != null ? "" + options.fallback : "";
|
|
1976
|
-
guess = $.trim(guess) || fallback;
|
|
1977
|
-
const score = {
|
|
1978
|
-
empty: guess === "",
|
|
1979
|
-
correct: false,
|
|
1980
|
-
message: null,
|
|
1981
|
-
guess: guess
|
|
1982
|
-
};
|
|
1983
|
-
|
|
1984
|
-
// iterate over all the acceptable forms, and if one of the
|
|
1985
|
-
// answers is correct, return true
|
|
1986
|
-
$.each(acceptableForms, function (i, form) {
|
|
1987
|
-
const transformed = forms[form](guess);
|
|
1988
|
-
for (let j = 0, l = transformed.length; j < l; j++) {
|
|
1989
|
-
const val = transformed[j].value;
|
|
1990
|
-
const exact = transformed[j].exact;
|
|
1991
|
-
const piApprox = transformed[j].piApprox;
|
|
1992
|
-
// If a string was returned, and it exactly matches,
|
|
1993
|
-
// return true
|
|
1994
|
-
if (predicate(val, options.maxError)) {
|
|
1995
|
-
// If the exact correct number was returned,
|
|
1996
|
-
// return true
|
|
1997
|
-
if (exact || options.simplify === "optional") {
|
|
1998
|
-
score.correct = true;
|
|
1999
|
-
score.message = options.message || null;
|
|
2000
|
-
// If the answer is correct, don't say it's
|
|
2001
|
-
// empty. This happens, for example, with the
|
|
2002
|
-
// coefficient type where guess === "" but is
|
|
2003
|
-
// interpreted as "1" which is correct.
|
|
2004
|
-
score.empty = false;
|
|
2005
|
-
} else if (form === "percent") {
|
|
2006
|
-
// Otherwise, an error was returned
|
|
2007
|
-
score.empty = true;
|
|
2008
|
-
score.message = strings.MISSING_PERCENT_ERROR;
|
|
2009
|
-
} else {
|
|
2010
|
-
if (options.simplify !== "enforced") {
|
|
2011
|
-
score.empty = true;
|
|
2012
|
-
}
|
|
2013
|
-
score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
|
|
2014
|
-
}
|
|
2015
|
-
// The return false below stops the looping of the
|
|
2016
|
-
// callback since predicate check succeeded.
|
|
2017
|
-
// No more forms to look to verify the user guess.
|
|
2018
|
-
return false;
|
|
2019
|
-
}
|
|
2020
|
-
if (piApprox && predicate(val, Math.abs(val * 0.001))) {
|
|
2021
|
-
score.empty = true;
|
|
2022
|
-
score.message = strings.APPROXIMATED_PI_ERROR;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
});
|
|
2026
|
-
if (score.correct === false) {
|
|
2027
|
-
let interpretedGuess = false;
|
|
2028
|
-
_.each(forms, function (form) {
|
|
2029
|
-
const anyAreNaN = _.any(form(guess), function (t) {
|
|
2030
|
-
return t.value != null && !_.isNaN(t.value);
|
|
2031
|
-
});
|
|
2032
|
-
if (anyAreNaN) {
|
|
2033
|
-
interpretedGuess = true;
|
|
2034
|
-
}
|
|
2035
|
-
});
|
|
2036
|
-
if (!interpretedGuess) {
|
|
2037
|
-
score.empty = true;
|
|
2038
|
-
score.message = strings.EXTRA_SYMBOLS_ERROR;
|
|
2039
|
-
return score;
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
return score;
|
|
2043
|
-
};
|
|
2044
|
-
}
|
|
2045
|
-
},
|
|
2046
|
-
/*
|
|
2047
|
-
* number answer type
|
|
2048
|
-
*
|
|
2049
|
-
* wraps the predicate answer type to performs simple number-based checking
|
|
2050
|
-
* of a solution
|
|
2051
|
-
*/
|
|
2052
|
-
number: {
|
|
2053
|
-
convertToPredicate: function (correctAnswer, options) {
|
|
2054
|
-
const correctFloat = parseFloat(correctAnswer);
|
|
2055
|
-
return [function (guess, maxError) {
|
|
2056
|
-
return Math.abs(guess - correctFloat) < maxError;
|
|
2057
|
-
}, $.extend({}, options, {
|
|
2058
|
-
type: "predicate"
|
|
2059
|
-
})];
|
|
2060
|
-
},
|
|
2061
|
-
createValidatorFunctional: function (correctAnswer, options, strings) {
|
|
2062
|
-
return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
2063
|
-
}
|
|
2064
|
-
},
|
|
2065
|
-
/*
|
|
2066
|
-
* The expression answer type parses a given expression or equation
|
|
2067
|
-
* and semantically compares it to the solution. In addition, instant
|
|
2068
|
-
* feedback is provided by rendering the last answer that fully parsed.
|
|
2069
|
-
*
|
|
2070
|
-
* Parsing options:
|
|
2071
|
-
* functions (e.g. data-functions="f g h")
|
|
2072
|
-
* A space or comma separated list of single-letter variables that
|
|
2073
|
-
* should be interpreted as functions. Case sensitive. "e" and "i"
|
|
2074
|
-
* are reserved.
|
|
2075
|
-
*
|
|
2076
|
-
* no functions specified: f(x+y) == fx + fy
|
|
2077
|
-
* with "f" as a function: f(x+y) != fx + fy
|
|
2078
|
-
*
|
|
2079
|
-
* Comparison options:
|
|
2080
|
-
* same-form (e.g. data-same-form)
|
|
2081
|
-
* If present, the answer must match the solution's structure in
|
|
2082
|
-
* addition to evaluating the same. Commutativity and excess negation
|
|
2083
|
-
* are ignored, but all other changes will trigger a rejection. Useful
|
|
2084
|
-
* for requiring a particular form of an equation, or if the answer
|
|
2085
|
-
* must be factored.
|
|
2086
|
-
*
|
|
2087
|
-
* example question: Factor x^2 + x - 2
|
|
2088
|
-
* example solution: (x-1)(x+2)
|
|
2089
|
-
* accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
|
|
2090
|
-
* rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
|
|
2091
|
-
* rejection message: Your answer is not in the correct form
|
|
2092
|
-
*
|
|
2093
|
-
* simplify (e.g. data-simplify)
|
|
2094
|
-
* If present, the answer must be fully expanded and simplified. Use
|
|
2095
|
-
* carefully - simplification is hard and there may be bugs, or you
|
|
2096
|
-
* might not agree on the definition of "simplified" used. You will
|
|
2097
|
-
* get an error if the provided solution is not itself fully expanded
|
|
2098
|
-
* and simplified.
|
|
2099
|
-
*
|
|
2100
|
-
* example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
|
|
2101
|
-
* example solution: x^31 / n
|
|
2102
|
-
* accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
|
|
2103
|
-
* rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
|
|
2104
|
-
* rejection message: Your answer is not fully expanded and simplified
|
|
2105
|
-
*
|
|
2106
|
-
* Rendering options:
|
|
2107
|
-
* times (e.g. data-times)
|
|
2108
|
-
* If present, explicit multiplication (such as between numbers) will
|
|
2109
|
-
* be rendered with a cross/x symbol (TeX: \times) instead of the usual
|
|
2110
|
-
* center dot (TeX: \cdot).
|
|
2111
|
-
*
|
|
2112
|
-
* normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
|
|
2113
|
-
* but with "times": 2 * 3^x -> 2 \times 3^{x}
|
|
2114
|
-
*/
|
|
2115
|
-
expression: {
|
|
2116
|
-
parseSolution: function (solutionString, options) {
|
|
2117
|
-
let solution = KAS.parse(solutionString, options);
|
|
2118
|
-
if (!solution.parsed) {
|
|
2119
|
-
throw new PerseusError("The provided solution (" + solutionString + ") didn't parse.", Errors.InvalidInput);
|
|
2120
|
-
} else if (options.simplified && !solution.expr.isSimplified()) {
|
|
2121
|
-
throw new PerseusError("The provided solution (" + solutionString + ") isn't fully expanded and simplified.", Errors.InvalidInput);
|
|
2122
|
-
} else {
|
|
2123
|
-
solution = solution.expr;
|
|
2124
|
-
}
|
|
2125
|
-
return solution;
|
|
2126
|
-
},
|
|
2127
|
-
createValidatorFunctional: function (solution, options, strings) {
|
|
2128
|
-
return function (guess) {
|
|
2129
|
-
const score = {
|
|
2130
|
-
empty: false,
|
|
2131
|
-
correct: false,
|
|
2132
|
-
message: null,
|
|
2133
|
-
guess: guess,
|
|
2134
|
-
// Setting `ungraded` to true indicates that if the
|
|
2135
|
-
// guess doesn't match any of the solutions, the guess
|
|
2136
|
-
// shouldn't be marked as incorrect; instead, `message`
|
|
2137
|
-
// should be shown to the user. This is different from
|
|
2138
|
-
// setting `empty` to true, since the behavior of `empty`
|
|
2139
|
-
// is that `message` only will be shown if the guess is
|
|
2140
|
-
// graded as empty for every solution.
|
|
2141
|
-
ungraded: false
|
|
2142
|
-
};
|
|
2143
|
-
|
|
2144
|
-
// Don't bother parsing an empty input
|
|
2145
|
-
if (!guess) {
|
|
2146
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2147
|
-
score.empty = true;
|
|
2148
|
-
return score;
|
|
2149
|
-
}
|
|
2150
|
-
const answer = KAS.parse(guess, options);
|
|
2151
|
-
|
|
2152
|
-
// An unsuccessful parse doesn't count as wrong
|
|
2153
|
-
if (!answer.parsed) {
|
|
2154
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'empty' because it is a read-only property.
|
|
2155
|
-
score.empty = true;
|
|
2156
|
-
return score;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
// Solution will need to be parsed again if we're creating
|
|
2160
|
-
// this from a multiple question type
|
|
2161
|
-
if (typeof solution === "string") {
|
|
2162
|
-
solution = KhanAnswerTypes.expression.parseSolution(solution, options);
|
|
2163
|
-
}
|
|
2164
|
-
const result = KAS.compare(answer.expr, solution, options);
|
|
2165
|
-
if (result.equal) {
|
|
2166
|
-
// Correct answer
|
|
2167
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'correct' because it is a read-only property.
|
|
2168
|
-
score.correct = true;
|
|
2169
|
-
} else if (result.wrongVariableNames || result.wrongVariableCase) {
|
|
2170
|
-
// We don't want to give people an error for getting the
|
|
2171
|
-
// variable names or the variable case wrong.
|
|
2172
|
-
// TODO(aasmund): This should ideally have been handled
|
|
2173
|
-
// under the `result.message` condition, but the
|
|
2174
|
-
// KAS messages currently aren't translatable.
|
|
2175
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2176
|
-
score.ungraded = true;
|
|
2177
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2178
|
-
score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
|
|
2179
|
-
// Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
|
|
2180
|
-
// @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; }'.
|
|
2181
|
-
score.suppressAlmostThere = true;
|
|
2182
|
-
} else if (result.message) {
|
|
2183
|
-
// Nearly correct answer
|
|
2184
|
-
// TODO(aasmund): This message also isn't translatable;
|
|
2185
|
-
// need to fix that in KAS
|
|
2186
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2187
|
-
score.message = result.message;
|
|
2188
|
-
} else {
|
|
2189
|
-
// Replace x with * and see if it would have been correct
|
|
2190
|
-
// TODO(aasmund): I think this branch is effectively dead,
|
|
2191
|
-
// because the replacement will only work in situations
|
|
2192
|
-
// where the variables are wrong (except if the variable
|
|
2193
|
-
// is x, in which case the replacement won't work either),
|
|
2194
|
-
// which is handled by another branch. When we implement a
|
|
2195
|
-
// more sophisticated variable check, revive this or
|
|
2196
|
-
// remove it completely if it will never come into play.
|
|
2197
|
-
const answerX = KAS.parse(guess.replace(/[xX]/g, "*"), options);
|
|
2198
|
-
if (answerX.parsed) {
|
|
2199
|
-
const resultX = KAS.compare(answerX.expr, solution, options);
|
|
2200
|
-
if (resultX.equal) {
|
|
2201
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
|
|
2202
|
-
score.ungraded = true;
|
|
2203
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2204
|
-
score.message = strings.MULTIPLICATION_SIGN_ERROR;
|
|
2205
|
-
} else if (resultX.message) {
|
|
2206
|
-
// TODO(aasmund): I18nize `score.message`
|
|
2207
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
|
|
2208
|
-
score.message = resultX.message + " Also, I'm a computer. I only understand " + "multiplication if you use an " + "asterisk (*) as the multiplication " + "sign.";
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
return score;
|
|
2213
|
-
};
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
};
|
|
2217
|
-
|
|
2218
1501
|
/**
|
|
2219
1502
|
* Checks user input from the expression widget to see if it is scorable.
|
|
2220
1503
|
*
|
|
@@ -2281,7 +1564,7 @@ function scoreExpression(userInput, rubric, strings, locale) {
|
|
|
2281
1564
|
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
2282
1565
|
simplify: answer.simplify,
|
|
2283
1566
|
form: answer.form
|
|
2284
|
-
})
|
|
1567
|
+
}));
|
|
2285
1568
|
};
|
|
2286
1569
|
|
|
2287
1570
|
// Find the first answer form that matches the user's input and that
|
|
@@ -2956,7 +2239,7 @@ function firstNumericalParse$1(text, strings) {
|
|
|
2956
2239
|
simplify: "optional",
|
|
2957
2240
|
inexact: true,
|
|
2958
2241
|
forms: "integer, proper, improper, pi, log, mixed, decimal"
|
|
2959
|
-
}
|
|
2242
|
+
});
|
|
2960
2243
|
val(text);
|
|
2961
2244
|
return first;
|
|
2962
2245
|
}
|
|
@@ -14684,54 +13967,6 @@ var MovableLineOptions = {
|
|
|
14684
13967
|
}
|
|
14685
13968
|
};
|
|
14686
13969
|
|
|
14687
|
-
/**
|
|
14688
|
-
* A work-in-progress of _ methods for objects.
|
|
14689
|
-
* That is, they take an object as a parameter,
|
|
14690
|
-
* and return an object instead of an array.
|
|
14691
|
-
*
|
|
14692
|
-
* TODO(aria): Move this out of interactive2
|
|
14693
|
-
*/
|
|
14694
|
-
|
|
14695
|
-
/**
|
|
14696
|
-
* Does a pluck on keys inside objects in an object
|
|
14697
|
-
*
|
|
14698
|
-
* Ex:
|
|
14699
|
-
* tools = {
|
|
14700
|
-
* translation: {
|
|
14701
|
-
* enabled: true
|
|
14702
|
-
* },
|
|
14703
|
-
* rotation: {
|
|
14704
|
-
* enabled: false
|
|
14705
|
-
* }
|
|
14706
|
-
* };
|
|
14707
|
-
* pluckObject(tools, "enabled") returns {
|
|
14708
|
-
* translation: true
|
|
14709
|
-
* rotation: false
|
|
14710
|
-
* }
|
|
14711
|
-
*/
|
|
14712
|
-
const pluck = function pluck(table, subKey) {
|
|
14713
|
-
return _.object(_.map(table, function (value, key) {
|
|
14714
|
-
return [key, value[subKey]];
|
|
14715
|
-
}));
|
|
14716
|
-
};
|
|
14717
|
-
|
|
14718
|
-
/**
|
|
14719
|
-
* Maps an object to an object
|
|
14720
|
-
*
|
|
14721
|
-
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
14722
|
-
* return value + 1;
|
|
14723
|
-
* });
|
|
14724
|
-
* {a: 2, b: 3}
|
|
14725
|
-
*/
|
|
14726
|
-
const mapObject = function mapObject(obj, lambda) {
|
|
14727
|
-
const result = {};
|
|
14728
|
-
Object.keys(obj).forEach(key => {
|
|
14729
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
14730
|
-
result[key] = lambda(obj[key], key);
|
|
14731
|
-
});
|
|
14732
|
-
return result;
|
|
14733
|
-
};
|
|
14734
|
-
|
|
14735
13970
|
/**
|
|
14736
13971
|
* MovableLine
|
|
14737
13972
|
*/
|
|
@@ -18547,7 +17782,7 @@ const inlineNodeTypes = {
|
|
|
18547
17782
|
|
|
18548
17783
|
// @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'.
|
|
18549
17784
|
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
18550
|
-
const parse$
|
|
17785
|
+
const parse$4 = (source, state) => {
|
|
18551
17786
|
const paragraphedSource = source + "\n\n";
|
|
18552
17787
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
18553
17788
|
inline: false
|
|
@@ -18630,14 +17865,14 @@ const getContent = ast => {
|
|
|
18630
17865
|
*/
|
|
18631
17866
|
const characterCount = source => {
|
|
18632
17867
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
18633
|
-
const ast = parse$
|
|
17868
|
+
const ast = parse$4(source);
|
|
18634
17869
|
const content = getContent(ast).join("");
|
|
18635
17870
|
return content.length;
|
|
18636
17871
|
};
|
|
18637
17872
|
var PerseusMarkdown = {
|
|
18638
17873
|
characterCount: characterCount,
|
|
18639
17874
|
traverseContent: traverseContent,
|
|
18640
|
-
parse: parse$
|
|
17875
|
+
parse: parse$4,
|
|
18641
17876
|
parseInline: inlineParser,
|
|
18642
17877
|
reactFor: SimpleMarkdown.reactFor,
|
|
18643
17878
|
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
@@ -19019,6 +18254,10 @@ const getWidgetScorer = name => {
|
|
|
19019
18254
|
var _widgets$name$scorer, _widgets$name4;
|
|
19020
18255
|
return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
|
|
19021
18256
|
};
|
|
18257
|
+
const getPublicWidgetOptionsFunction = name => {
|
|
18258
|
+
var _widgets$name$getPubl, _widgets$name5;
|
|
18259
|
+
return (_widgets$name$getPubl = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
18260
|
+
};
|
|
19022
18261
|
const getEditor = name => {
|
|
19023
18262
|
return _.has(editors, name) ? editors[name] : null;
|
|
19024
18263
|
};
|
|
@@ -19323,6 +18562,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19323
18562
|
getWidget: getWidget,
|
|
19324
18563
|
getWidgetExport: getWidgetExport,
|
|
19325
18564
|
getWidgetScorer: getWidgetScorer,
|
|
18565
|
+
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
19326
18566
|
getEditor: getEditor,
|
|
19327
18567
|
getTransform: getTransform,
|
|
19328
18568
|
getVersion: getVersion,
|
|
@@ -21866,7 +21106,7 @@ function scoreInputNumber(userInput, rubric, strings) {
|
|
|
21866
21106
|
inexact: rubric.inexact || undefined,
|
|
21867
21107
|
maxError: rubric.maxError,
|
|
21868
21108
|
forms: answerTypes[rubric.answerType].forms
|
|
21869
|
-
}
|
|
21109
|
+
});
|
|
21870
21110
|
|
|
21871
21111
|
// We may have received TeX; try to parse it before grading.
|
|
21872
21112
|
// If `currentValue` is not TeX, this should be a no-op.
|
|
@@ -22204,7 +21444,7 @@ function scoreNumericInput(userInput, rubric, strings) {
|
|
|
22204
21444
|
// TODO(merlob) backfill / delete
|
|
22205
21445
|
maxError: answer.maxError,
|
|
22206
21446
|
forms: validatorForms
|
|
22207
|
-
}
|
|
21447
|
+
});
|
|
22208
21448
|
};
|
|
22209
21449
|
|
|
22210
21450
|
// We may have received TeX; try to parse it before grading.
|
|
@@ -24309,7 +23549,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24309
23549
|
// TODO(LEMS-2656): remove TS suppression
|
|
24310
23550
|
// @ts-expect-error: State is not assignable to ParseState
|
|
24311
23551
|
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24312
|
-
const parse$
|
|
23552
|
+
const parse$3 = (source, state) => {
|
|
24313
23553
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24314
23554
|
state = state || {};
|
|
24315
23555
|
const paragraphedSource = source + "\n\n";
|
|
@@ -24319,7 +23559,7 @@ const parse$2 = (source, state) => {
|
|
|
24319
23559
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24320
23560
|
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
24321
23561
|
var PassageMarkdown = {
|
|
24322
|
-
parse: parse$
|
|
23562
|
+
parse: parse$3,
|
|
24323
23563
|
output,
|
|
24324
23564
|
START_REF_PREFIX,
|
|
24325
23565
|
END_REF_PREFIX,
|
|
@@ -26779,6 +26019,24 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
26779
26019
|
};
|
|
26780
26020
|
};
|
|
26781
26021
|
|
|
26022
|
+
/**
|
|
26023
|
+
* For details on the individual options, see the
|
|
26024
|
+
* PerseusCategorizerWidgetOptions type
|
|
26025
|
+
*/
|
|
26026
|
+
|
|
26027
|
+
/**
|
|
26028
|
+
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
26029
|
+
* the public options that should be exposed to the client.
|
|
26030
|
+
*/
|
|
26031
|
+
function getCategorizerPublicWidgetOptions(options) {
|
|
26032
|
+
return {
|
|
26033
|
+
items: options.items,
|
|
26034
|
+
categories: options.categories,
|
|
26035
|
+
randomizeItems: options.randomizeItems,
|
|
26036
|
+
static: options.static
|
|
26037
|
+
};
|
|
26038
|
+
}
|
|
26039
|
+
|
|
26782
26040
|
/**
|
|
26783
26041
|
* Checks userInput from the categorizer widget to see if the user has selected
|
|
26784
26042
|
* a category for each item.
|
|
@@ -26995,7 +26253,8 @@ var Categorizer$1 = {
|
|
|
26995
26253
|
isLintable: true,
|
|
26996
26254
|
// TODO(LEMS-2656): remove TS suppression
|
|
26997
26255
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
26998
|
-
scorer: scoreCategorizer
|
|
26256
|
+
scorer: scoreCategorizer,
|
|
26257
|
+
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
26999
26258
|
};
|
|
27000
26259
|
|
|
27001
26260
|
/**
|
|
@@ -27834,7 +27093,7 @@ class GradedGroup extends React.Component {
|
|
|
27834
27093
|
DEFAULT_INVALID_MESSAGE_2
|
|
27835
27094
|
} = this.context.strings;
|
|
27836
27095
|
const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
|
|
27837
|
-
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
27096
|
+
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${mapErrorToString(score.message)}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
27838
27097
|
this.setState({
|
|
27839
27098
|
status: status,
|
|
27840
27099
|
message: message,
|
|
@@ -28956,6 +28215,9 @@ const typeToButton = type => {
|
|
|
28956
28215
|
};
|
|
28957
28216
|
|
|
28958
28217
|
function getCoefficientsByType(data) {
|
|
28218
|
+
if (data.coords == null) {
|
|
28219
|
+
return undefined;
|
|
28220
|
+
}
|
|
28959
28221
|
if (data.type === "exponential" || data.type === "logarithm") {
|
|
28960
28222
|
const grader = functionForType(data.type);
|
|
28961
28223
|
return grader.getCoefficients(data.coords, data.asymptote);
|
|
@@ -31710,9 +30972,10 @@ const AxisTicks = () => {
|
|
|
31710
30972
|
* @param {number} axisIndex which axis we're getting options for
|
|
31711
30973
|
*/
|
|
31712
30974
|
const axisOptions = (props, axisIndex) => {
|
|
30975
|
+
const lines = props.markings === "axes" ? false : props.gridStep[axisIndex];
|
|
31713
30976
|
return {
|
|
31714
|
-
axis: props.markings === "graph",
|
|
31715
|
-
lines:
|
|
30977
|
+
axis: props.markings === "graph" || props.markings === "axes",
|
|
30978
|
+
lines: lines,
|
|
31716
30979
|
labels: false
|
|
31717
30980
|
};
|
|
31718
30981
|
};
|
|
@@ -32294,231 +31557,6 @@ const LegacyGrid = ({
|
|
|
32294
31557
|
return null;
|
|
32295
31558
|
};
|
|
32296
31559
|
|
|
32297
|
-
/**
|
|
32298
|
-
* The Perseus "data schema" file.
|
|
32299
|
-
*
|
|
32300
|
-
* This file, and the types in it, represents the "data schema" that Perseus
|
|
32301
|
-
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
32302
|
-
* that conform to the types in this file. Similarly, the top-level renderers
|
|
32303
|
-
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
32304
|
-
*
|
|
32305
|
-
* WARNING: This file should not import any types from elsewhere so that it is
|
|
32306
|
-
* easy to reason about changes that alter the Perseus schema. This helps
|
|
32307
|
-
* ensure that it is not changed accidentally when upgrading a dependant
|
|
32308
|
-
* package or other part of Perseus code. Note that TypeScript does type
|
|
32309
|
-
* checking via something called "structural typing". This means that as long
|
|
32310
|
-
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
32311
|
-
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
32312
|
-
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
32313
|
-
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
32314
|
-
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
32315
|
-
* labels are for humans, not TypeScript. :)
|
|
32316
|
-
*
|
|
32317
|
-
* If you make changes to types in this file, be very sure that:
|
|
32318
|
-
*
|
|
32319
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
32320
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
32321
|
-
* introduce hard-to-diagnose bugs.
|
|
32322
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
32323
|
-
* the new format _as well as_ the old format.
|
|
32324
|
-
*/
|
|
32325
|
-
|
|
32326
|
-
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
32327
|
-
|
|
32328
|
-
// Same name as Mafs
|
|
32329
|
-
|
|
32330
|
-
/**
|
|
32331
|
-
* Our core set of Perseus widgets.
|
|
32332
|
-
*
|
|
32333
|
-
* This interface is the basis for "registering" all Perseus widget types.
|
|
32334
|
-
* There should be one key/value pair for each supported widget. If you create
|
|
32335
|
-
* a new widget, an entry should be added to this interface. Note that this
|
|
32336
|
-
* only registers the widget options type, you'll also need to register the
|
|
32337
|
-
* widget so that it's available at runtime (@see
|
|
32338
|
-
* {@link file://./widgets.ts#registerWidget}).
|
|
32339
|
-
*
|
|
32340
|
-
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
32341
|
-
* widgets that is the same as the widget option's `type` field. In cases where
|
|
32342
|
-
* a widget has been deprecated and replaced with the deprecated-standin
|
|
32343
|
-
* widget, it should be the original widget type!
|
|
32344
|
-
*
|
|
32345
|
-
* If you define the widget outside of this package, you can still add the new
|
|
32346
|
-
* widget to this interface by writing the following in that package that
|
|
32347
|
-
* contains the widget. TypeScript will merge that definition of the
|
|
32348
|
-
* `PerseusWidgets` with the one defined below.
|
|
32349
|
-
*
|
|
32350
|
-
* ```typescript
|
|
32351
|
-
* declare module "@khanacademy/perseus" {
|
|
32352
|
-
* interface PerseusWidgetTypes {
|
|
32353
|
-
* // A new widget
|
|
32354
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
32355
|
-
*
|
|
32356
|
-
* // A deprecated widget
|
|
32357
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
32358
|
-
* }
|
|
32359
|
-
* }
|
|
32360
|
-
*
|
|
32361
|
-
* // The new widget's options definition
|
|
32362
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
32363
|
-
*
|
|
32364
|
-
* // The deprecated widget's options definition
|
|
32365
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
32366
|
-
* ```
|
|
32367
|
-
*
|
|
32368
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
32369
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
32370
|
-
*
|
|
32371
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
32372
|
-
*/
|
|
32373
|
-
|
|
32374
|
-
/**
|
|
32375
|
-
* A map of widget IDs to widget options. This is most often used as the type
|
|
32376
|
-
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
32377
|
-
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
32378
|
-
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
32379
|
-
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
32380
|
-
* reading/parsing the widget id to derive any information from it, except in
|
|
32381
|
-
* the case of this map.
|
|
32382
|
-
*
|
|
32383
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
32384
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
32385
|
-
*/
|
|
32386
|
-
|
|
32387
|
-
/**
|
|
32388
|
-
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
32389
|
-
* `ServerItemRenderer` and the layout is pre-set.
|
|
32390
|
-
*
|
|
32391
|
-
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
32392
|
-
* area.
|
|
32393
|
-
*/
|
|
32394
|
-
|
|
32395
|
-
/**
|
|
32396
|
-
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32397
|
-
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32398
|
-
*/
|
|
32399
|
-
|
|
32400
|
-
const ItemExtras = [
|
|
32401
|
-
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
32402
|
-
"calculator",
|
|
32403
|
-
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
32404
|
-
"chi2Table",
|
|
32405
|
-
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
32406
|
-
"financialCalculatorMonthlyPayment",
|
|
32407
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
32408
|
-
"financialCalculatorTotalAmount",
|
|
32409
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
32410
|
-
"financialCalculatorTimeToPayOff",
|
|
32411
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
32412
|
-
"periodicTable",
|
|
32413
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
32414
|
-
"periodicTableWithKey",
|
|
32415
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
32416
|
-
"tTable",
|
|
32417
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
32418
|
-
"zTable"];
|
|
32419
|
-
|
|
32420
|
-
/**
|
|
32421
|
-
* The type representing the common structure of all widget's options. The
|
|
32422
|
-
* `Options` generic type represents the widget-specific option data.
|
|
32423
|
-
*/
|
|
32424
|
-
|
|
32425
|
-
// prettier-ignore
|
|
32426
|
-
|
|
32427
|
-
// prettier-ignore
|
|
32428
|
-
|
|
32429
|
-
// prettier-ignore
|
|
32430
|
-
|
|
32431
|
-
// prettier-ignore
|
|
32432
|
-
|
|
32433
|
-
// prettier-ignore
|
|
32434
|
-
|
|
32435
|
-
// prettier-ignore
|
|
32436
|
-
|
|
32437
|
-
// prettier-ignore
|
|
32438
|
-
|
|
32439
|
-
// prettier-ignore
|
|
32440
|
-
|
|
32441
|
-
// prettier-ignore
|
|
32442
|
-
|
|
32443
|
-
// prettier-ignore
|
|
32444
|
-
|
|
32445
|
-
// prettier-ignore
|
|
32446
|
-
|
|
32447
|
-
// prettier-ignore
|
|
32448
|
-
|
|
32449
|
-
// prettier-ignore
|
|
32450
|
-
|
|
32451
|
-
// prettier-ignore
|
|
32452
|
-
|
|
32453
|
-
// prettier-ignore
|
|
32454
|
-
|
|
32455
|
-
// prettier-ignore
|
|
32456
|
-
|
|
32457
|
-
// prettier-ignore
|
|
32458
|
-
|
|
32459
|
-
// prettier-ignore
|
|
32460
|
-
|
|
32461
|
-
// prettier-ignore
|
|
32462
|
-
|
|
32463
|
-
// prettier-ignore
|
|
32464
|
-
|
|
32465
|
-
// prettier-ignore
|
|
32466
|
-
|
|
32467
|
-
// prettier-ignore
|
|
32468
|
-
|
|
32469
|
-
// prettier-ignore
|
|
32470
|
-
|
|
32471
|
-
// prettier-ignore
|
|
32472
|
-
|
|
32473
|
-
// prettier-ignore
|
|
32474
|
-
|
|
32475
|
-
// prettier-ignore
|
|
32476
|
-
|
|
32477
|
-
// prettier-ignore
|
|
32478
|
-
|
|
32479
|
-
// prettier-ignore
|
|
32480
|
-
|
|
32481
|
-
// prettier-ignore
|
|
32482
|
-
|
|
32483
|
-
// prettier-ignore
|
|
32484
|
-
|
|
32485
|
-
// prettier-ignore
|
|
32486
|
-
|
|
32487
|
-
// prettier-ignore
|
|
32488
|
-
|
|
32489
|
-
// prettier-ignore
|
|
32490
|
-
|
|
32491
|
-
//prettier-ignore
|
|
32492
|
-
|
|
32493
|
-
/**
|
|
32494
|
-
* A background image applied to various widgets.
|
|
32495
|
-
*/
|
|
32496
|
-
|
|
32497
|
-
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32498
|
-
|
|
32499
|
-
// 2D range: xMin, xMax, yMin, yMax
|
|
32500
|
-
|
|
32501
|
-
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
32502
|
-
const lockedFigureColors = {
|
|
32503
|
-
blue: "#3D7586",
|
|
32504
|
-
green: "#447A53",
|
|
32505
|
-
grayH: "#3B3D45",
|
|
32506
|
-
purple: "#594094",
|
|
32507
|
-
pink: "#B25071",
|
|
32508
|
-
red: "#D92916",
|
|
32509
|
-
orange: "#946700"
|
|
32510
|
-
};
|
|
32511
|
-
const lockedFigureFillStyles = {
|
|
32512
|
-
none: 0,
|
|
32513
|
-
white: 1,
|
|
32514
|
-
translucent: 0.4,
|
|
32515
|
-
solid: 1
|
|
32516
|
-
};
|
|
32517
|
-
|
|
32518
|
-
// Not associated with a specific figure
|
|
32519
|
-
|
|
32520
|
-
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
32521
|
-
|
|
32522
31560
|
function LockedLabel(props) {
|
|
32523
31561
|
const {
|
|
32524
31562
|
coord,
|
|
@@ -32583,7 +31621,8 @@ const LockedEllipse = props => {
|
|
|
32583
31621
|
return /*#__PURE__*/React.createElement("g", {
|
|
32584
31622
|
className: "locked-ellipse",
|
|
32585
31623
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
32586
|
-
"aria-hidden": !hasAria
|
|
31624
|
+
"aria-hidden": !hasAria,
|
|
31625
|
+
role: "img"
|
|
32587
31626
|
}, /*#__PURE__*/React.createElement(Ellipse, {
|
|
32588
31627
|
center: center,
|
|
32589
31628
|
radius: radius,
|
|
@@ -32628,7 +31667,8 @@ const LockedFunction = props => {
|
|
|
32628
31667
|
return /*#__PURE__*/React.createElement("g", {
|
|
32629
31668
|
className: "locked-function",
|
|
32630
31669
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
32631
|
-
"aria-hidden": !hasAria
|
|
31670
|
+
"aria-hidden": !hasAria,
|
|
31671
|
+
role: "img"
|
|
32632
31672
|
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
|
|
32633
31673
|
y: x => equation.eval({
|
|
32634
31674
|
x
|
|
@@ -32785,7 +31825,8 @@ const LockedLine = props => {
|
|
|
32785
31825
|
return /*#__PURE__*/React.createElement("g", {
|
|
32786
31826
|
className: kind === "ray" ? "locked-ray" : "locked-line",
|
|
32787
31827
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
32788
|
-
"aria-hidden": !hasAria
|
|
31828
|
+
"aria-hidden": !hasAria,
|
|
31829
|
+
role: "img"
|
|
32789
31830
|
}, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
|
|
32790
31831
|
x: point1.coord[X],
|
|
32791
31832
|
y: point1.coord[Y],
|
|
@@ -32821,7 +31862,8 @@ const LockedPoint = props => {
|
|
|
32821
31862
|
return /*#__PURE__*/React.createElement("g", {
|
|
32822
31863
|
className: "locked-point",
|
|
32823
31864
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
32824
|
-
"aria-hidden": !hasAria
|
|
31865
|
+
"aria-hidden": !hasAria,
|
|
31866
|
+
role: "img"
|
|
32825
31867
|
}, /*#__PURE__*/React.createElement(Point$2, {
|
|
32826
31868
|
x: x,
|
|
32827
31869
|
y: y,
|
|
@@ -32847,7 +31889,8 @@ const LockedPolygon = props => {
|
|
|
32847
31889
|
return /*#__PURE__*/React.createElement("g", {
|
|
32848
31890
|
className: "locked-polygon",
|
|
32849
31891
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
32850
|
-
"aria-hidden": !hasAria
|
|
31892
|
+
"aria-hidden": !hasAria,
|
|
31893
|
+
role: "img"
|
|
32851
31894
|
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
32852
31895
|
points: [...points],
|
|
32853
31896
|
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
@@ -32881,7 +31924,8 @@ const LockedVector = props => {
|
|
|
32881
31924
|
return /*#__PURE__*/React.createElement("g", {
|
|
32882
31925
|
className: "locked-vector",
|
|
32883
31926
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
32884
|
-
"aria-hidden": !hasAria
|
|
31927
|
+
"aria-hidden": !hasAria,
|
|
31928
|
+
role: "img"
|
|
32885
31929
|
}, /*#__PURE__*/React.createElement(Vector, {
|
|
32886
31930
|
tail: tail,
|
|
32887
31931
|
tip: tip,
|
|
@@ -34289,7 +33333,9 @@ function renderCircleGraph(state, dispatch) {
|
|
|
34289
33333
|
graphState: state,
|
|
34290
33334
|
dispatch: dispatch
|
|
34291
33335
|
}),
|
|
34292
|
-
interactiveElementsDescription:
|
|
33336
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(CircleGraphDescription, {
|
|
33337
|
+
state: state
|
|
33338
|
+
})
|
|
34293
33339
|
};
|
|
34294
33340
|
}
|
|
34295
33341
|
function CircleGraph(props) {
|
|
@@ -34313,38 +33359,26 @@ function CircleGraph(props) {
|
|
|
34313
33359
|
const outerPointsId = id + "-outer-points";
|
|
34314
33360
|
|
|
34315
33361
|
// Aria label strings
|
|
34316
|
-
const
|
|
34317
|
-
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
const circleRadiusDescription = strings.srCircleRadius({
|
|
34326
|
-
radius
|
|
34327
|
-
});
|
|
34328
|
-
const circleOuterPointsDescription = strings.srCircleOuterPoints({
|
|
34329
|
-
point1X: srFormatNumber(center[0] + radius, locale),
|
|
34330
|
-
point1Y: srFormatNumber(center[1], locale),
|
|
34331
|
-
point2X: srFormatNumber(center[0], locale),
|
|
34332
|
-
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
34333
|
-
point3X: srFormatNumber(center[0] - radius, locale),
|
|
34334
|
-
point3Y: srFormatNumber(center[1], locale),
|
|
34335
|
-
point4X: srFormatNumber(center[0], locale),
|
|
34336
|
-
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33362
|
+
const {
|
|
33363
|
+
srCircleGraph,
|
|
33364
|
+
srCircleShape,
|
|
33365
|
+
srCircleRadiusPoint,
|
|
33366
|
+
srCircleRadius,
|
|
33367
|
+
srCircleOuterPoints
|
|
33368
|
+
} = describeCircleGraph(graphState, {
|
|
33369
|
+
strings,
|
|
33370
|
+
locale
|
|
34337
33371
|
});
|
|
34338
33372
|
return /*#__PURE__*/React.createElement("g", {
|
|
34339
33373
|
// Outer circle minimal description
|
|
34340
|
-
"aria-label":
|
|
33374
|
+
"aria-label": srCircleGraph,
|
|
34341
33375
|
"aria-describedby": `${circleId} ${radiusId} ${outerPointsId}`
|
|
34342
33376
|
}, /*#__PURE__*/React.createElement(MovableCircle, {
|
|
34343
33377
|
id: circleId
|
|
34344
33378
|
// Focusable circle aria label reads with every update
|
|
34345
33379
|
// because of the aria-live property in the circle <g>.
|
|
34346
33380
|
,
|
|
34347
|
-
ariaLabel:
|
|
33381
|
+
ariaLabel: srCircleShape
|
|
34348
33382
|
// Aria-describedby describes additional info on focus.
|
|
34349
33383
|
,
|
|
34350
33384
|
ariaDescribedBy: `${radiusId} ${outerPointsId}`,
|
|
@@ -34357,7 +33391,7 @@ function CircleGraph(props) {
|
|
|
34357
33391
|
}), /*#__PURE__*/React.createElement(MovablePoint$1
|
|
34358
33392
|
// Radius point aria label reads with every update.
|
|
34359
33393
|
, {
|
|
34360
|
-
ariaLabel: `${
|
|
33394
|
+
ariaLabel: `${srCircleRadiusPoint} ${srCircleRadius}`
|
|
34361
33395
|
// Aria-describedby describes additional info on focus.
|
|
34362
33396
|
,
|
|
34363
33397
|
ariaDescribedBy: `${outerPointsId}`
|
|
@@ -34380,12 +33414,12 @@ function CircleGraph(props) {
|
|
|
34380
33414
|
style: {
|
|
34381
33415
|
display: "hidden"
|
|
34382
33416
|
}
|
|
34383
|
-
},
|
|
33417
|
+
}, srCircleRadius), /*#__PURE__*/React.createElement("g", {
|
|
34384
33418
|
id: outerPointsId,
|
|
34385
33419
|
style: {
|
|
34386
33420
|
display: "hidden"
|
|
34387
33421
|
}
|
|
34388
|
-
},
|
|
33422
|
+
}, srCircleOuterPoints));
|
|
34389
33423
|
}
|
|
34390
33424
|
function MovableCircle(props) {
|
|
34391
33425
|
const {
|
|
@@ -34472,6 +33506,64 @@ function crossProduct(as, bs) {
|
|
|
34472
33506
|
}
|
|
34473
33507
|
return result;
|
|
34474
33508
|
}
|
|
33509
|
+
function CircleGraphDescription({
|
|
33510
|
+
state
|
|
33511
|
+
}) {
|
|
33512
|
+
// The reason that CircleGraphDescription is a component (rather than a
|
|
33513
|
+
// function that returns a string) is because it needs to use a
|
|
33514
|
+
// hook: `usePerseusI18n`.
|
|
33515
|
+
const i18n = usePerseusI18n();
|
|
33516
|
+
const strings = describeCircleGraph(state, i18n);
|
|
33517
|
+
return strings.srCircleInteractiveElement;
|
|
33518
|
+
}
|
|
33519
|
+
|
|
33520
|
+
// Exported for testing
|
|
33521
|
+
function describeCircleGraph(state, i18n) {
|
|
33522
|
+
const {
|
|
33523
|
+
strings,
|
|
33524
|
+
locale
|
|
33525
|
+
} = i18n;
|
|
33526
|
+
const {
|
|
33527
|
+
center,
|
|
33528
|
+
radiusPoint
|
|
33529
|
+
} = state;
|
|
33530
|
+
const radius = getRadius(state);
|
|
33531
|
+
|
|
33532
|
+
// Aria label strings
|
|
33533
|
+
const srCircleGraph = strings.srCircleGraph;
|
|
33534
|
+
const srCircleShape = strings.srCircleShape({
|
|
33535
|
+
centerX: srFormatNumber(center[0], locale),
|
|
33536
|
+
centerY: srFormatNumber(center[1], locale)
|
|
33537
|
+
});
|
|
33538
|
+
const srCircleRadiusPoint = strings.srCircleRadiusPoint({
|
|
33539
|
+
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
33540
|
+
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
33541
|
+
});
|
|
33542
|
+
const srCircleRadius = strings.srCircleRadius({
|
|
33543
|
+
radius
|
|
33544
|
+
});
|
|
33545
|
+
const srCircleOuterPoints = strings.srCircleOuterPoints({
|
|
33546
|
+
point1X: srFormatNumber(center[0] + radius, locale),
|
|
33547
|
+
point1Y: srFormatNumber(center[1], locale),
|
|
33548
|
+
point2X: srFormatNumber(center[0], locale),
|
|
33549
|
+
point2Y: srFormatNumber(center[1] + radius, locale),
|
|
33550
|
+
point3X: srFormatNumber(center[0] - radius, locale),
|
|
33551
|
+
point3Y: srFormatNumber(center[1], locale),
|
|
33552
|
+
point4X: srFormatNumber(center[0], locale),
|
|
33553
|
+
point4Y: srFormatNumber(center[1] - radius, locale)
|
|
33554
|
+
});
|
|
33555
|
+
const srCircleInteractiveElement = strings.srInteractiveElements({
|
|
33556
|
+
elements: [srCircleShape, srCircleRadius].join(" ")
|
|
33557
|
+
});
|
|
33558
|
+
return {
|
|
33559
|
+
srCircleGraph,
|
|
33560
|
+
srCircleShape,
|
|
33561
|
+
srCircleRadiusPoint,
|
|
33562
|
+
srCircleRadius,
|
|
33563
|
+
srCircleOuterPoints,
|
|
33564
|
+
srCircleInteractiveElement
|
|
33565
|
+
};
|
|
33566
|
+
}
|
|
34475
33567
|
|
|
34476
33568
|
function renderLinearGraph(state, dispatch) {
|
|
34477
33569
|
return {
|
|
@@ -35462,7 +34554,9 @@ const MafsGraph = props => {
|
|
|
35462
34554
|
labels,
|
|
35463
34555
|
disableKeyboardInteraction: readOnly || !!props.static
|
|
35464
34556
|
}
|
|
35465
|
-
}, /*#__PURE__*/React.createElement(View,
|
|
34557
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
34558
|
+
className: "mafs-graph-container"
|
|
34559
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
35466
34560
|
className: "mafs-graph",
|
|
35467
34561
|
style: {
|
|
35468
34562
|
position: "relative",
|
|
@@ -35505,7 +34599,7 @@ const MafsGraph = props => {
|
|
|
35505
34599
|
bottom: 0,
|
|
35506
34600
|
left: 0
|
|
35507
34601
|
}
|
|
35508
|
-
}, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
34602
|
+
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
35509
34603
|
preserveAspectRatio: false,
|
|
35510
34604
|
viewBox: {
|
|
35511
34605
|
x: state.range[X],
|
|
@@ -35525,7 +34619,7 @@ const MafsGraph = props => {
|
|
|
35525
34619
|
height: height
|
|
35526
34620
|
})),
|
|
35527
34621
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
35528
|
-
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
34622
|
+
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
35529
34623
|
lockedFigures: props.lockedFigures,
|
|
35530
34624
|
range: state.range
|
|
35531
34625
|
}))), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
@@ -41025,7 +40119,7 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41025
40119
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
41026
40120
|
solution[row][col], {
|
|
41027
40121
|
simplify: true
|
|
41028
|
-
}
|
|
40122
|
+
});
|
|
41029
40123
|
const result = validator(supplied[row][col]);
|
|
41030
40124
|
if (result.message) {
|
|
41031
40125
|
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
@@ -42178,10 +41272,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
42178
41272
|
const firstChar = smiles[0];
|
|
42179
41273
|
const rest = smiles.slice(1);
|
|
42180
41274
|
if (firstChar === "=") {
|
|
42181
|
-
return parse$
|
|
41275
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
42182
41276
|
}
|
|
42183
41277
|
if (firstChar === "#") {
|
|
42184
|
-
return parse$
|
|
41278
|
+
return parse$2(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
42185
41279
|
}
|
|
42186
41280
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
42187
41281
|
}
|
|
@@ -42240,8 +41334,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
42240
41334
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
42241
41335
|
parens: newCtx.parens.concat("(")
|
|
42242
41336
|
});
|
|
42243
|
-
const parenExpr = parse$
|
|
42244
|
-
const remainder = parse$
|
|
41337
|
+
const parenExpr = parse$2(rest, parenCtx);
|
|
41338
|
+
const remainder = parse$2(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
42245
41339
|
return [parenExpr].concat(remainder);
|
|
42246
41340
|
}
|
|
42247
41341
|
if (firstChar === ")") {
|
|
@@ -42305,7 +41399,7 @@ function parseAtom(smiles, ctx) {
|
|
|
42305
41399
|
|
|
42306
41400
|
// increment the atom counter and reset the branch counter
|
|
42307
41401
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
42308
|
-
let restOfMolecule = parse$
|
|
41402
|
+
let restOfMolecule = parse$2(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
42309
41403
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
42310
41404
|
//TODO(colin): fix this awkwardness.
|
|
42311
41405
|
restOfMolecule = [restOfMolecule];
|
|
@@ -42344,7 +41438,7 @@ function isModifierChar(s) {
|
|
|
42344
41438
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
42345
41439
|
* yet implemented.
|
|
42346
41440
|
*/
|
|
42347
|
-
function parse$
|
|
41441
|
+
function parse$2(smiles, ctx) {
|
|
42348
41442
|
if (!validate(smiles)) {
|
|
42349
41443
|
throw new ParseError$1("Invalid input.");
|
|
42350
41444
|
}
|
|
@@ -42369,7 +41463,7 @@ function parse$1(smiles, ctx) {
|
|
|
42369
41463
|
return parseParenthesizedExpression(smiles, ctx);
|
|
42370
41464
|
}
|
|
42371
41465
|
var SmilesParser = {
|
|
42372
|
-
parse: parse$
|
|
41466
|
+
parse: parse$2,
|
|
42373
41467
|
ParseError: ParseError$1
|
|
42374
41468
|
};
|
|
42375
41469
|
|
|
@@ -42377,7 +41471,7 @@ var SmilesParser = {
|
|
|
42377
41471
|
const {
|
|
42378
41472
|
layout
|
|
42379
41473
|
} = MoleculeLayout;
|
|
42380
|
-
const parse = SmilesParser.parse;
|
|
41474
|
+
const parse$1 = SmilesParser.parse;
|
|
42381
41475
|
const ParseError = SmilesParser.ParseError;
|
|
42382
41476
|
const borderSize = 30;
|
|
42383
41477
|
class Molecule extends React.Component {
|
|
@@ -42391,7 +41485,7 @@ class Molecule extends React.Component {
|
|
|
42391
41485
|
try {
|
|
42392
41486
|
this.setState({
|
|
42393
41487
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
42394
|
-
parsedSmiles: parse(smiles),
|
|
41488
|
+
parsedSmiles: parse$1(smiles),
|
|
42395
41489
|
error: null
|
|
42396
41490
|
});
|
|
42397
41491
|
} catch (e) {
|
|
@@ -45077,7 +44171,7 @@ function scoreTable(userInput, rubric, strings) {
|
|
|
45077
44171
|
const cellSolution = rowSolution[i];
|
|
45078
44172
|
const validator = createValidator(cellSolution, {
|
|
45079
44173
|
simplify: true
|
|
45080
|
-
}
|
|
44174
|
+
});
|
|
45081
44175
|
const result = validator(cellSupplied);
|
|
45082
44176
|
if (result.message) {
|
|
45083
44177
|
message = result.message;
|
|
@@ -45501,7 +44595,7 @@ const init = function init() {
|
|
|
45501
44595
|
|
|
45502
44596
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45503
44597
|
const libName = "@khanacademy/perseus";
|
|
45504
|
-
const libVersion = "
|
|
44598
|
+
const libVersion = "50.1.0";
|
|
45505
44599
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45506
44600
|
|
|
45507
44601
|
const apiVersion = {
|
|
@@ -47582,6 +46676,69 @@ function all(results, combineFailureDetails = a => a) {
|
|
|
47582
46676
|
return success(values);
|
|
47583
46677
|
}
|
|
47584
46678
|
|
|
46679
|
+
class ErrorTrackingParseContext {
|
|
46680
|
+
constructor(path) {
|
|
46681
|
+
this.path = path;
|
|
46682
|
+
}
|
|
46683
|
+
failure(expected, badValue) {
|
|
46684
|
+
return failure([{
|
|
46685
|
+
expected: wrapInArray(expected),
|
|
46686
|
+
badValue,
|
|
46687
|
+
path: this.path
|
|
46688
|
+
}]);
|
|
46689
|
+
}
|
|
46690
|
+
forSubtree(key) {
|
|
46691
|
+
return new ErrorTrackingParseContext([...this.path, key]);
|
|
46692
|
+
}
|
|
46693
|
+
success(value) {
|
|
46694
|
+
return success(value);
|
|
46695
|
+
}
|
|
46696
|
+
}
|
|
46697
|
+
function wrapInArray(a) {
|
|
46698
|
+
return Array.isArray(a) ? a : [a];
|
|
46699
|
+
}
|
|
46700
|
+
|
|
46701
|
+
function formatPath(path) {
|
|
46702
|
+
return "(root)" + path.map(formatPathSegment).join("");
|
|
46703
|
+
}
|
|
46704
|
+
function formatPathSegment(segment) {
|
|
46705
|
+
if (typeof segment === "string") {
|
|
46706
|
+
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
46707
|
+
}
|
|
46708
|
+
return `[${segment.toString()}]`;
|
|
46709
|
+
}
|
|
46710
|
+
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
46711
|
+
|
|
46712
|
+
function message(failure) {
|
|
46713
|
+
const expected = conjoin(failure.expected);
|
|
46714
|
+
const path = formatPath(failure.path);
|
|
46715
|
+
const badValue = JSON.stringify(failure.badValue);
|
|
46716
|
+
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
46717
|
+
}
|
|
46718
|
+
function conjoin(items) {
|
|
46719
|
+
switch (items.length) {
|
|
46720
|
+
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
46721
|
+
case 1:
|
|
46722
|
+
return items[0];
|
|
46723
|
+
case 2:
|
|
46724
|
+
return items.join(" or ");
|
|
46725
|
+
default:
|
|
46726
|
+
{
|
|
46727
|
+
const allButLast = items.slice(0, items.length - 1);
|
|
46728
|
+
const last = items[items.length - 1];
|
|
46729
|
+
return allButLast.join(", ") + ", or " + last;
|
|
46730
|
+
}
|
|
46731
|
+
}
|
|
46732
|
+
}
|
|
46733
|
+
|
|
46734
|
+
function parse(value, parser) {
|
|
46735
|
+
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
46736
|
+
if (isFailure(result)) {
|
|
46737
|
+
return failure(result.detail.map(message).join("; "));
|
|
46738
|
+
}
|
|
46739
|
+
return result;
|
|
46740
|
+
}
|
|
46741
|
+
|
|
47585
46742
|
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
47586
46743
|
|
|
47587
46744
|
function array(elementParser) {
|
|
@@ -47862,7 +47019,6 @@ const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
|
47862
47019
|
})),
|
|
47863
47020
|
showEditor: boolean,
|
|
47864
47021
|
showButtons: boolean,
|
|
47865
|
-
width: number,
|
|
47866
47022
|
height: number,
|
|
47867
47023
|
static: defaulted(boolean, () => false)
|
|
47868
47024
|
}));
|
|
@@ -48049,25 +47205,6 @@ function migrateV0ToV1(widget) {
|
|
|
48049
47205
|
}
|
|
48050
47206
|
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
48051
47207
|
|
|
48052
|
-
const parsePerseusRenderer = defaulted(object({
|
|
48053
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
48054
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
48055
|
-
content: defaulted(string, () => ""),
|
|
48056
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
48057
|
-
// `group` widget can contain another renderer.
|
|
48058
|
-
// The anonymous function below ensures that we don't try to access
|
|
48059
|
-
// parseWidgetsMap before it's defined.
|
|
48060
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48061
|
-
metadata: optional(array(string)),
|
|
48062
|
-
images: parseImages
|
|
48063
|
-
}),
|
|
48064
|
-
// Default value
|
|
48065
|
-
() => ({
|
|
48066
|
-
content: "",
|
|
48067
|
-
widgets: {},
|
|
48068
|
-
images: {}
|
|
48069
|
-
}));
|
|
48070
|
-
|
|
48071
47208
|
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
48072
47209
|
const parseGradedGroupWidgetOptions = object({
|
|
48073
47210
|
title: defaulted(string, () => ""),
|
|
@@ -48138,27 +47275,27 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
|
48138
47275
|
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
48139
47276
|
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
48140
47277
|
type: constant("absolute_value"),
|
|
48141
|
-
coords: pairOfPoints
|
|
47278
|
+
coords: nullable(pairOfPoints)
|
|
48142
47279
|
})).withBranch("exponential", object({
|
|
48143
47280
|
type: constant("exponential"),
|
|
48144
47281
|
asymptote: pairOfPoints,
|
|
48145
|
-
coords: pairOfPoints
|
|
47282
|
+
coords: nullable(pairOfPoints)
|
|
48146
47283
|
})).withBranch("linear", object({
|
|
48147
47284
|
type: constant("linear"),
|
|
48148
|
-
coords:
|
|
47285
|
+
coords: nullable(pairOfPoints)
|
|
48149
47286
|
})).withBranch("logarithm", object({
|
|
48150
47287
|
type: constant("logarithm"),
|
|
48151
47288
|
asymptote: pairOfPoints,
|
|
48152
|
-
coords: pairOfPoints
|
|
47289
|
+
coords: nullable(pairOfPoints)
|
|
48153
47290
|
})).withBranch("quadratic", object({
|
|
48154
47291
|
type: constant("quadratic"),
|
|
48155
|
-
coords: pairOfPoints
|
|
47292
|
+
coords: nullable(pairOfPoints)
|
|
48156
47293
|
})).withBranch("sinusoid", object({
|
|
48157
47294
|
type: constant("sinusoid"),
|
|
48158
|
-
coords: pairOfPoints
|
|
47295
|
+
coords: nullable(pairOfPoints)
|
|
48159
47296
|
})).withBranch("tangent", object({
|
|
48160
47297
|
type: constant("tangent"),
|
|
48161
|
-
coords: pairOfPoints
|
|
47298
|
+
coords: nullable(pairOfPoints)
|
|
48162
47299
|
})).parser,
|
|
48163
47300
|
graph: object({
|
|
48164
47301
|
backgroundImage: object({
|
|
@@ -48194,13 +47331,13 @@ const parseGroupWidget = parseWidget(constant("group"),
|
|
|
48194
47331
|
|
|
48195
47332
|
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
48196
47333
|
url: string,
|
|
48197
|
-
settings: array(object({
|
|
47334
|
+
settings: optional(array(object({
|
|
48198
47335
|
name: string,
|
|
48199
47336
|
value: string
|
|
48200
|
-
})),
|
|
47337
|
+
}))),
|
|
48201
47338
|
width: union(number).or(string).parser,
|
|
48202
47339
|
height: union(number).or(string).parser,
|
|
48203
|
-
allowFullScreen: boolean,
|
|
47340
|
+
allowFullScreen: defaulted(boolean, () => false),
|
|
48204
47341
|
allowTopNavigation: optional(boolean),
|
|
48205
47342
|
static: defaulted(boolean, () => false)
|
|
48206
47343
|
}));
|
|
@@ -48273,10 +47410,10 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
48273
47410
|
|
|
48274
47411
|
const pairOfNumbers$1 = pair(number, number);
|
|
48275
47412
|
const stringOrEmpty = defaulted(string, () => "");
|
|
48276
|
-
const
|
|
47413
|
+
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
48277
47414
|
const parseFunctionElement = object({
|
|
48278
|
-
type:
|
|
48279
|
-
key:
|
|
47415
|
+
type: constant("function"),
|
|
47416
|
+
key: parseKey,
|
|
48280
47417
|
options: object({
|
|
48281
47418
|
value: string,
|
|
48282
47419
|
funcName: string,
|
|
@@ -48287,10 +47424,9 @@ const parseFunctionElement = object({
|
|
|
48287
47424
|
strokeWidth: number
|
|
48288
47425
|
})
|
|
48289
47426
|
});
|
|
48290
|
-
const parseLabelType = constant("label");
|
|
48291
47427
|
const parseLabelElement = object({
|
|
48292
|
-
type:
|
|
48293
|
-
key:
|
|
47428
|
+
type: constant("label"),
|
|
47429
|
+
key: parseKey,
|
|
48294
47430
|
options: object({
|
|
48295
47431
|
label: string,
|
|
48296
47432
|
color: string,
|
|
@@ -48298,10 +47434,9 @@ const parseLabelElement = object({
|
|
|
48298
47434
|
coordY: string
|
|
48299
47435
|
})
|
|
48300
47436
|
});
|
|
48301
|
-
const parseLineType = constant("line");
|
|
48302
47437
|
const parseLineElement = object({
|
|
48303
|
-
type:
|
|
48304
|
-
key:
|
|
47438
|
+
type: constant("line"),
|
|
47439
|
+
key: parseKey,
|
|
48305
47440
|
options: object({
|
|
48306
47441
|
color: string,
|
|
48307
47442
|
startX: string,
|
|
@@ -48313,10 +47448,9 @@ const parseLineElement = object({
|
|
|
48313
47448
|
arrows: string
|
|
48314
47449
|
})
|
|
48315
47450
|
});
|
|
48316
|
-
const parseMovableLineType = constant("movable-line");
|
|
48317
47451
|
const parseMovableLineElement = object({
|
|
48318
|
-
type:
|
|
48319
|
-
key:
|
|
47452
|
+
type: constant("movable-line"),
|
|
47453
|
+
key: parseKey,
|
|
48320
47454
|
options: object({
|
|
48321
47455
|
startX: string,
|
|
48322
47456
|
startY: string,
|
|
@@ -48333,10 +47467,9 @@ const parseMovableLineElement = object({
|
|
|
48333
47467
|
constraintYMax: string
|
|
48334
47468
|
})
|
|
48335
47469
|
});
|
|
48336
|
-
const parseMovablePointType = constant("movable-point");
|
|
48337
47470
|
const parseMovablePointElement = object({
|
|
48338
|
-
type:
|
|
48339
|
-
key:
|
|
47471
|
+
type: constant("movable-point"),
|
|
47472
|
+
key: parseKey,
|
|
48340
47473
|
options: object({
|
|
48341
47474
|
startX: string,
|
|
48342
47475
|
startY: string,
|
|
@@ -48350,10 +47483,9 @@ const parseMovablePointElement = object({
|
|
|
48350
47483
|
constraintYMax: stringOrEmpty
|
|
48351
47484
|
})
|
|
48352
47485
|
});
|
|
48353
|
-
const parseParametricType = constant("parametric");
|
|
48354
47486
|
const parseParametricElement = object({
|
|
48355
|
-
type:
|
|
48356
|
-
key:
|
|
47487
|
+
type: constant("parametric"),
|
|
47488
|
+
key: parseKey,
|
|
48357
47489
|
options: object({
|
|
48358
47490
|
x: string,
|
|
48359
47491
|
y: string,
|
|
@@ -48364,20 +47496,18 @@ const parseParametricElement = object({
|
|
|
48364
47496
|
strokeWidth: number
|
|
48365
47497
|
})
|
|
48366
47498
|
});
|
|
48367
|
-
const parsePointType = constant("point");
|
|
48368
47499
|
const parsePointElement = object({
|
|
48369
|
-
type:
|
|
48370
|
-
key:
|
|
47500
|
+
type: constant("point"),
|
|
47501
|
+
key: parseKey,
|
|
48371
47502
|
options: object({
|
|
48372
47503
|
color: string,
|
|
48373
47504
|
coordX: string,
|
|
48374
47505
|
coordY: string
|
|
48375
47506
|
})
|
|
48376
47507
|
});
|
|
48377
|
-
const parseRectangleType = constant("rectangle");
|
|
48378
47508
|
const parseRectangleElement = object({
|
|
48379
|
-
type:
|
|
48380
|
-
key:
|
|
47509
|
+
type: constant("rectangle"),
|
|
47510
|
+
key: parseKey,
|
|
48381
47511
|
options: object({
|
|
48382
47512
|
color: string,
|
|
48383
47513
|
coordX: string,
|
|
@@ -48523,8 +47653,8 @@ const parseLockedLineType = object({
|
|
|
48523
47653
|
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48524
47654
|
color: parseLockedFigureColor,
|
|
48525
47655
|
lineStyle: parseLockedLineStyle,
|
|
48526
|
-
showPoint1: boolean,
|
|
48527
|
-
showPoint2: boolean,
|
|
47656
|
+
showPoint1: defaulted(boolean, () => false),
|
|
47657
|
+
showPoint2: defaulted(boolean, () => false),
|
|
48528
47658
|
// TODO(benchristel): default labels to empty array?
|
|
48529
47659
|
labels: optional(array(parseLockedLabelType)),
|
|
48530
47660
|
ariaLabel: optional(string)
|
|
@@ -48571,7 +47701,7 @@ const parseLockedFunctionType = object({
|
|
|
48571
47701
|
labels: optional(array(parseLockedLabelType)),
|
|
48572
47702
|
ariaLabel: optional(string)
|
|
48573
47703
|
});
|
|
48574
|
-
const parseLockedFigure =
|
|
47704
|
+
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;
|
|
48575
47705
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48576
47706
|
step: pairOfNumbers,
|
|
48577
47707
|
// TODO(benchristel): rather than making gridStep and snapStep
|
|
@@ -48639,7 +47769,14 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
|
|
|
48639
47769
|
}));
|
|
48640
47770
|
|
|
48641
47771
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
48642
|
-
image
|
|
47772
|
+
// The default value for image comes from measurer.tsx.
|
|
47773
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47774
|
+
// defaults here.
|
|
47775
|
+
image: defaulted(parsePerseusImageBackground, () => ({
|
|
47776
|
+
url: null,
|
|
47777
|
+
top: 0,
|
|
47778
|
+
left: 0
|
|
47779
|
+
})),
|
|
48643
47780
|
showProtractor: boolean,
|
|
48644
47781
|
showRuler: boolean,
|
|
48645
47782
|
rulerLabel: string,
|
|
@@ -48750,9 +47887,15 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
|
48750
47887
|
categories: array(string),
|
|
48751
47888
|
type: enumeration(...plotterPlotTypes),
|
|
48752
47889
|
maxY: number,
|
|
48753
|
-
scaleY
|
|
47890
|
+
// The default value for scaleY comes from plotter.tsx.
|
|
47891
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47892
|
+
// defaults here.
|
|
47893
|
+
scaleY: defaulted(number, () => 1),
|
|
48754
47894
|
labelInterval: optional(nullable(number)),
|
|
48755
|
-
snapsPerLine
|
|
47895
|
+
// The default value for snapsPerLine comes from plotter.tsx.
|
|
47896
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
47897
|
+
// defaults here.
|
|
47898
|
+
snapsPerLine: defaulted(number, () => 2),
|
|
48756
47899
|
starting: array(number),
|
|
48757
47900
|
correct: array(number),
|
|
48758
47901
|
picUrl: optional(nullable(string)),
|
|
@@ -48829,12 +47972,12 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
48829
47972
|
}
|
|
48830
47973
|
return ctx.success(widgetsMap);
|
|
48831
47974
|
};
|
|
48832
|
-
const parseWidgetsMapEntry = ([
|
|
48833
|
-
const
|
|
48834
|
-
if (isFailure(
|
|
48835
|
-
return
|
|
47975
|
+
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
47976
|
+
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
47977
|
+
if (isFailure(idComponentsResult)) {
|
|
47978
|
+
return idComponentsResult;
|
|
48836
47979
|
}
|
|
48837
|
-
const [type,
|
|
47980
|
+
const [type, n] = idComponentsResult.value;
|
|
48838
47981
|
function parseAndAssign(key, parse) {
|
|
48839
47982
|
const widgetResult = parse(widget, ctx);
|
|
48840
47983
|
if (isFailure(widgetResult)) {
|
|
@@ -48845,88 +47988,88 @@ const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
|
48845
47988
|
}
|
|
48846
47989
|
switch (type) {
|
|
48847
47990
|
case "categorizer":
|
|
48848
|
-
return parseAndAssign(`categorizer ${
|
|
47991
|
+
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
48849
47992
|
case "cs-program":
|
|
48850
|
-
return parseAndAssign(`cs-program ${
|
|
47993
|
+
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
48851
47994
|
case "definition":
|
|
48852
|
-
return parseAndAssign(`definition ${
|
|
47995
|
+
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
48853
47996
|
case "dropdown":
|
|
48854
|
-
return parseAndAssign(`dropdown ${
|
|
47997
|
+
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
48855
47998
|
case "explanation":
|
|
48856
|
-
return parseAndAssign(`explanation ${
|
|
47999
|
+
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
48857
48000
|
case "expression":
|
|
48858
|
-
return parseAndAssign(`expression ${
|
|
48001
|
+
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
48859
48002
|
case "grapher":
|
|
48860
|
-
return parseAndAssign(`grapher ${
|
|
48003
|
+
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
48861
48004
|
case "group":
|
|
48862
|
-
return parseAndAssign(`group ${
|
|
48005
|
+
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
48863
48006
|
case "graded-group":
|
|
48864
|
-
return parseAndAssign(`graded-group ${
|
|
48007
|
+
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
48865
48008
|
case "graded-group-set":
|
|
48866
|
-
return parseAndAssign(`graded-group-set ${
|
|
48009
|
+
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
48867
48010
|
case "iframe":
|
|
48868
|
-
return parseAndAssign(`iframe ${
|
|
48011
|
+
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
48869
48012
|
case "image":
|
|
48870
|
-
return parseAndAssign(`image ${
|
|
48013
|
+
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
48871
48014
|
case "input-number":
|
|
48872
|
-
return parseAndAssign(`input-number ${
|
|
48015
|
+
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
48873
48016
|
case "interaction":
|
|
48874
|
-
return parseAndAssign(`interaction ${
|
|
48017
|
+
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
48875
48018
|
case "interactive-graph":
|
|
48876
|
-
return parseAndAssign(`interactive-graph ${
|
|
48019
|
+
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
48877
48020
|
case "label-image":
|
|
48878
|
-
return parseAndAssign(`label-image ${
|
|
48021
|
+
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
48879
48022
|
case "matcher":
|
|
48880
|
-
return parseAndAssign(`matcher ${
|
|
48023
|
+
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
48881
48024
|
case "matrix":
|
|
48882
|
-
return parseAndAssign(`matrix ${
|
|
48025
|
+
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
48883
48026
|
case "measurer":
|
|
48884
|
-
return parseAndAssign(`measurer ${
|
|
48027
|
+
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
48885
48028
|
case "molecule-renderer":
|
|
48886
|
-
return parseAndAssign(`molecule-renderer ${
|
|
48029
|
+
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
48887
48030
|
case "number-line":
|
|
48888
|
-
return parseAndAssign(`number-line ${
|
|
48031
|
+
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
48889
48032
|
case "numeric-input":
|
|
48890
|
-
return parseAndAssign(`numeric-input ${
|
|
48033
|
+
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
48891
48034
|
case "orderer":
|
|
48892
|
-
return parseAndAssign(`orderer ${
|
|
48035
|
+
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
48893
48036
|
case "passage":
|
|
48894
|
-
return parseAndAssign(`passage ${
|
|
48037
|
+
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
48895
48038
|
case "passage-ref":
|
|
48896
|
-
return parseAndAssign(`passage-ref ${
|
|
48039
|
+
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
48897
48040
|
case "passage-ref-target":
|
|
48898
48041
|
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
48899
48042
|
// used in test content. See:
|
|
48900
48043
|
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
48901
|
-
return parseAndAssign(`passage-ref-target ${
|
|
48044
|
+
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
48902
48045
|
case "phet-simulation":
|
|
48903
|
-
return parseAndAssign(`phet-simulation ${
|
|
48046
|
+
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
48904
48047
|
case "plotter":
|
|
48905
|
-
return parseAndAssign(`plotter ${
|
|
48048
|
+
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
48906
48049
|
case "python-program":
|
|
48907
|
-
return parseAndAssign(`python-program ${
|
|
48050
|
+
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
48908
48051
|
case "radio":
|
|
48909
|
-
return parseAndAssign(`radio ${
|
|
48052
|
+
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
48910
48053
|
case "sorter":
|
|
48911
|
-
return parseAndAssign(`sorter ${
|
|
48054
|
+
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
48912
48055
|
case "table":
|
|
48913
|
-
return parseAndAssign(`table ${
|
|
48056
|
+
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
48914
48057
|
case "video":
|
|
48915
|
-
return parseAndAssign(`video ${
|
|
48058
|
+
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
48916
48059
|
case "sequence":
|
|
48917
48060
|
// sequence is a deprecated widget type, and the corresponding
|
|
48918
48061
|
// widget component no longer exists.
|
|
48919
|
-
return parseAndAssign(`sequence ${
|
|
48062
|
+
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
48920
48063
|
case "lights-puzzle":
|
|
48921
|
-
return parseAndAssign(`lights-puzzle ${
|
|
48064
|
+
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
48922
48065
|
case "simulator":
|
|
48923
|
-
return parseAndAssign(`simulator ${
|
|
48066
|
+
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
48924
48067
|
case "transformer":
|
|
48925
|
-
return parseAndAssign(`transformer ${
|
|
48068
|
+
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
48926
48069
|
default:
|
|
48927
48070
|
if (getWidget(type)) {
|
|
48928
48071
|
// @ts-expect-error - 'type' is not a valid widget type
|
|
48929
|
-
return parseAndAssign(`${type} ${
|
|
48072
|
+
return parseAndAssign(`${type} ${n}`, any);
|
|
48930
48073
|
}
|
|
48931
48074
|
return ctx.failure("a valid widget type", type);
|
|
48932
48075
|
}
|
|
@@ -48938,11 +48081,32 @@ const parseDeprecatedWidget = parseWidget(
|
|
|
48938
48081
|
object({}));
|
|
48939
48082
|
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48940
48083
|
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48941
|
-
return ctx.failure("
|
|
48084
|
+
return ctx.failure("a string representing a positive integer", rawValue);
|
|
48942
48085
|
}
|
|
48943
48086
|
return ctx.success(+rawValue);
|
|
48944
48087
|
};
|
|
48945
|
-
const
|
|
48088
|
+
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
48089
|
+
|
|
48090
|
+
const parsePerseusRenderer = defaulted(object({
|
|
48091
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
48092
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
48093
|
+
content: defaulted(string, () => ""),
|
|
48094
|
+
// This module has an import cycle with parseWidgetsMap, because the
|
|
48095
|
+
// `group` widget can contain another renderer.
|
|
48096
|
+
// The anonymous function below ensures that we don't try to access
|
|
48097
|
+
// parseWidgetsMap before it's defined.
|
|
48098
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
48099
|
+
metadata: optional(array(string)),
|
|
48100
|
+
images: parseImages
|
|
48101
|
+
}),
|
|
48102
|
+
// Default value
|
|
48103
|
+
() => ({
|
|
48104
|
+
content: "",
|
|
48105
|
+
widgets: {},
|
|
48106
|
+
images: {}
|
|
48107
|
+
}));
|
|
48108
|
+
|
|
48109
|
+
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
48946
48110
|
|
|
48947
48111
|
const parseHint = object({
|
|
48948
48112
|
replace: optional(boolean),
|
|
@@ -48953,7 +48117,7 @@ const parseHint = object({
|
|
|
48953
48117
|
});
|
|
48954
48118
|
|
|
48955
48119
|
const _excluded = ["type", "options"];
|
|
48956
|
-
object({
|
|
48120
|
+
const parsePerseusItem$1 = object({
|
|
48957
48121
|
question: parsePerseusRenderer,
|
|
48958
48122
|
hints: defaulted(array(parseHint), () => []),
|
|
48959
48123
|
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
@@ -48988,6 +48152,7 @@ function migrateAnswerArea(rawValue, ctx) {
|
|
|
48988
48152
|
* Why not just use JSON.parse? We want:
|
|
48989
48153
|
* - To make sure types are correct
|
|
48990
48154
|
* - To give us a central place to validate/transform output if needed
|
|
48155
|
+
* @deprecated - use parseAndMigratePerseusItem instead
|
|
48991
48156
|
* @param {string} json - the stringified PerseusItem JSON
|
|
48992
48157
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
48993
48158
|
*/
|
|
@@ -48999,6 +48164,60 @@ function parsePerseusItem(json) {
|
|
|
48999
48164
|
}
|
|
49000
48165
|
throw new Error("Something went wrong.");
|
|
49001
48166
|
}
|
|
48167
|
+
/**
|
|
48168
|
+
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
48169
|
+
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
48170
|
+
* data.
|
|
48171
|
+
*
|
|
48172
|
+
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
48173
|
+
* failure, it will contain an error message describing where in the tree
|
|
48174
|
+
* parsing failed.
|
|
48175
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48176
|
+
*/
|
|
48177
|
+
function parseAndMigratePerseusItem(json) {
|
|
48178
|
+
throwErrorIfCheatingDetected();
|
|
48179
|
+
const object = JSON.parse(json);
|
|
48180
|
+
const result = parse(object, parsePerseusItem$1);
|
|
48181
|
+
if (isFailure(result)) {
|
|
48182
|
+
return failure({
|
|
48183
|
+
message: result.detail,
|
|
48184
|
+
invalidObject: object
|
|
48185
|
+
});
|
|
48186
|
+
}
|
|
48187
|
+
return result;
|
|
48188
|
+
}
|
|
48189
|
+
|
|
48190
|
+
/**
|
|
48191
|
+
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
48192
|
+
* latest schema, and runtime-typechecks the result.
|
|
48193
|
+
*
|
|
48194
|
+
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
48195
|
+
* failure, it will contain an error message describing where in the tree
|
|
48196
|
+
* parsing failed.
|
|
48197
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
48198
|
+
*/
|
|
48199
|
+
function parseAndMigratePerseusArticle(json) {
|
|
48200
|
+
throwErrorIfCheatingDetected();
|
|
48201
|
+
const object = JSON.parse(json);
|
|
48202
|
+
const result = parse(object, parsePerseusArticle);
|
|
48203
|
+
if (isFailure(result)) {
|
|
48204
|
+
return failure({
|
|
48205
|
+
message: result.detail,
|
|
48206
|
+
invalidObject: object
|
|
48207
|
+
});
|
|
48208
|
+
}
|
|
48209
|
+
return result;
|
|
48210
|
+
}
|
|
48211
|
+
|
|
48212
|
+
/**
|
|
48213
|
+
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
48214
|
+
*/
|
|
48215
|
+
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
48216
|
+
function throwErrorIfCheatingDetected() {
|
|
48217
|
+
if (!isRealJSONParse(JSON.parse)) {
|
|
48218
|
+
throw new Error("Something went wrong.");
|
|
48219
|
+
}
|
|
48220
|
+
}
|
|
49002
48221
|
|
|
49003
48222
|
const genericPerseusItemData = {
|
|
49004
48223
|
question: {
|
|
@@ -49218,5 +48437,5 @@ const ScoringUtil = {
|
|
|
49218
48437
|
* Types
|
|
49219
48438
|
*/
|
|
49220
48439
|
|
|
49221
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget,
|
|
48440
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49222
48441
|
//# sourceMappingURL=index.js.map
|