@khanacademy/perseus 51.0.1 → 52.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +681 -2195
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +123 -15
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +36 -6
- package/dist/index.js +736 -2203
- package/dist/index.js.map +1 -1
- package/dist/item-version.d.ts +1 -1
- package/dist/renderer-util.d.ts +7 -2
- package/dist/strings.d.ts +101 -1
- package/dist/strings.js +179 -26
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +13 -12
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
- package/dist/widget-type-utils.d.ts +1 -22
- package/dist/widgets/categorizer/categorizer.d.ts +5 -5
- package/dist/widgets/cs-program/cs-program.d.ts +3 -2
- package/dist/widgets/dropdown/dropdown.d.ts +2 -1
- package/dist/widgets/expression/expression.d.ts +8 -10
- package/dist/widgets/grapher/grapher.d.ts +2 -1
- package/dist/widgets/group/group.d.ts +4 -2
- package/dist/widgets/group/score-group.d.ts +2 -2
- package/dist/widgets/group/validate-group.d.ts +4 -0
- package/dist/widgets/iframe/iframe.d.ts +3 -2
- package/dist/widgets/input-number/input-number.d.ts +2 -1
- package/dist/widgets/interactive-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/linear.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +12 -1
- package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +6 -0
- package/dist/widgets/label-image/label-image.d.ts +5 -4
- package/dist/widgets/matcher/matcher.d.ts +2 -1
- package/dist/widgets/matrix/matrix.d.ts +4 -2
- package/dist/widgets/measurer/measurer.d.ts +3 -8
- package/dist/widgets/mock-widgets/mock-widget-types.d.ts +17 -0
- package/dist/widgets/mock-widgets/mock-widget.d.ts +5 -4
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +2 -1
- package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +4 -0
- package/dist/widgets/number-line/number-line.d.ts +3 -1
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -1
- package/dist/widgets/orderer/orderer.d.ts +6 -2
- package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -4
- package/dist/widgets/plotter/plotter.d.ts +4 -2
- package/dist/widgets/radio/radio-component.d.ts +1 -1
- package/dist/widgets/radio/radio.d.ts +5 -9
- package/dist/widgets/sorter/sorter.d.ts +3 -1
- package/dist/widgets/table/table.d.ts +3 -1
- package/dist/widgets.d.ts +3 -2
- package/package.json +14 -14
- package/dist/util/is-real-json-parse.d.ts +0 -1
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +0 -9
- package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +0 -4
- package/dist/util/parse-perseus-json/general-purpose-parsers/convert.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +0 -21
- package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +0 -17
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +0 -6
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +0 -7
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +0 -8
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +0 -7
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +0 -2
- package/dist/util/parse-perseus-json/index.d.ts +0 -45
- package/dist/util/parse-perseus-json/object-path.d.ts +0 -2
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +0 -2
- package/dist/util/parse-perseus-json/parse.d.ts +0 -3
- package/dist/util/parse-perseus-json/parser-types.d.ts +0 -30
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +0 -16
- package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +0 -5
- package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +0 -36
- package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +0 -7
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +0 -3
- package/dist/util/parse-perseus-json/result.d.ts +0 -16
- package/dist/util/snowman-utils.d.ts +0 -17
- package/dist/widgets/categorizer/categorizer.util.d.ts +0 -17
package/dist/index.js
CHANGED
|
@@ -109,191 +109,6 @@ function _extends() {
|
|
|
109
109
|
* The translated strings that are used to render Perseus.
|
|
110
110
|
*/
|
|
111
111
|
|
|
112
|
-
/**
|
|
113
|
-
* Untranslated strings used in Perseus. To be used by an external
|
|
114
|
-
* translator to produce translated strings, passed in as `PerseusStrings`.
|
|
115
|
-
* !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
|
|
116
|
-
*/
|
|
117
|
-
const strings = {
|
|
118
|
-
closeKeypad: "close math keypad",
|
|
119
|
-
openKeypad: "open math keypad",
|
|
120
|
-
mathInputBox: "Math input box",
|
|
121
|
-
removeHighlight: "Remove highlight",
|
|
122
|
-
addHighlight: "Add highlight",
|
|
123
|
-
hintPos: "Hint #%(pos)s",
|
|
124
|
-
errorRendering: "Error rendering: %(error)s",
|
|
125
|
-
APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "12 pi or " + "2/3 pi",
|
|
126
|
-
EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
|
|
127
|
-
NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
|
|
128
|
-
MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
|
|
129
|
-
MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
|
|
130
|
-
WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
|
|
131
|
-
WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
|
|
132
|
-
invalidSelection: "Make sure you select something for every row.",
|
|
133
|
-
ERROR_TITLE: "Oops!",
|
|
134
|
-
ERROR_MESSAGE: "Sorry, I don't understand that!",
|
|
135
|
-
hints: "Hints",
|
|
136
|
-
getAnotherHint: "Get another hint",
|
|
137
|
-
deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
|
|
138
|
-
keepTrying: "Keep trying",
|
|
139
|
-
tryAgain: "Try again",
|
|
140
|
-
check: "Check",
|
|
141
|
-
correctExcited: "Correct!",
|
|
142
|
-
nextQuestion: "Next question",
|
|
143
|
-
skipToTitle: "Skip to %(title)s",
|
|
144
|
-
current: "Current",
|
|
145
|
-
correct: "Correct",
|
|
146
|
-
correctSelected: "Correct (selected)",
|
|
147
|
-
correctCrossedOut: "Correct (but you crossed it out)",
|
|
148
|
-
incorrect: "Incorrect",
|
|
149
|
-
incorrectSelected: "Incorrect (selected)",
|
|
150
|
-
hideExplanation: "Hide explanation",
|
|
151
|
-
explain: "Explain",
|
|
152
|
-
INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
|
|
153
|
-
DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
|
|
154
|
-
DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
|
|
155
|
-
integerExample: "an integer, like $6$",
|
|
156
|
-
properExample: "a *proper* fraction, like $1/2$ or $6/10$",
|
|
157
|
-
simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
|
|
158
|
-
improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
|
|
159
|
-
simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
|
|
160
|
-
mixedExample: "a mixed number, like $1\\\\ 3/4$",
|
|
161
|
-
decimalExample: "an *exact* decimal, like $0.75$",
|
|
162
|
-
percentExample: "a percent, like $12.34\\\\%$",
|
|
163
|
-
piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
|
|
164
|
-
yourAnswer: "**Your answer should be** ",
|
|
165
|
-
yourAnswerLabel: "Your answer:",
|
|
166
|
-
addPoints: "Click to add points",
|
|
167
|
-
addVertices: "Click to add vertices",
|
|
168
|
-
tapMultiple: "Tap each dot on the image to select all answers that apply.",
|
|
169
|
-
tapSingle: "Tap each dot on the image to select an answer.",
|
|
170
|
-
clickMultiple: "Click each dot on the image to select all answers that apply.",
|
|
171
|
-
clickSingle: "Click each dot on the image to select an answer.",
|
|
172
|
-
choices: "Choices:",
|
|
173
|
-
answers: {
|
|
174
|
-
one: "%(num)s answer",
|
|
175
|
-
other: "%(num)s answers"
|
|
176
|
-
},
|
|
177
|
-
hideAnswersToggleLabel: "Hide answer choices",
|
|
178
|
-
moves: {
|
|
179
|
-
one: "Moves: %(num)s",
|
|
180
|
-
other: "Moves: %(num)s"
|
|
181
|
-
},
|
|
182
|
-
clickTiles: "Click on the tiles to change the lights.",
|
|
183
|
-
turnOffLights: "You must turn on all of the lights to continue.",
|
|
184
|
-
fillAllCells: "Make sure you fill in all cells in the matrix.",
|
|
185
|
-
molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
|
|
186
|
-
switchDirection: "Switch direction",
|
|
187
|
-
circleOpen: "Make circle open",
|
|
188
|
-
circleFilled: "Make circle filled",
|
|
189
|
-
numDivisions: "Number of divisions:",
|
|
190
|
-
divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
|
|
191
|
-
lineRange: "lines %(lineRange)s",
|
|
192
|
-
lineNumber: "line %(lineNumber)s",
|
|
193
|
-
symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
|
|
194
|
-
symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
|
|
195
|
-
lineLabel: {
|
|
196
|
-
context: "a label next to a reading passage to denote the line number",
|
|
197
|
-
message: "Line"
|
|
198
|
-
},
|
|
199
|
-
beginningPassage: "Beginning of reading passage.",
|
|
200
|
-
beginningFootnotes: "Beginning of reading passage footnotes.",
|
|
201
|
-
endPassage: "End of reading passage.",
|
|
202
|
-
questionMarker: "[Marker for question %(number)s]",
|
|
203
|
-
circleMarker: "[Circle marker %(number)s]",
|
|
204
|
-
sentenceMarker: "[Sentence %(number)s]",
|
|
205
|
-
dragHandles: "Drag handles to make graph",
|
|
206
|
-
tapAddPoints: "Tap to add points",
|
|
207
|
-
false: "False",
|
|
208
|
-
true: "True",
|
|
209
|
-
no: "No",
|
|
210
|
-
yes: "Yes",
|
|
211
|
-
chooseCorrectNum: "Please choose the correct number of answers.",
|
|
212
|
-
notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
|
|
213
|
-
noneOfTheAbove: "None of the above",
|
|
214
|
-
chooseNumAnswers: "Choose %(numCorrect)s answers:",
|
|
215
|
-
chooseAllAnswers: "Choose all answers that apply:",
|
|
216
|
-
chooseOneAnswer: "Choose 1 answer:",
|
|
217
|
-
choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
|
|
218
|
-
choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
|
|
219
|
-
choiceCorrect: "(Choice %(letter)s, Correct)",
|
|
220
|
-
choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
|
|
221
|
-
choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
|
|
222
|
-
choiceIncorrect: "(Choice %(letter)s, Incorrect)",
|
|
223
|
-
choiceChecked: "(Choice %(letter)s, Checked)",
|
|
224
|
-
choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
|
|
225
|
-
choice: "(Choice %(letter)s)",
|
|
226
|
-
crossOut: "Cross out",
|
|
227
|
-
crossOutOption: "Cross out option",
|
|
228
|
-
crossOutChoice: "Cross out Choice %(letter)s",
|
|
229
|
-
bringBack: "Bring back",
|
|
230
|
-
openMenuForChoice: "Open menu for Choice %(letter)s",
|
|
231
|
-
letters: {
|
|
232
|
-
context: "This is a list of single-character labels that will appear in front of multiple-choice options. For instance, a multiple-choice question with three options would display (A) first option (B) second option (C) third option. There must be spaces between each of the different characters. The characters will show up next to options in the order that they are listed here. Most multiple choice questions have 5 or fewer options.",
|
|
233
|
-
message: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
|
|
234
|
-
},
|
|
235
|
-
rightArrow: "Reaction arrow pointing to the right.",
|
|
236
|
-
dontUnderstandUnits: "I couldn't understand those units.",
|
|
237
|
-
checkSigFigs: "Check your significant figures.",
|
|
238
|
-
answerNumericallyIncorrect: "That answer is numerically incorrect.",
|
|
239
|
-
checkUnits: "Check your units.",
|
|
240
|
-
dontUnderstand: "I don't understand that",
|
|
241
|
-
loading: "Loading...",
|
|
242
|
-
videoTranscript: "See video transcript",
|
|
243
|
-
somethingWrong: "Something went wrong.",
|
|
244
|
-
videoWrapper: "Khan Academy video wrapper",
|
|
245
|
-
mathInputTitle: "mathematics keyboard",
|
|
246
|
-
mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
|
|
247
|
-
sin: "sin",
|
|
248
|
-
cos: "cos",
|
|
249
|
-
tan: "tan",
|
|
250
|
-
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
251
|
-
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
252
|
-
selectAnAnswer: "Select an answer",
|
|
253
|
-
// The following strings are used for interactive graph SR descriptions.
|
|
254
|
-
addPoint: "Add Point",
|
|
255
|
-
removePoint: "Remove Point",
|
|
256
|
-
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
257
|
-
closePolygon: "Close shape",
|
|
258
|
-
openPolygon: "Re-open shape",
|
|
259
|
-
srInteractiveElements: "Interactive elements: %(elements)s",
|
|
260
|
-
srNoInteractiveElements: "No interactive elements",
|
|
261
|
-
// TODO(LEMS-2660): The following strings are ones that will need
|
|
262
|
-
// translation tickets after all interactive graph SR strings have
|
|
263
|
-
// been finalized. Remove this comment after the tickets have been
|
|
264
|
-
// created.
|
|
265
|
-
srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
|
|
266
|
-
srCircleGraph: "A circle on a coordinate plane.",
|
|
267
|
-
srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
|
|
268
|
-
srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
|
|
269
|
-
srCircleRadius: "Circle radius is %(radius)s.",
|
|
270
|
-
srCircleOuterPoints: "Points on the circle at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, %(point3X)s comma %(point3Y)s, %(point4X)s comma %(point4Y)s.",
|
|
271
|
-
srLinearGraph: "A line on a coordinate plane.",
|
|
272
|
-
srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
273
|
-
srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
|
|
274
|
-
srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
|
|
275
|
-
srLinearGraphSlopeHorizontal: "Its slope is zero.",
|
|
276
|
-
srLinearGraphSlopeVertical: "Its slope is undefined.",
|
|
277
|
-
srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
|
|
278
|
-
srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
|
|
279
|
-
srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
|
|
280
|
-
srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
|
|
281
|
-
srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
282
|
-
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
283
|
-
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
284
|
-
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
285
|
-
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
|
|
286
|
-
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
287
|
-
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
288
|
-
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
289
|
-
srRayGraph: "A ray on a coordinate plane.",
|
|
290
|
-
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
291
|
-
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
292
|
-
srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
|
|
293
|
-
srRayTerminalPoint: "Through point at %(x)s comma %(y)s."
|
|
294
|
-
// The above strings are used for interactive graph SR descriptions.
|
|
295
|
-
};
|
|
296
|
-
|
|
297
112
|
/**
|
|
298
113
|
* Mock strings for the Perseus package, to be used for tests and Storybook.
|
|
299
114
|
*/
|
|
@@ -680,82 +495,213 @@ const mockStrings = {
|
|
|
680
495
|
} = _ref40;
|
|
681
496
|
return `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`;
|
|
682
497
|
},
|
|
498
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
499
|
+
srMultipleSegmentGraphAriaLabel: _ref41 => {
|
|
500
|
+
let {
|
|
501
|
+
countOfSegments
|
|
502
|
+
} = _ref41;
|
|
503
|
+
return `${countOfSegments} segments on a coordinate plane.`;
|
|
504
|
+
},
|
|
505
|
+
srMultipleSegmentIndividualLabel: _ref42 => {
|
|
506
|
+
let {
|
|
507
|
+
point1X,
|
|
508
|
+
point1Y,
|
|
509
|
+
point2X,
|
|
510
|
+
point2Y,
|
|
511
|
+
indexOfSegment
|
|
512
|
+
} = _ref42;
|
|
513
|
+
return `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
|
|
514
|
+
},
|
|
515
|
+
srSingleSegmentLabel: _ref43 => {
|
|
516
|
+
let {
|
|
517
|
+
point1X,
|
|
518
|
+
point1Y,
|
|
519
|
+
point2X,
|
|
520
|
+
point2Y
|
|
521
|
+
} = _ref43;
|
|
522
|
+
return `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
|
|
523
|
+
},
|
|
524
|
+
srSegmentLength: _ref44 => {
|
|
525
|
+
let {
|
|
526
|
+
length
|
|
527
|
+
} = _ref44;
|
|
528
|
+
return `Segment length ${length} units.`;
|
|
529
|
+
},
|
|
530
|
+
srSingleSegmentGraphEndpointAriaLabel: _ref45 => {
|
|
531
|
+
let {
|
|
532
|
+
endpointNumber,
|
|
533
|
+
x,
|
|
534
|
+
y
|
|
535
|
+
} = _ref45;
|
|
536
|
+
return `Endpoint ${endpointNumber} at ${x} comma ${y}.`;
|
|
537
|
+
},
|
|
538
|
+
srMultipleSegmentGraphEndpointAriaLabel: _ref46 => {
|
|
539
|
+
let {
|
|
540
|
+
endpointNumber,
|
|
541
|
+
x,
|
|
542
|
+
y,
|
|
543
|
+
indexOfSegment
|
|
544
|
+
} = _ref46;
|
|
545
|
+
return `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`;
|
|
546
|
+
},
|
|
547
|
+
srSegmentGrabHandle: _ref47 => {
|
|
548
|
+
let {
|
|
549
|
+
point1X,
|
|
550
|
+
point1Y,
|
|
551
|
+
point2X,
|
|
552
|
+
point2Y
|
|
553
|
+
} = _ref47;
|
|
554
|
+
return `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
|
|
555
|
+
},
|
|
683
556
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
684
|
-
srLinearSystemPoints:
|
|
557
|
+
srLinearSystemPoints: _ref48 => {
|
|
685
558
|
let {
|
|
686
559
|
lineNumber,
|
|
687
560
|
point1X,
|
|
688
561
|
point1Y,
|
|
689
562
|
point2X,
|
|
690
563
|
point2Y
|
|
691
|
-
} =
|
|
564
|
+
} = _ref48;
|
|
692
565
|
return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
|
|
693
566
|
},
|
|
694
|
-
srLinearSystemPoint:
|
|
567
|
+
srLinearSystemPoint: _ref49 => {
|
|
695
568
|
let {
|
|
696
569
|
lineNumber,
|
|
697
570
|
pointSequence,
|
|
698
571
|
x,
|
|
699
572
|
y
|
|
700
|
-
} =
|
|
573
|
+
} = _ref49;
|
|
701
574
|
return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
|
|
702
575
|
},
|
|
703
576
|
srRayGraph: "A ray on a coordinate plane.",
|
|
704
|
-
srRayPoints:
|
|
577
|
+
srRayPoints: _ref50 => {
|
|
705
578
|
let {
|
|
706
579
|
point1X,
|
|
707
580
|
point1Y,
|
|
708
581
|
point2X,
|
|
709
582
|
point2Y
|
|
710
|
-
} =
|
|
583
|
+
} = _ref50;
|
|
711
584
|
return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
|
|
712
585
|
},
|
|
713
|
-
srRayGrabHandle:
|
|
586
|
+
srRayGrabHandle: _ref51 => {
|
|
714
587
|
let {
|
|
715
588
|
point1X,
|
|
716
589
|
point1Y,
|
|
717
590
|
point2X,
|
|
718
591
|
point2Y
|
|
719
|
-
} =
|
|
592
|
+
} = _ref51;
|
|
720
593
|
return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
|
|
721
594
|
},
|
|
722
|
-
srRayEndpoint:
|
|
595
|
+
srRayEndpoint: _ref52 => {
|
|
723
596
|
let {
|
|
724
597
|
x,
|
|
725
598
|
y
|
|
726
|
-
} =
|
|
599
|
+
} = _ref52;
|
|
727
600
|
return `Endpoint at ${x} comma ${y}.`;
|
|
728
601
|
},
|
|
729
|
-
srRayTerminalPoint:
|
|
602
|
+
srRayTerminalPoint: _ref53 => {
|
|
730
603
|
let {
|
|
731
604
|
x,
|
|
732
605
|
y
|
|
733
|
-
} =
|
|
606
|
+
} = _ref53;
|
|
734
607
|
return `Through point at ${x} comma ${y}.`;
|
|
608
|
+
},
|
|
609
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
610
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
611
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
612
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
613
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
614
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
615
|
+
srQuadraticGraphVertexQuadrant: _ref54 => {
|
|
616
|
+
let {
|
|
617
|
+
quadrant
|
|
618
|
+
} = _ref54;
|
|
619
|
+
return `Vertex is in quadrant ${quadrant}.`;
|
|
620
|
+
},
|
|
621
|
+
srQuadraticTwoXIntercepts: _ref55 => {
|
|
622
|
+
let {
|
|
623
|
+
intercept1,
|
|
624
|
+
intercept2
|
|
625
|
+
} = _ref55;
|
|
626
|
+
return `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`;
|
|
627
|
+
},
|
|
628
|
+
srQuadraticOneXIntercept: _ref56 => {
|
|
629
|
+
let {
|
|
630
|
+
intercept
|
|
631
|
+
} = _ref56;
|
|
632
|
+
return `The X-intercept is at ${intercept} comma 0.`;
|
|
633
|
+
},
|
|
634
|
+
srQuadraticYIntercept: _ref57 => {
|
|
635
|
+
let {
|
|
636
|
+
intercept
|
|
637
|
+
} = _ref57;
|
|
638
|
+
return `The Y-intercept is at 0 comma ${intercept}.`;
|
|
639
|
+
},
|
|
640
|
+
srQuadraticPointOrigin: _ref58 => {
|
|
641
|
+
let {
|
|
642
|
+
pointNumber
|
|
643
|
+
} = _ref58;
|
|
644
|
+
return `Point ${pointNumber} on parabola at the origin.`;
|
|
645
|
+
},
|
|
646
|
+
srQuadraticPointAxis: _ref59 => {
|
|
647
|
+
let {
|
|
648
|
+
pointNumber,
|
|
649
|
+
x,
|
|
650
|
+
y
|
|
651
|
+
} = _ref59;
|
|
652
|
+
return `Point ${pointNumber} on parabola at ${x} comma ${y}.`;
|
|
653
|
+
},
|
|
654
|
+
srQuadraticPointQuadrant: _ref60 => {
|
|
655
|
+
let {
|
|
656
|
+
pointNumber,
|
|
657
|
+
x,
|
|
658
|
+
y,
|
|
659
|
+
quadrant
|
|
660
|
+
} = _ref60;
|
|
661
|
+
return `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`;
|
|
662
|
+
},
|
|
663
|
+
srQuadraticInteractiveElements: _ref61 => {
|
|
664
|
+
let {
|
|
665
|
+
point1X,
|
|
666
|
+
point1Y,
|
|
667
|
+
point2X,
|
|
668
|
+
point2Y,
|
|
669
|
+
point3X,
|
|
670
|
+
point3Y
|
|
671
|
+
} = _ref61;
|
|
672
|
+
return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
|
|
735
673
|
}
|
|
736
674
|
// The above strings are used for interactive graph SR descriptions.
|
|
737
675
|
};
|
|
738
676
|
|
|
739
677
|
// This type helps us make sure all error codes are mapped to strings
|
|
740
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Map an error string to a PerseusStrings key
|
|
681
|
+
* that we can use to get the translated error message
|
|
682
|
+
*/
|
|
741
683
|
const errorToString = {
|
|
742
|
-
MISSING_PERCENT_ERROR:
|
|
743
|
-
NEEDS_TO_BE_SIMPLIFIED_ERROR:
|
|
744
|
-
APPROXIMATED_PI_ERROR:
|
|
745
|
-
EXTRA_SYMBOLS_ERROR:
|
|
746
|
-
WRONG_CASE_ERROR:
|
|
747
|
-
WRONG_LETTER_ERROR:
|
|
748
|
-
MULTIPLICATION_SIGN_ERROR:
|
|
749
|
-
INVALID_SELECTION_ERROR:
|
|
750
|
-
CHOOSE_CORRECT_NUM_ERROR:
|
|
751
|
-
NOT_NONE_ABOVE_ERROR:
|
|
752
|
-
FILL_ALL_CELLS_ERROR:
|
|
753
|
-
};
|
|
754
|
-
function mapErrorToString(
|
|
684
|
+
MISSING_PERCENT_ERROR: "MISSING_PERCENT_ERROR",
|
|
685
|
+
NEEDS_TO_BE_SIMPLIFIED_ERROR: "NEEDS_TO_BE_SIMPLFIED_ERROR",
|
|
686
|
+
APPROXIMATED_PI_ERROR: "APPROXIMATED_PI_ERROR",
|
|
687
|
+
EXTRA_SYMBOLS_ERROR: "EXTRA_SYMBOLS_ERROR",
|
|
688
|
+
WRONG_CASE_ERROR: "WRONG_CASE_ERROR",
|
|
689
|
+
WRONG_LETTER_ERROR: "WRONG_LETTER_ERROR",
|
|
690
|
+
MULTIPLICATION_SIGN_ERROR: "MULTIPLICATION_SIGN_ERROR",
|
|
691
|
+
INVALID_SELECTION_ERROR: "invalidSelection",
|
|
692
|
+
CHOOSE_CORRECT_NUM_ERROR: "chooseCorrectNum",
|
|
693
|
+
NOT_NONE_ABOVE_ERROR: "notNoneOfTheAbove",
|
|
694
|
+
FILL_ALL_CELLS_ERROR: "fillAllCells"
|
|
695
|
+
};
|
|
696
|
+
function mapErrorToString(
|
|
697
|
+
// the string representing an error code
|
|
698
|
+
err,
|
|
699
|
+
// the translated Perseus strings
|
|
700
|
+
translatedStrings) {
|
|
755
701
|
if (!err) {
|
|
756
702
|
return err;
|
|
757
703
|
}
|
|
758
|
-
return errorToString[err] || err;
|
|
704
|
+
return translatedStrings[errorToString[err]] || err;
|
|
759
705
|
}
|
|
760
706
|
|
|
761
707
|
/**
|
|
@@ -2006,23 +1952,6 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
2006
1952
|
times: widgetOptions.times
|
|
2007
1953
|
};
|
|
2008
1954
|
};
|
|
2009
|
-
const propUpgrades$2 = {
|
|
2010
|
-
/* c8 ignore next */
|
|
2011
|
-
"1": v0props => ({
|
|
2012
|
-
times: v0props.times,
|
|
2013
|
-
buttonSets: v0props.buttonSets,
|
|
2014
|
-
functions: v0props.functions,
|
|
2015
|
-
buttonsVisible: v0props.buttonsVisible,
|
|
2016
|
-
visibleLabel: v0props.visibleLabel,
|
|
2017
|
-
ariaLabel: v0props.ariaLabel,
|
|
2018
|
-
answerForms: [{
|
|
2019
|
-
considered: "correct",
|
|
2020
|
-
form: v0props.form,
|
|
2021
|
-
simplify: v0props.simplify,
|
|
2022
|
-
value: v0props.value
|
|
2023
|
-
}]
|
|
2024
|
-
})
|
|
2025
|
-
};
|
|
2026
1955
|
const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
2027
1956
|
const deps = useDependencies();
|
|
2028
1957
|
return /*#__PURE__*/React__namespace.createElement(Expression, _extends({
|
|
@@ -2062,17 +1991,18 @@ var Expression$1 = {
|
|
|
2062
1991
|
ariaLabel
|
|
2063
1992
|
};
|
|
2064
1993
|
},
|
|
2065
|
-
version:
|
|
2066
|
-
|
|
2067
|
-
minor: 0
|
|
2068
|
-
},
|
|
2069
|
-
propUpgrades: propUpgrades$2,
|
|
1994
|
+
version: perseusCore.expressionLogic.version,
|
|
1995
|
+
propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
|
|
2070
1996
|
// For use by the editor
|
|
2071
1997
|
isLintable: true,
|
|
2072
1998
|
// TODO(LEMS-2656): remove TS suppression
|
|
2073
1999
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2074
2000
|
scorer: perseusScore.scoreExpression,
|
|
2075
2001
|
// TODO(LEMS-2656): remove TS suppression
|
|
2002
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2003
|
+
validator: perseusScore.validateExpression,
|
|
2004
|
+
getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
|
|
2005
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2076
2006
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2077
2007
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2078
2008
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
@@ -17580,7 +17510,7 @@ const inlineNodeTypes = {
|
|
|
17580
17510
|
|
|
17581
17511
|
// @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'.
|
|
17582
17512
|
const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
|
|
17583
|
-
const parse$
|
|
17513
|
+
const parse$3 = (source, state) => {
|
|
17584
17514
|
const paragraphedSource = source + "\n\n";
|
|
17585
17515
|
return builtParser$1(paragraphedSource, {
|
|
17586
17516
|
...state,
|
|
@@ -17665,14 +17595,14 @@ const getContent = ast => {
|
|
|
17665
17595
|
*/
|
|
17666
17596
|
const characterCount = source => {
|
|
17667
17597
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
17668
|
-
const ast = parse$
|
|
17598
|
+
const ast = parse$3(source);
|
|
17669
17599
|
const content = getContent(ast).join("");
|
|
17670
17600
|
return content.length;
|
|
17671
17601
|
};
|
|
17672
17602
|
var PerseusMarkdown = {
|
|
17673
17603
|
characterCount: characterCount,
|
|
17674
17604
|
traverseContent: traverseContent,
|
|
17675
|
-
parse: parse$
|
|
17605
|
+
parse: parse$3,
|
|
17676
17606
|
parseInline: inlineParser,
|
|
17677
17607
|
reactFor: SimpleMarkdown__default["default"].reactFor,
|
|
17678
17608
|
ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
|
|
@@ -17813,136 +17743,6 @@ guess, state) {
|
|
|
17813
17743
|
});
|
|
17814
17744
|
}
|
|
17815
17745
|
|
|
17816
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
17817
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
17818
|
-
|
|
17819
|
-
/**
|
|
17820
|
-
* Regex for widget placeholders in a string.
|
|
17821
|
-
*
|
|
17822
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
17823
|
-
* Second capture group is the widget type (ex. "radio)
|
|
17824
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
17825
|
-
*/
|
|
17826
|
-
function getWidgetRegex() {
|
|
17827
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
17828
|
-
}
|
|
17829
|
-
|
|
17830
|
-
/**
|
|
17831
|
-
* Add a widget placeholder using the widget ID.
|
|
17832
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
17833
|
-
*
|
|
17834
|
-
* @param {string} id
|
|
17835
|
-
* @returns {string}
|
|
17836
|
-
*/
|
|
17837
|
-
function addWidget(id) {
|
|
17838
|
-
return `[[☃ ${id}]]`;
|
|
17839
|
-
}
|
|
17840
|
-
|
|
17841
|
-
/**
|
|
17842
|
-
* Get a widget type by a widget's ID
|
|
17843
|
-
* (please don't derive type from ID)
|
|
17844
|
-
*
|
|
17845
|
-
* @param {string} widgetId the ID of the widget
|
|
17846
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17847
|
-
* @returns {string} the widget type (ie "radio")
|
|
17848
|
-
*/
|
|
17849
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
17850
|
-
const widget = widgetMap[WidgetId];
|
|
17851
|
-
return widget?.type ?? null;
|
|
17852
|
-
}
|
|
17853
|
-
|
|
17854
|
-
/**
|
|
17855
|
-
* Does the content have a specific type of widget?
|
|
17856
|
-
*
|
|
17857
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
17858
|
-
* @param {string} content the string to search through
|
|
17859
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17860
|
-
* @returns {boolean} if the content includes the widget type
|
|
17861
|
-
*/
|
|
17862
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
17863
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
17864
|
-
}
|
|
17865
|
-
|
|
17866
|
-
/**
|
|
17867
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
17868
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
17869
|
-
*
|
|
17870
|
-
* Content should contain Perseus widget placeholders,
|
|
17871
|
-
* which look like: '[[☃ radio 1]]'.
|
|
17872
|
-
*
|
|
17873
|
-
* @param {string} content
|
|
17874
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
17875
|
-
*/
|
|
17876
|
-
function getWidgetIdsFromContent(content) {
|
|
17877
|
-
const widgets = [];
|
|
17878
|
-
const localWidgetRegex = getWidgetRegex();
|
|
17879
|
-
let match = localWidgetRegex.exec(content);
|
|
17880
|
-
while (match !== null) {
|
|
17881
|
-
widgets.push(match[1]);
|
|
17882
|
-
match = localWidgetRegex.exec(content);
|
|
17883
|
-
}
|
|
17884
|
-
return widgets;
|
|
17885
|
-
}
|
|
17886
|
-
|
|
17887
|
-
/**
|
|
17888
|
-
* Get a list of widget IDs from content,
|
|
17889
|
-
* but only for specific widget types
|
|
17890
|
-
*
|
|
17891
|
-
* @param {string} type the type of widget (ie "radio")
|
|
17892
|
-
* @param {string} content the string to parse
|
|
17893
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17894
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
17895
|
-
*/
|
|
17896
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
17897
|
-
const rv = [];
|
|
17898
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
17899
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
17900
|
-
const widget = widgetMap[widgetId];
|
|
17901
|
-
if (widget?.type === type) {
|
|
17902
|
-
rv.push(widgetId);
|
|
17903
|
-
}
|
|
17904
|
-
});
|
|
17905
|
-
return rv;
|
|
17906
|
-
}
|
|
17907
|
-
|
|
17908
|
-
/**
|
|
17909
|
-
* Pull the widget map out of ItemData
|
|
17910
|
-
*
|
|
17911
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
17912
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
17913
|
-
*/
|
|
17914
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
17915
|
-
return itemData.question.widgets;
|
|
17916
|
-
}
|
|
17917
|
-
|
|
17918
|
-
/**
|
|
17919
|
-
* Get the widget information from a WidgetMap
|
|
17920
|
-
*
|
|
17921
|
-
* @param {string} widgetId the ID of the widget
|
|
17922
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
17923
|
-
*/
|
|
17924
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
17925
|
-
return widgetMap[widgetId] ?? null;
|
|
17926
|
-
}
|
|
17927
|
-
|
|
17928
|
-
/**
|
|
17929
|
-
* Get select widgets from a widget map.
|
|
17930
|
-
*
|
|
17931
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
17932
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
17933
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
17934
|
-
*/
|
|
17935
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
17936
|
-
const widgets = {};
|
|
17937
|
-
widgetIds.forEach(widgetId => {
|
|
17938
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
17939
|
-
if (widget) {
|
|
17940
|
-
widgets[widgetId] = widget;
|
|
17941
|
-
}
|
|
17942
|
-
});
|
|
17943
|
-
return widgets;
|
|
17944
|
-
}
|
|
17945
|
-
|
|
17946
17746
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17947
17747
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17948
17748
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -18044,6 +17844,9 @@ const getWidget = name => {
|
|
|
18044
17844
|
const getWidgetExport = name => {
|
|
18045
17845
|
return widgets[name] ?? null;
|
|
18046
17846
|
};
|
|
17847
|
+
const getWidgetValidator = name => {
|
|
17848
|
+
return widgets[name]?.validator ?? null;
|
|
17849
|
+
};
|
|
18047
17850
|
const getWidgetScorer = name => {
|
|
18048
17851
|
return widgets[name]?.scorer ?? null;
|
|
18049
17852
|
};
|
|
@@ -18356,6 +18159,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18356
18159
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
18357
18160
|
getWidget: getWidget,
|
|
18358
18161
|
getWidgetExport: getWidgetExport,
|
|
18162
|
+
getWidgetValidator: getWidgetValidator,
|
|
18359
18163
|
getWidgetScorer: getWidgetScorer,
|
|
18360
18164
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18361
18165
|
getEditor: getEditor,
|
|
@@ -18378,7 +18182,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18378
18182
|
});
|
|
18379
18183
|
|
|
18380
18184
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
18381
|
-
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
18382
18185
|
return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18383
18186
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
18384
18187
|
const newValues = {};
|
|
@@ -18396,22 +18199,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
|
18396
18199
|
...newValues
|
|
18397
18200
|
};
|
|
18398
18201
|
}
|
|
18202
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
18399
18203
|
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
18400
18204
|
});
|
|
18401
18205
|
}
|
|
18206
|
+
|
|
18207
|
+
/**
|
|
18208
|
+
* Checks the given user input to see if any answerable widgets have not been
|
|
18209
|
+
* "filled in" (ie. if they're empty). Another way to think about this
|
|
18210
|
+
* function is that its a check to see if we can score the provided input.
|
|
18211
|
+
*/
|
|
18402
18212
|
function emptyWidgetsFunctional(widgets,
|
|
18403
18213
|
// This is a port of old code, I'm not sure why
|
|
18404
18214
|
// we need widgetIds vs the keys of the widgets object
|
|
18405
18215
|
widgetIds, userInputMap, strings, locale) {
|
|
18406
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
18407
18216
|
return widgetIds.filter(id => {
|
|
18408
|
-
const widget =
|
|
18409
|
-
if (!widget || widget.static) {
|
|
18217
|
+
const widget = widgets[id];
|
|
18218
|
+
if (!widget || widget.static === true) {
|
|
18410
18219
|
// Static widgets shouldn't count as empty
|
|
18411
18220
|
return false;
|
|
18412
18221
|
}
|
|
18413
|
-
const
|
|
18414
|
-
const
|
|
18222
|
+
const validator = getWidgetValidator(widget.type);
|
|
18223
|
+
const userInput = userInputMap[id];
|
|
18224
|
+
const validationData = widget.options;
|
|
18225
|
+
const score = validator?.(userInput, validationData, strings, locale);
|
|
18415
18226
|
if (score) {
|
|
18416
18227
|
return scoreIsEmpty(score);
|
|
18417
18228
|
}
|
|
@@ -18427,7 +18238,7 @@ strings, locale) {
|
|
|
18427
18238
|
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
18428
18239
|
// widget data for widgets that are not in PerseusRenderer.content,
|
|
18429
18240
|
// so this checks that the widgets are being used before scoring them
|
|
18430
|
-
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
18241
|
+
const usedWidgetIds = perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
|
|
18431
18242
|
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
18432
18243
|
return flattenScores(scores);
|
|
18433
18244
|
}
|
|
@@ -18450,8 +18261,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
18450
18261
|
return;
|
|
18451
18262
|
}
|
|
18452
18263
|
const userInput = userInputMap[id];
|
|
18264
|
+
const validator = getWidgetValidator(widget.type);
|
|
18453
18265
|
const scorer = getWidgetScorer(widget.type);
|
|
18454
|
-
|
|
18266
|
+
|
|
18267
|
+
// We do validation (empty checks) first and then scoring. If
|
|
18268
|
+
// validation fails, it's result is itself a PerseusScore.
|
|
18269
|
+
const score = validator?.(userInput, widget.options, strings, locale) ?? scorer?.(userInput, widget.options, strings, locale);
|
|
18455
18270
|
if (score != null) {
|
|
18456
18271
|
widgetScores[id] = score;
|
|
18457
18272
|
}
|
|
@@ -19060,7 +18875,7 @@ class Renderer extends React__namespace.Component {
|
|
|
19060
18875
|
const apiOptions = this.getApiOptions();
|
|
19061
18876
|
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19062
18877
|
|
|
19063
|
-
// The widget needs access to its "
|
|
18878
|
+
// The widget needs access to its "scoring data" at all times when in review
|
|
19064
18879
|
// mode (which is really just part of its widget info).
|
|
19065
18880
|
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19066
18881
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
@@ -23020,7 +22835,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
23020
22835
|
// TODO(LEMS-2656): remove TS suppression
|
|
23021
22836
|
// @ts-expect-error: State is not assignable to ParseState
|
|
23022
22837
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
23023
|
-
const parse$
|
|
22838
|
+
const parse$2 = (source, state) => {
|
|
23024
22839
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
23025
22840
|
state = state || {};
|
|
23026
22841
|
const paragraphedSource = source + "\n\n";
|
|
@@ -23030,7 +22845,7 @@ const parse$3 = (source, state) => {
|
|
|
23030
22845
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
23031
22846
|
const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
|
|
23032
22847
|
var PassageMarkdown = {
|
|
23033
|
-
parse: parse$
|
|
22848
|
+
parse: parse$2,
|
|
23034
22849
|
output,
|
|
23035
22850
|
START_REF_PREFIX,
|
|
23036
22851
|
END_REF_PREFIX,
|
|
@@ -23585,10 +23400,7 @@ var PassageRef$1 = {
|
|
|
23585
23400
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23586
23401
|
summaryText: widgetOptions.summaryText
|
|
23587
23402
|
}),
|
|
23588
|
-
version:
|
|
23589
|
-
major: 0,
|
|
23590
|
-
minor: 1
|
|
23591
|
-
},
|
|
23403
|
+
version: perseusCore.passageRefLogic.version,
|
|
23592
23404
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23593
23405
|
scorer: () => scoreNoop()
|
|
23594
23406
|
};
|
|
@@ -25274,7 +25086,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25274
25086
|
// Current versions of the radio widget always pass in the
|
|
25275
25087
|
// "correct" value through the choices. Old serialized state
|
|
25276
25088
|
// for radio widgets doesn't have this though, so we have to
|
|
25277
|
-
// pull the correctness out of the review mode
|
|
25089
|
+
// pull the correctness out of the review mode scoring data. This
|
|
25278
25090
|
// only works because all of the places we use
|
|
25279
25091
|
// `restoreSerializedState()` also turn on reviewMode, but is
|
|
25280
25092
|
// fine for now.
|
|
@@ -25398,21 +25210,6 @@ const transform = (widgetOptions, strings, problemNum) => {
|
|
|
25398
25210
|
selectedChoices: ___default["default"].pluck(choices, "correct")
|
|
25399
25211
|
};
|
|
25400
25212
|
};
|
|
25401
|
-
const propUpgrades$1 = {
|
|
25402
|
-
"1": v0props => {
|
|
25403
|
-
const {
|
|
25404
|
-
noneOfTheAbove,
|
|
25405
|
-
...rest
|
|
25406
|
-
} = v0props;
|
|
25407
|
-
if (noneOfTheAbove) {
|
|
25408
|
-
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
25409
|
-
}
|
|
25410
|
-
return {
|
|
25411
|
-
...rest,
|
|
25412
|
-
hasNoneOfTheAbove: false
|
|
25413
|
-
};
|
|
25414
|
-
}
|
|
25415
|
-
};
|
|
25416
25213
|
var Radio = {
|
|
25417
25214
|
name: "radio",
|
|
25418
25215
|
displayName: "Radio / Multiple choice",
|
|
@@ -25420,15 +25217,15 @@ var Radio = {
|
|
|
25420
25217
|
widget: Radio$1,
|
|
25421
25218
|
transform: transform,
|
|
25422
25219
|
staticTransform: transform,
|
|
25423
|
-
version:
|
|
25424
|
-
|
|
25425
|
-
minor: 0
|
|
25426
|
-
},
|
|
25427
|
-
propUpgrades: propUpgrades$1,
|
|
25220
|
+
version: perseusCore.radioLogic.version,
|
|
25221
|
+
propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
|
|
25428
25222
|
isLintable: true,
|
|
25429
25223
|
// TODO(LEMS-2656): remove TS suppression
|
|
25430
25224
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25431
|
-
scorer: perseusScore.scoreRadio
|
|
25225
|
+
scorer: perseusScore.scoreRadio,
|
|
25226
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25227
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25228
|
+
validator: perseusScore.validateRadio
|
|
25432
25229
|
};
|
|
25433
25230
|
|
|
25434
25231
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -25447,24 +25244,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
25447
25244
|
};
|
|
25448
25245
|
};
|
|
25449
25246
|
|
|
25450
|
-
/**
|
|
25451
|
-
* For details on the individual options, see the
|
|
25452
|
-
* PerseusCategorizerWidgetOptions type
|
|
25453
|
-
*/
|
|
25454
|
-
|
|
25455
|
-
/**
|
|
25456
|
-
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
25457
|
-
* the public options that should be exposed to the client.
|
|
25458
|
-
*/
|
|
25459
|
-
function getCategorizerPublicWidgetOptions(options) {
|
|
25460
|
-
return {
|
|
25461
|
-
items: options.items,
|
|
25462
|
-
categories: options.categories,
|
|
25463
|
-
randomizeItems: options.randomizeItems,
|
|
25464
|
-
static: options.static
|
|
25465
|
-
};
|
|
25466
|
-
}
|
|
25467
|
-
|
|
25468
25247
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
25469
25248
|
class Categorizer extends React__namespace.Component {
|
|
25470
25249
|
static contextType = PerseusI18nContext;
|
|
@@ -25647,7 +25426,10 @@ var Categorizer$1 = {
|
|
|
25647
25426
|
// TODO(LEMS-2656): remove TS suppression
|
|
25648
25427
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25649
25428
|
scorer: perseusScore.scoreCategorizer,
|
|
25650
|
-
|
|
25429
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25430
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25431
|
+
validator: perseusScore.validateCategorizer,
|
|
25432
|
+
getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
|
|
25651
25433
|
};
|
|
25652
25434
|
|
|
25653
25435
|
/**
|
|
@@ -26086,7 +25868,10 @@ var Dropdown$1 = {
|
|
|
26086
25868
|
transform: optionsTransform,
|
|
26087
25869
|
// TODO(LEMS-2656): remove TS suppression
|
|
26088
25870
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
26089
|
-
scorer: perseusScore.scoreDropdown
|
|
25871
|
+
scorer: perseusScore.scoreDropdown,
|
|
25872
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25873
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25874
|
+
validator: perseusScore.validateDropdown
|
|
26090
25875
|
};
|
|
26091
25876
|
|
|
26092
25877
|
const getPromptJSON$j = renderProps => {
|
|
@@ -26458,7 +26243,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
26458
26243
|
DEFAULT_INVALID_MESSAGE_2
|
|
26459
26244
|
} = this.context.strings;
|
|
26460
26245
|
const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
|
|
26461
|
-
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}`;
|
|
26246
|
+
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${mapErrorToString(score.message, this.context.strings)}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
26462
26247
|
this.setState({
|
|
26463
26248
|
status: status,
|
|
26464
26249
|
message: message,
|
|
@@ -27604,11 +27389,22 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27604
27389
|
|
|
27605
27390
|
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27606
27391
|
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27607
|
-
function scoreGroup(userInput,
|
|
27608
|
-
const scores = scoreWidgetsFunctional(
|
|
27392
|
+
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27393
|
+
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27609
27394
|
return flattenScores(scores);
|
|
27610
27395
|
}
|
|
27611
27396
|
|
|
27397
|
+
function validateGroup(userInput, validationData, strings, locale) {
|
|
27398
|
+
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27399
|
+
if (emptyWidgets.length === 0) {
|
|
27400
|
+
return null;
|
|
27401
|
+
}
|
|
27402
|
+
return {
|
|
27403
|
+
type: "invalid",
|
|
27404
|
+
message: null
|
|
27405
|
+
};
|
|
27406
|
+
}
|
|
27407
|
+
|
|
27612
27408
|
// exports has no 'transform'
|
|
27613
27409
|
|
|
27614
27410
|
class Group extends React__namespace.Component {
|
|
@@ -27761,8 +27557,11 @@ var Group$1 = {
|
|
|
27761
27557
|
widget: Group,
|
|
27762
27558
|
traverseChildWidgets: traverseChildWidgets,
|
|
27763
27559
|
// TODO(LEMS-2656): remove TS suppression
|
|
27764
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type '
|
|
27560
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27765
27561
|
scorer: scoreGroup,
|
|
27562
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27563
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27564
|
+
validator: validateGroup,
|
|
27766
27565
|
hidden: true,
|
|
27767
27566
|
isLintable: true
|
|
27768
27567
|
};
|
|
@@ -30682,6 +30481,96 @@ function getInterceptStringForLine(line, strings, locale) {
|
|
|
30682
30481
|
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30683
30482
|
});
|
|
30684
30483
|
}
|
|
30484
|
+
function getCoordQuadrant(coord) {
|
|
30485
|
+
const [unroundedX, unroundedY] = coord;
|
|
30486
|
+
const x = Number(unroundedX.toFixed(3));
|
|
30487
|
+
const y = Number(unroundedY.toFixed(3));
|
|
30488
|
+
if (x === 0 && y === 0) {
|
|
30489
|
+
return "origin";
|
|
30490
|
+
}
|
|
30491
|
+
if (y === 0) {
|
|
30492
|
+
return "x-axis";
|
|
30493
|
+
}
|
|
30494
|
+
if (x === 0) {
|
|
30495
|
+
return "y-axis";
|
|
30496
|
+
}
|
|
30497
|
+
if (x > 0 && y > 0) {
|
|
30498
|
+
return 1;
|
|
30499
|
+
}
|
|
30500
|
+
if (x < 0 && y > 0) {
|
|
30501
|
+
return 2;
|
|
30502
|
+
}
|
|
30503
|
+
if (x < 0 && y < 0) {
|
|
30504
|
+
return 3;
|
|
30505
|
+
}
|
|
30506
|
+
return 4;
|
|
30507
|
+
}
|
|
30508
|
+
function getQuadraticVertexString(vertex, strings) {
|
|
30509
|
+
const location = getCoordQuadrant(vertex);
|
|
30510
|
+
switch (location) {
|
|
30511
|
+
case "origin":
|
|
30512
|
+
return strings.srQuadraticGraphVertexOrigin;
|
|
30513
|
+
case "x-axis":
|
|
30514
|
+
return strings.srQuadraticGraphVertexXAxis;
|
|
30515
|
+
case "y-axis":
|
|
30516
|
+
return strings.srQuadraticGraphVertexYAxis;
|
|
30517
|
+
default:
|
|
30518
|
+
return strings.srQuadraticGraphVertexQuadrant({
|
|
30519
|
+
quadrant: location
|
|
30520
|
+
});
|
|
30521
|
+
}
|
|
30522
|
+
}
|
|
30523
|
+
function getQuadraticPointString(pointNumber, coord, strings, locale) {
|
|
30524
|
+
const location = getCoordQuadrant(coord);
|
|
30525
|
+
const [x, y] = coord;
|
|
30526
|
+
switch (location) {
|
|
30527
|
+
case "origin":
|
|
30528
|
+
return strings.srQuadraticPointOrigin({
|
|
30529
|
+
pointNumber: pointNumber
|
|
30530
|
+
});
|
|
30531
|
+
case "x-axis":
|
|
30532
|
+
case "y-axis":
|
|
30533
|
+
return strings.srQuadraticPointAxis({
|
|
30534
|
+
pointNumber: pointNumber,
|
|
30535
|
+
x: srFormatNumber(x, locale),
|
|
30536
|
+
y: srFormatNumber(y, locale)
|
|
30537
|
+
});
|
|
30538
|
+
default:
|
|
30539
|
+
// return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
|
|
30540
|
+
return strings.srQuadraticPointQuadrant({
|
|
30541
|
+
pointNumber: pointNumber,
|
|
30542
|
+
quadrant: location,
|
|
30543
|
+
x: srFormatNumber(x, locale),
|
|
30544
|
+
y: srFormatNumber(y, locale)
|
|
30545
|
+
});
|
|
30546
|
+
}
|
|
30547
|
+
}
|
|
30548
|
+
function getQuadraticXIntercepts(
|
|
30549
|
+
// Coefficients of the quadratic equation
|
|
30550
|
+
a, b, c) {
|
|
30551
|
+
// If a is 0, the equation is a line and has maybe only one x-intercept.
|
|
30552
|
+
if (a === 0) {
|
|
30553
|
+
// If b is 0, the equation a horizontal line and has no x-intercepts.
|
|
30554
|
+
if (b === 0) {
|
|
30555
|
+
return [];
|
|
30556
|
+
}
|
|
30557
|
+
return [-c / b];
|
|
30558
|
+
}
|
|
30559
|
+
|
|
30560
|
+
// Find the x-intercepts by plugging in y = 0 into the quadratic equation.
|
|
30561
|
+
// 0 = ax^2 + bx + c
|
|
30562
|
+
// x = (-b ± sqrt(b^2 - 4ac)) / 2a
|
|
30563
|
+
const discriminant = b * b - 4 * a * c;
|
|
30564
|
+
if (discriminant < 0) {
|
|
30565
|
+
return [];
|
|
30566
|
+
}
|
|
30567
|
+
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
|
|
30568
|
+
const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
|
|
30569
|
+
if (x1 === x2) {
|
|
30570
|
+
return [x1];
|
|
30571
|
+
}
|
|
30572
|
+
return [x1, x2];
|
|
30573
|
+
}
|
|
30685
30574
|
|
|
30686
30575
|
const {
|
|
30687
30576
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
@@ -32519,7 +32408,9 @@ function renderLinearGraph(state, dispatch) {
|
|
|
32519
32408
|
graphState: state,
|
|
32520
32409
|
dispatch: dispatch
|
|
32521
32410
|
}),
|
|
32522
|
-
interactiveElementsDescription:
|
|
32411
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearGraphDescription, {
|
|
32412
|
+
state: state
|
|
32413
|
+
})
|
|
32523
32414
|
};
|
|
32524
32415
|
}
|
|
32525
32416
|
const LinearGraph = (props, key) => {
|
|
@@ -32538,33 +32429,29 @@ const LinearGraph = (props, key) => {
|
|
|
32538
32429
|
const interceptDescriptionId = id + "-intercept";
|
|
32539
32430
|
const slopeDescriptionId = id + "-slope";
|
|
32540
32431
|
|
|
32541
|
-
// Aria
|
|
32542
|
-
const
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32547
|
-
|
|
32548
|
-
|
|
32549
|
-
|
|
32550
|
-
|
|
32551
|
-
point2X: srFormatNumber(line[1][0], locale),
|
|
32552
|
-
point2Y: srFormatNumber(line[1][1], locale)
|
|
32432
|
+
// Aria strings
|
|
32433
|
+
const {
|
|
32434
|
+
srLinearGraph,
|
|
32435
|
+
srLinearGraphPoints,
|
|
32436
|
+
srLinearGrabHandle,
|
|
32437
|
+
slopeString,
|
|
32438
|
+
interceptString
|
|
32439
|
+
} = describeLinearGraph(props.graphState, {
|
|
32440
|
+
strings,
|
|
32441
|
+
locale
|
|
32553
32442
|
});
|
|
32554
|
-
const slopeString = getSlopeStringForLine(line, strings);
|
|
32555
|
-
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32556
32443
|
|
|
32557
32444
|
// Linear graphs only have one line
|
|
32558
32445
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
32559
32446
|
// and another action for moving multiple lines
|
|
32560
32447
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32561
32448
|
// Outer line minimal description
|
|
32562
|
-
"aria-label":
|
|
32449
|
+
"aria-label": srLinearGraph,
|
|
32563
32450
|
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
32564
32451
|
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
32565
32452
|
key: 0,
|
|
32566
32453
|
ariaLabels: {
|
|
32567
|
-
grabHandleAriaLabel:
|
|
32454
|
+
grabHandleAriaLabel: srLinearGrabHandle
|
|
32568
32455
|
},
|
|
32569
32456
|
ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
|
|
32570
32457
|
points: line,
|
|
@@ -32580,7 +32467,7 @@ const LinearGraph = (props, key) => {
|
|
|
32580
32467
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32581
32468
|
id: pointsDescriptionId,
|
|
32582
32469
|
style: a11y.srOnly
|
|
32583
|
-
},
|
|
32470
|
+
}, srLinearGraphPoints), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32584
32471
|
id: interceptDescriptionId,
|
|
32585
32472
|
style: a11y.srOnly
|
|
32586
32473
|
}, interceptString), /*#__PURE__*/React__namespace.createElement("g", {
|
|
@@ -32588,6 +32475,56 @@ const LinearGraph = (props, key) => {
|
|
|
32588
32475
|
style: a11y.srOnly
|
|
32589
32476
|
}, slopeString));
|
|
32590
32477
|
};
|
|
32478
|
+
function LinearGraphDescription(_ref) {
|
|
32479
|
+
let {
|
|
32480
|
+
state
|
|
32481
|
+
} = _ref;
|
|
32482
|
+
// The reason that LinearGraphDescription is a component (rather than a
|
|
32483
|
+
// function that returns a string) is because it needs to use a
|
|
32484
|
+
// hook: `usePerseusI18n`.
|
|
32485
|
+
const i18n = usePerseusI18n();
|
|
32486
|
+
const strings = describeLinearGraph(state, i18n);
|
|
32487
|
+
return strings.srLinearInteractiveElement;
|
|
32488
|
+
}
|
|
32489
|
+
|
|
32490
|
+
// Exported for testing
|
|
32491
|
+
function describeLinearGraph(state, i18n) {
|
|
32492
|
+
const {
|
|
32493
|
+
coords: line
|
|
32494
|
+
} = state;
|
|
32495
|
+
const {
|
|
32496
|
+
strings,
|
|
32497
|
+
locale
|
|
32498
|
+
} = i18n;
|
|
32499
|
+
|
|
32500
|
+
// Aria label strings
|
|
32501
|
+
const srLinearGraph = strings.srLinearGraph;
|
|
32502
|
+
const srLinearGraphPoints = strings.srLinearGraphPoints({
|
|
32503
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32504
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32505
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32506
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32507
|
+
});
|
|
32508
|
+
const srLinearGrabHandle = strings.srLinearGrabHandle({
|
|
32509
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32510
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32511
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32512
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32513
|
+
});
|
|
32514
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32515
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32516
|
+
const srLinearInteractiveElement = strings.srInteractiveElements({
|
|
32517
|
+
elements: [srLinearGraph, srLinearGraphPoints].join(" ")
|
|
32518
|
+
});
|
|
32519
|
+
return {
|
|
32520
|
+
srLinearGraph,
|
|
32521
|
+
srLinearGraphPoints,
|
|
32522
|
+
srLinearGrabHandle,
|
|
32523
|
+
slopeString,
|
|
32524
|
+
interceptString,
|
|
32525
|
+
srLinearInteractiveElement
|
|
32526
|
+
};
|
|
32527
|
+
}
|
|
32591
32528
|
|
|
32592
32529
|
function renderLinearSystemGraph(state, dispatch) {
|
|
32593
32530
|
return {
|
|
@@ -33146,13 +33083,13 @@ const hasFocusVisible = element => {
|
|
|
33146
33083
|
}
|
|
33147
33084
|
};
|
|
33148
33085
|
|
|
33149
|
-
function renderQuadraticGraph(state, dispatch) {
|
|
33086
|
+
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
33150
33087
|
return {
|
|
33151
33088
|
graph: /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
|
|
33152
33089
|
graphState: state,
|
|
33153
33090
|
dispatch: dispatch
|
|
33154
33091
|
}),
|
|
33155
|
-
interactiveElementsDescription:
|
|
33092
|
+
interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
|
|
33156
33093
|
};
|
|
33157
33094
|
}
|
|
33158
33095
|
function QuadraticGraph(props) {
|
|
@@ -33163,6 +33100,14 @@ function QuadraticGraph(props) {
|
|
|
33163
33100
|
const {
|
|
33164
33101
|
coords
|
|
33165
33102
|
} = graphState;
|
|
33103
|
+
const {
|
|
33104
|
+
strings,
|
|
33105
|
+
locale
|
|
33106
|
+
} = usePerseusI18n();
|
|
33107
|
+
const id = React__namespace.useId();
|
|
33108
|
+
const quadraticDirectionId = id + "-direction";
|
|
33109
|
+
const quadraticVertexId = id + "-vertex";
|
|
33110
|
+
const quadraticInterceptsId = id + "-intercepts";
|
|
33166
33111
|
|
|
33167
33112
|
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
33168
33113
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
@@ -33178,15 +33123,46 @@ function QuadraticGraph(props) {
|
|
|
33178
33123
|
|
|
33179
33124
|
// Calculate the y value based on the current x value
|
|
33180
33125
|
const y = x => (a * x + b) * x + c;
|
|
33181
|
-
|
|
33126
|
+
|
|
33127
|
+
// Aria strings
|
|
33128
|
+
const {
|
|
33129
|
+
srQuadraticGraph,
|
|
33130
|
+
srQuadraticDirection,
|
|
33131
|
+
srQuadraticVertex,
|
|
33132
|
+
srQuadraticXIntercepts,
|
|
33133
|
+
srQuadraticYIntercept
|
|
33134
|
+
} = describeQuadraticGraph(graphState, {
|
|
33135
|
+
strings,
|
|
33136
|
+
locale
|
|
33137
|
+
});
|
|
33138
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33139
|
+
// Outer graph minimal description
|
|
33140
|
+
"aria-label": srQuadraticGraph,
|
|
33141
|
+
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
33142
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
33182
33143
|
y: y,
|
|
33183
33144
|
color: wonderBlocksTokens.color.blue
|
|
33184
|
-
}), coords.map((coord, i) =>
|
|
33185
|
-
|
|
33186
|
-
|
|
33187
|
-
|
|
33188
|
-
|
|
33189
|
-
|
|
33145
|
+
}), coords.map((coord, i) => {
|
|
33146
|
+
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
33147
|
+
// Vertex might not exist if the quadratic graph is a line
|
|
33148
|
+
const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
|
|
33149
|
+
return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33150
|
+
key: "point-" + i,
|
|
33151
|
+
ariaLabel: `${srQuadraticPoint}${srVertex}`,
|
|
33152
|
+
point: coord,
|
|
33153
|
+
sequenceNumber: i + 1,
|
|
33154
|
+
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
33155
|
+
});
|
|
33156
|
+
}), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
|
|
33157
|
+
id: quadraticDirectionId,
|
|
33158
|
+
style: a11y.srOnly
|
|
33159
|
+
}, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React__namespace.createElement("g", {
|
|
33160
|
+
id: quadraticVertexId,
|
|
33161
|
+
style: a11y.srOnly
|
|
33162
|
+
}, srQuadraticVertex), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33163
|
+
id: quadraticInterceptsId,
|
|
33164
|
+
style: a11y.srOnly
|
|
33165
|
+
}, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
|
|
33190
33166
|
}
|
|
33191
33167
|
|
|
33192
33168
|
// Get the quadratic coefficients from the 3 control points
|
|
@@ -33209,6 +33185,60 @@ const getQuadraticCoefficients$2 = coords => {
|
|
|
33209
33185
|
const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
|
|
33210
33186
|
return [a, b, c];
|
|
33211
33187
|
};
|
|
33188
|
+
function getQuadraticGraphDescription(state, i18n) {
|
|
33189
|
+
const strings = describeQuadraticGraph(state, i18n);
|
|
33190
|
+
return strings.srQuadraticInteractiveElements;
|
|
33191
|
+
}
|
|
33192
|
+
// Exported for testing
|
|
33193
|
+
function describeQuadraticGraph(state, i18n) {
|
|
33194
|
+
const {
|
|
33195
|
+
strings,
|
|
33196
|
+
locale
|
|
33197
|
+
} = i18n;
|
|
33198
|
+
const coeffs = getQuadraticCoefficients$2(state.coords);
|
|
33199
|
+
const [a, b, c] = coeffs ?? [0, 0, 0];
|
|
33200
|
+
const vertex = [-b / (2 * a), c - b * b / (4 * a)];
|
|
33201
|
+
const xIntercepts = getQuadraticXIntercepts(a, b, c);
|
|
33202
|
+
|
|
33203
|
+
// Aria label strings
|
|
33204
|
+
const srQuadraticGraph = strings.srQuadraticGraph;
|
|
33205
|
+
const srQuadraticFaceUp = strings.srQuadraticFaceUp;
|
|
33206
|
+
const srQuadraticFaceDown = strings.srQuadraticFaceDown;
|
|
33207
|
+
// The graph only has a direction if it is not a line.
|
|
33208
|
+
const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
|
|
33209
|
+
// Only describe vertex if the quadratic graph is not a line.
|
|
33210
|
+
// (Undefined means the quadratic graph is a line and has no vertex.)
|
|
33211
|
+
const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
|
|
33212
|
+
// Undefined means the quadratic graph has no x-intercepts,
|
|
33213
|
+
// such as when the graph is a horizontal line.
|
|
33214
|
+
const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
|
|
33215
|
+
intercept1: srFormatNumber(xIntercepts[0], locale),
|
|
33216
|
+
intercept2: srFormatNumber(xIntercepts[1], locale)
|
|
33217
|
+
}) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
|
|
33218
|
+
intercept: srFormatNumber(xIntercepts[0], locale)
|
|
33219
|
+
}) : undefined;
|
|
33220
|
+
const srQuadraticYIntercept = strings.srQuadraticYIntercept({
|
|
33221
|
+
intercept: srFormatNumber(c, locale)
|
|
33222
|
+
});
|
|
33223
|
+
const srQuadraticInteractiveElements = strings.srInteractiveElements({
|
|
33224
|
+
elements: strings.srQuadraticInteractiveElements({
|
|
33225
|
+
point1X: srFormatNumber(state.coords[0][0], locale),
|
|
33226
|
+
point1Y: srFormatNumber(state.coords[0][1], locale),
|
|
33227
|
+
point2X: srFormatNumber(state.coords[1][0], locale),
|
|
33228
|
+
point2Y: srFormatNumber(state.coords[1][1], locale),
|
|
33229
|
+
point3X: srFormatNumber(state.coords[2][0], locale),
|
|
33230
|
+
point3Y: srFormatNumber(state.coords[2][1], locale)
|
|
33231
|
+
})
|
|
33232
|
+
});
|
|
33233
|
+
return {
|
|
33234
|
+
srQuadraticGraph,
|
|
33235
|
+
srQuadraticDirection,
|
|
33236
|
+
srQuadraticVertex,
|
|
33237
|
+
srQuadraticXIntercepts,
|
|
33238
|
+
srQuadraticYIntercept,
|
|
33239
|
+
srQuadraticInteractiveElements
|
|
33240
|
+
};
|
|
33241
|
+
}
|
|
33212
33242
|
|
|
33213
33243
|
function renderRayGraph(state, dispatch) {
|
|
33214
33244
|
return {
|
|
@@ -33335,17 +33365,71 @@ function renderSegmentGraph(state, dispatch) {
|
|
|
33335
33365
|
graphState: state,
|
|
33336
33366
|
dispatch: dispatch
|
|
33337
33367
|
}),
|
|
33338
|
-
interactiveElementsDescription:
|
|
33368
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(SegmentGraphDescription, {
|
|
33369
|
+
state: state
|
|
33370
|
+
})
|
|
33339
33371
|
};
|
|
33340
33372
|
}
|
|
33341
|
-
const SegmentGraph =
|
|
33342
|
-
|
|
33343
|
-
dispatch
|
|
33344
|
-
|
|
33373
|
+
const SegmentGraph = _ref => {
|
|
33374
|
+
let {
|
|
33375
|
+
dispatch,
|
|
33376
|
+
graphState
|
|
33377
|
+
} = _ref;
|
|
33345
33378
|
const {
|
|
33346
33379
|
coords: segments
|
|
33347
|
-
} =
|
|
33348
|
-
|
|
33380
|
+
} = graphState;
|
|
33381
|
+
const {
|
|
33382
|
+
strings,
|
|
33383
|
+
locale
|
|
33384
|
+
} = usePerseusI18n();
|
|
33385
|
+
const segmentUniqueId = React__namespace.useId();
|
|
33386
|
+
const lengthDescriptionId = segmentUniqueId + "-length";
|
|
33387
|
+
const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
|
|
33388
|
+
function getWholeSegmentGraphAriaLabel() {
|
|
33389
|
+
return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({
|
|
33390
|
+
countOfSegments: segments.length
|
|
33391
|
+
}) : strings.srSingleSegmentGraphAriaLabel;
|
|
33392
|
+
}
|
|
33393
|
+
const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
|
|
33394
|
+
function getIndividualSegmentAriaLabel(segment, index) {
|
|
33395
|
+
if (segments.length === 1) {
|
|
33396
|
+
return strings.srSingleSegmentLabel({
|
|
33397
|
+
point1X: srFormatNumber(segments[0][0][X], locale),
|
|
33398
|
+
point1Y: srFormatNumber(segments[0][0][Y], locale),
|
|
33399
|
+
point2X: srFormatNumber(segments[0][1][X], locale),
|
|
33400
|
+
point2Y: srFormatNumber(segments[0][1][Y], locale)
|
|
33401
|
+
});
|
|
33402
|
+
}
|
|
33403
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
33404
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33405
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33406
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33407
|
+
point2Y: srFormatNumber(segment[1][Y], locale),
|
|
33408
|
+
indexOfSegment: index + 1
|
|
33409
|
+
});
|
|
33410
|
+
}
|
|
33411
|
+
function getWholeSegmentGraphAriaDescription() {
|
|
33412
|
+
return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
|
|
33413
|
+
}
|
|
33414
|
+
function formatSegment(endpointNumber, x, y, index) {
|
|
33415
|
+
const segObj = {
|
|
33416
|
+
endpointNumber: endpointNumber,
|
|
33417
|
+
x: srFormatNumber(x, locale),
|
|
33418
|
+
y: srFormatNumber(y, locale)
|
|
33419
|
+
};
|
|
33420
|
+
return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel({
|
|
33421
|
+
...segObj,
|
|
33422
|
+
indexOfSegment: index
|
|
33423
|
+
}) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
|
|
33424
|
+
}
|
|
33425
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33426
|
+
"aria-label": wholeSegmentGraphAriaLabel,
|
|
33427
|
+
"aria-describedby": wholeGraphDescriptionId
|
|
33428
|
+
}, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement("g", {
|
|
33429
|
+
"aria-label": getIndividualSegmentAriaLabel(segment, i),
|
|
33430
|
+
"aria-describedby": lengthDescriptionId,
|
|
33431
|
+
key: `${segmentUniqueId}-${i}`
|
|
33432
|
+
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
33349
33433
|
key: i,
|
|
33350
33434
|
points: segment,
|
|
33351
33435
|
onMoveLine: delta => {
|
|
@@ -33353,9 +33437,61 @@ const SegmentGraph = props => {
|
|
|
33353
33437
|
},
|
|
33354
33438
|
onMovePoint: (endpointIndex, destination) => {
|
|
33355
33439
|
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
33440
|
+
},
|
|
33441
|
+
ariaLabels: {
|
|
33442
|
+
point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
|
|
33443
|
+
point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
|
|
33444
|
+
grabHandleAriaLabel: strings.srSegmentGrabHandle({
|
|
33445
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33446
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33447
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33448
|
+
point2Y: srFormatNumber(segment[1][Y], locale)
|
|
33449
|
+
})
|
|
33356
33450
|
}
|
|
33357
|
-
})
|
|
33451
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33452
|
+
id: lengthDescriptionId,
|
|
33453
|
+
style: a11y.srOnly
|
|
33454
|
+
}, strings.srSegmentLength({
|
|
33455
|
+
length: srFormatNumber(getLengthOfSegment(segment), locale)
|
|
33456
|
+
})))), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33457
|
+
style: a11y.srOnly,
|
|
33458
|
+
id: wholeGraphDescriptionId
|
|
33459
|
+
}, getWholeSegmentGraphAriaDescription()));
|
|
33358
33460
|
};
|
|
33461
|
+
function getLengthOfSegment(segment) {
|
|
33462
|
+
return kmath.point.distanceToPoint(...segment);
|
|
33463
|
+
}
|
|
33464
|
+
function SegmentGraphDescription(_ref2) {
|
|
33465
|
+
let {
|
|
33466
|
+
state
|
|
33467
|
+
} = _ref2;
|
|
33468
|
+
// The reason that SegmentGraphDescription is a component (rather than a
|
|
33469
|
+
// function that returns a string) is because it needs to use a
|
|
33470
|
+
// hook: `usePerseusI18n`.
|
|
33471
|
+
const i18n = usePerseusI18n();
|
|
33472
|
+
return describeSegmentGraph(state, i18n);
|
|
33473
|
+
}
|
|
33474
|
+
|
|
33475
|
+
// Exported for testing
|
|
33476
|
+
function describeSegmentGraph(state, i18n) {
|
|
33477
|
+
const {
|
|
33478
|
+
strings,
|
|
33479
|
+
locale
|
|
33480
|
+
} = i18n;
|
|
33481
|
+
const segmentDescriptions = state.coords.map((_ref3, index) => {
|
|
33482
|
+
let [point1, point2] = _ref3;
|
|
33483
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
33484
|
+
point1X: srFormatNumber(point1[X], locale),
|
|
33485
|
+
point1Y: srFormatNumber(point1[Y], locale),
|
|
33486
|
+
point2X: srFormatNumber(point2[X], locale),
|
|
33487
|
+
point2Y: srFormatNumber(point2[Y], locale),
|
|
33488
|
+
indexOfSegment: index + 1
|
|
33489
|
+
});
|
|
33490
|
+
});
|
|
33491
|
+
return strings.srInteractiveElements({
|
|
33492
|
+
elements: segmentDescriptions.join(" ")
|
|
33493
|
+
});
|
|
33494
|
+
}
|
|
33359
33495
|
|
|
33360
33496
|
function renderSinusoidGraph(state, dispatch) {
|
|
33361
33497
|
return {
|
|
@@ -33631,9 +33767,10 @@ const MafsGraph = props => {
|
|
|
33631
33767
|
x: viewboxX,
|
|
33632
33768
|
y: viewboxY
|
|
33633
33769
|
};
|
|
33770
|
+
const i18n = usePerseusI18n();
|
|
33634
33771
|
const {
|
|
33635
33772
|
strings
|
|
33636
|
-
} =
|
|
33773
|
+
} = i18n;
|
|
33637
33774
|
const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
|
|
33638
33775
|
wonderBlocksCore.useOnMountEffect(() => {
|
|
33639
33776
|
analytics.onAnalyticsEvent({
|
|
@@ -33650,7 +33787,8 @@ const MafsGraph = props => {
|
|
|
33650
33787
|
interactiveElementsDescription
|
|
33651
33788
|
} = renderGraphElements({
|
|
33652
33789
|
state,
|
|
33653
|
-
dispatch
|
|
33790
|
+
dispatch,
|
|
33791
|
+
i18n
|
|
33654
33792
|
});
|
|
33655
33793
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
33656
33794
|
value: {
|
|
@@ -34027,7 +34165,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
34027
34165
|
const renderGraphElements = props => {
|
|
34028
34166
|
const {
|
|
34029
34167
|
state,
|
|
34030
|
-
dispatch
|
|
34168
|
+
dispatch,
|
|
34169
|
+
i18n
|
|
34031
34170
|
} = props;
|
|
34032
34171
|
const {
|
|
34033
34172
|
type
|
|
@@ -34050,7 +34189,7 @@ const renderGraphElements = props => {
|
|
|
34050
34189
|
case "circle":
|
|
34051
34190
|
return renderCircleGraph(state, dispatch);
|
|
34052
34191
|
case "quadratic":
|
|
34053
|
-
return renderQuadraticGraph(state, dispatch);
|
|
34192
|
+
return renderQuadraticGraph(state, dispatch, i18n);
|
|
34054
34193
|
case "sinusoid":
|
|
34055
34194
|
return renderSinusoidGraph(state, dispatch);
|
|
34056
34195
|
case "none":
|
|
@@ -37670,19 +37809,21 @@ class LabelImage extends React__namespace.Component {
|
|
|
37670
37809
|
this._mounted = false;
|
|
37671
37810
|
}
|
|
37672
37811
|
getUserInput() {
|
|
37673
|
-
const {
|
|
37674
|
-
markers
|
|
37675
|
-
} = this.props;
|
|
37676
37812
|
return {
|
|
37677
|
-
markers
|
|
37813
|
+
markers: this.props.markers.map(marker => ({
|
|
37814
|
+
selected: marker.selected,
|
|
37815
|
+
label: marker.label
|
|
37816
|
+
}))
|
|
37678
37817
|
};
|
|
37679
37818
|
}
|
|
37680
37819
|
getPromptJSON() {
|
|
37681
37820
|
return getPromptJSON$a(this.props, this.getUserInput());
|
|
37682
37821
|
}
|
|
37683
37822
|
|
|
37684
|
-
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37685
|
-
|
|
37823
|
+
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37824
|
+
// Also consider how scoreMarker is being called as it seems to require the marker.answers property.
|
|
37825
|
+
// Removed rubric parameter, but it gets a full widget options object from the renderer
|
|
37826
|
+
showRationalesForCurrentlySelectedChoices() {
|
|
37686
37827
|
const {
|
|
37687
37828
|
markers
|
|
37688
37829
|
} = this.props;
|
|
@@ -37690,7 +37831,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37690
37831
|
onChange
|
|
37691
37832
|
} = this.props;
|
|
37692
37833
|
const updatedMarkers = markers.map(marker => {
|
|
37693
|
-
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
37834
|
+
const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37694
37835
|
return {
|
|
37695
37836
|
...marker,
|
|
37696
37837
|
// Reveal correctness state for markers with answers.
|
|
@@ -37798,6 +37939,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37798
37939
|
selected: selected.length ? selected : undefined
|
|
37799
37940
|
});
|
|
37800
37941
|
}
|
|
37942
|
+
// TODO(LEMS-2723): Investigate if possible to change this to not require answers
|
|
37801
37943
|
renderMarkers() {
|
|
37802
37944
|
const {
|
|
37803
37945
|
markers,
|
|
@@ -37839,7 +37981,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37839
37981
|
bottom: "top"
|
|
37840
37982
|
}[markerPosition];
|
|
37841
37983
|
}
|
|
37842
|
-
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
37984
|
+
const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37843
37985
|
// Once the question is answered, show markers
|
|
37844
37986
|
// with correct answers, otherwise passthrough
|
|
37845
37987
|
// the correctness state.
|
|
@@ -39384,7 +39526,10 @@ var Matrix$1 = {
|
|
|
39384
39526
|
isLintable: true,
|
|
39385
39527
|
// TODO(LEMS-2656): remove TS suppression
|
|
39386
39528
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39387
|
-
scorer: perseusScore.scoreMatrix
|
|
39529
|
+
scorer: perseusScore.scoreMatrix,
|
|
39530
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39531
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39532
|
+
validator: perseusScore.validateMatrix
|
|
39388
39533
|
};
|
|
39389
39534
|
|
|
39390
39535
|
const getPromptJSON$7 = () => {
|
|
@@ -39493,34 +39638,13 @@ class Measurer extends React__namespace.Component {
|
|
|
39493
39638
|
}));
|
|
39494
39639
|
}
|
|
39495
39640
|
}
|
|
39496
|
-
const propUpgrades = {
|
|
39497
|
-
"1": v0props => {
|
|
39498
|
-
const {
|
|
39499
|
-
imageUrl,
|
|
39500
|
-
imageTop,
|
|
39501
|
-
imageLeft,
|
|
39502
|
-
...rest
|
|
39503
|
-
} = v0props;
|
|
39504
|
-
return {
|
|
39505
|
-
...rest,
|
|
39506
|
-
image: {
|
|
39507
|
-
url: imageUrl,
|
|
39508
|
-
top: imageTop,
|
|
39509
|
-
left: imageLeft
|
|
39510
|
-
}
|
|
39511
|
-
};
|
|
39512
|
-
}
|
|
39513
|
-
};
|
|
39514
39641
|
var Measurer$1 = {
|
|
39515
39642
|
name: "measurer",
|
|
39516
39643
|
displayName: "Measurer",
|
|
39517
39644
|
hidden: true,
|
|
39518
39645
|
widget: Measurer,
|
|
39519
|
-
version:
|
|
39520
|
-
|
|
39521
|
-
minor: 0
|
|
39522
|
-
},
|
|
39523
|
-
propUpgrades: propUpgrades,
|
|
39646
|
+
version: perseusCore.measurerLogic.version,
|
|
39647
|
+
propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades,
|
|
39524
39648
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39525
39649
|
scorer: () => scoreNoop(1)
|
|
39526
39650
|
};
|
|
@@ -40095,10 +40219,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
40095
40219
|
const firstChar = smiles[0];
|
|
40096
40220
|
const rest = smiles.slice(1);
|
|
40097
40221
|
if (firstChar === "=") {
|
|
40098
|
-
return parse$
|
|
40222
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
40099
40223
|
}
|
|
40100
40224
|
if (firstChar === "#") {
|
|
40101
|
-
return parse$
|
|
40225
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
40102
40226
|
}
|
|
40103
40227
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
40104
40228
|
}
|
|
@@ -40159,8 +40283,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
40159
40283
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
40160
40284
|
parens: newCtx.parens.concat("(")
|
|
40161
40285
|
};
|
|
40162
|
-
const parenExpr = parse$
|
|
40163
|
-
const remainder = parse$
|
|
40286
|
+
const parenExpr = parse$1(rest, parenCtx);
|
|
40287
|
+
const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
40164
40288
|
return [parenExpr].concat(remainder);
|
|
40165
40289
|
}
|
|
40166
40290
|
if (firstChar === ")") {
|
|
@@ -40224,7 +40348,7 @@ function parseAtom(smiles, ctx) {
|
|
|
40224
40348
|
|
|
40225
40349
|
// increment the atom counter and reset the branch counter
|
|
40226
40350
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
40227
|
-
let restOfMolecule = parse$
|
|
40351
|
+
let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
40228
40352
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
40229
40353
|
//TODO(colin): fix this awkwardness.
|
|
40230
40354
|
restOfMolecule = [restOfMolecule];
|
|
@@ -40263,7 +40387,7 @@ function isModifierChar(s) {
|
|
|
40263
40387
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
40264
40388
|
* yet implemented.
|
|
40265
40389
|
*/
|
|
40266
|
-
function parse$
|
|
40390
|
+
function parse$1(smiles, ctx) {
|
|
40267
40391
|
if (!validate(smiles)) {
|
|
40268
40392
|
throw new ParseError$1("Invalid input.");
|
|
40269
40393
|
}
|
|
@@ -40288,7 +40412,7 @@ function parse$2(smiles, ctx) {
|
|
|
40288
40412
|
return parseParenthesizedExpression(smiles, ctx);
|
|
40289
40413
|
}
|
|
40290
40414
|
var SmilesParser = {
|
|
40291
|
-
parse: parse$
|
|
40415
|
+
parse: parse$1,
|
|
40292
40416
|
ParseError: ParseError$1
|
|
40293
40417
|
};
|
|
40294
40418
|
|
|
@@ -40296,7 +40420,7 @@ var SmilesParser = {
|
|
|
40296
40420
|
const {
|
|
40297
40421
|
layout
|
|
40298
40422
|
} = MoleculeLayout;
|
|
40299
|
-
const parse
|
|
40423
|
+
const parse = SmilesParser.parse;
|
|
40300
40424
|
const ParseError = SmilesParser.ParseError;
|
|
40301
40425
|
const borderSize = 30;
|
|
40302
40426
|
class Molecule extends React__namespace.Component {
|
|
@@ -40323,7 +40447,7 @@ class Molecule extends React__namespace.Component {
|
|
|
40323
40447
|
try {
|
|
40324
40448
|
this.setState({
|
|
40325
40449
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
40326
|
-
parsedSmiles: parse
|
|
40450
|
+
parsedSmiles: parse(smiles),
|
|
40327
40451
|
error: null
|
|
40328
40452
|
});
|
|
40329
40453
|
} catch (e) {
|
|
@@ -40988,7 +41112,10 @@ var NumberLine$1 = {
|
|
|
40988
41112
|
staticTransform: staticTransform$1,
|
|
40989
41113
|
// TODO(LEMS-2656): remove TS suppression
|
|
40990
41114
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40991
|
-
scorer: perseusScore.scoreNumberLine
|
|
41115
|
+
scorer: perseusScore.scoreNumberLine,
|
|
41116
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41117
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
41118
|
+
validator: perseusScore.validateNumberLine
|
|
40992
41119
|
};
|
|
40993
41120
|
|
|
40994
41121
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -41542,7 +41669,11 @@ var Orderer$1 = {
|
|
|
41542
41669
|
isLintable: true,
|
|
41543
41670
|
// TODO(LEMS-2656): remove TS suppression
|
|
41544
41671
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41545
|
-
scorer: perseusScore.scoreOrderer
|
|
41672
|
+
scorer: perseusScore.scoreOrderer,
|
|
41673
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41674
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41675
|
+
validator: perseusScore.validateOrderer,
|
|
41676
|
+
getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
|
|
41546
41677
|
};
|
|
41547
41678
|
|
|
41548
41679
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -41591,10 +41722,6 @@ var PassageRefTarget$1 = {
|
|
|
41591
41722
|
transform: editorProps => {
|
|
41592
41723
|
return ___default["default"].pick(editorProps, "content");
|
|
41593
41724
|
},
|
|
41594
|
-
version: {
|
|
41595
|
-
major: 0,
|
|
41596
|
-
minor: 0
|
|
41597
|
-
},
|
|
41598
41725
|
isLintable: true,
|
|
41599
41726
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41600
41727
|
scorer: () => scoreNoop()
|
|
@@ -42618,7 +42745,10 @@ var Plotter$1 = {
|
|
|
42618
42745
|
staticTransform: staticTransform,
|
|
42619
42746
|
// TODO(LEMS-2656): remove TS suppression
|
|
42620
42747
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42621
|
-
scorer: perseusScore.scorePlotter
|
|
42748
|
+
scorer: perseusScore.scorePlotter,
|
|
42749
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42750
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42751
|
+
validator: perseusScore.validatePlotter
|
|
42622
42752
|
};
|
|
42623
42753
|
|
|
42624
42754
|
const getPromptJSON$2 = () => {
|
|
@@ -42781,7 +42911,10 @@ var Sorter$1 = {
|
|
|
42781
42911
|
isLintable: true,
|
|
42782
42912
|
// TODO(LEMS-2656): remove TS suppression
|
|
42783
42913
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42784
|
-
scorer: perseusScore.scoreSorter
|
|
42914
|
+
scorer: perseusScore.scoreSorter,
|
|
42915
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42916
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42917
|
+
validator: perseusScore.validateSorter
|
|
42785
42918
|
};
|
|
42786
42919
|
|
|
42787
42920
|
const {
|
|
@@ -43000,7 +43133,10 @@ var Table$1 = {
|
|
|
43000
43133
|
isLintable: true,
|
|
43001
43134
|
// TODO(LEMS-2656): remove TS suppression
|
|
43002
43135
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43003
|
-
scorer: perseusScore.scoreTable
|
|
43136
|
+
scorer: perseusScore.scoreTable,
|
|
43137
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43138
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43139
|
+
validator: perseusScore.validateTable
|
|
43004
43140
|
};
|
|
43005
43141
|
|
|
43006
43142
|
const getPromptJSON = () => {
|
|
@@ -43185,7 +43321,7 @@ const init = function () {
|
|
|
43185
43321
|
|
|
43186
43322
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43187
43323
|
const libName = "@khanacademy/perseus";
|
|
43188
|
-
const libVersion = "
|
|
43324
|
+
const libVersion = "52.0.1";
|
|
43189
43325
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43190
43326
|
|
|
43191
43327
|
const apiVersion = {
|
|
@@ -44631,6 +44767,69 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44631
44767
|
replaceDeprecatedWidgets();
|
|
44632
44768
|
};
|
|
44633
44769
|
|
|
44770
|
+
/**
|
|
44771
|
+
* Get a widget type by a widget's ID
|
|
44772
|
+
* (please don't derive type from ID)
|
|
44773
|
+
*
|
|
44774
|
+
* @param {string} widgetId the ID of the widget
|
|
44775
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44776
|
+
* @returns {string} the widget type (ie "radio")
|
|
44777
|
+
*/
|
|
44778
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44779
|
+
const widget = widgetMap[WidgetId];
|
|
44780
|
+
return widget?.type ?? null;
|
|
44781
|
+
}
|
|
44782
|
+
|
|
44783
|
+
/**
|
|
44784
|
+
* Does the content have a specific type of widget?
|
|
44785
|
+
*
|
|
44786
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44787
|
+
* @param {string} content the string to search through
|
|
44788
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44789
|
+
* @returns {boolean} if the content includes the widget type
|
|
44790
|
+
*/
|
|
44791
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44792
|
+
return perseusCore.getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44793
|
+
}
|
|
44794
|
+
|
|
44795
|
+
/**
|
|
44796
|
+
* Pull the widget map out of ItemData
|
|
44797
|
+
*
|
|
44798
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44799
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44800
|
+
*/
|
|
44801
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44802
|
+
return itemData.question.widgets;
|
|
44803
|
+
}
|
|
44804
|
+
|
|
44805
|
+
/**
|
|
44806
|
+
* Get the widget information from a WidgetMap
|
|
44807
|
+
*
|
|
44808
|
+
* @param {string} widgetId the ID of the widget
|
|
44809
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44810
|
+
*/
|
|
44811
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44812
|
+
return widgetMap[widgetId] ?? null;
|
|
44813
|
+
}
|
|
44814
|
+
|
|
44815
|
+
/**
|
|
44816
|
+
* Get select widgets from a widget map.
|
|
44817
|
+
*
|
|
44818
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44819
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44820
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44821
|
+
*/
|
|
44822
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44823
|
+
const widgets = {};
|
|
44824
|
+
widgetIds.forEach(widgetId => {
|
|
44825
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44826
|
+
if (widget) {
|
|
44827
|
+
widgets[widgetId] = widget;
|
|
44828
|
+
}
|
|
44829
|
+
});
|
|
44830
|
+
return widgets;
|
|
44831
|
+
}
|
|
44832
|
+
|
|
44634
44833
|
/**
|
|
44635
44834
|
* This function extracts the answers from the widgets.
|
|
44636
44835
|
*
|
|
@@ -45154,1683 +45353,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
45154
45353
|
return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
45155
45354
|
};
|
|
45156
45355
|
|
|
45157
|
-
function isRealJSONParse(jsonParse) {
|
|
45158
|
-
const randomPhrase = buildRandomPhrase();
|
|
45159
|
-
const randomHintPhrase = buildRandomPhrase();
|
|
45160
|
-
const randomString = buildRandomString();
|
|
45161
|
-
const testingObject = JSON.stringify({
|
|
45162
|
-
answerArea: {
|
|
45163
|
-
calculator: false,
|
|
45164
|
-
chi2Table: false,
|
|
45165
|
-
financialCalculatorMonthlyPayment: false,
|
|
45166
|
-
financialCalculatorTimeToPayOff: false,
|
|
45167
|
-
financialCalculatorTotalAmount: false,
|
|
45168
|
-
periodicTable: false,
|
|
45169
|
-
periodicTableWithKey: false,
|
|
45170
|
-
tTable: false,
|
|
45171
|
-
zTable: false
|
|
45172
|
-
},
|
|
45173
|
-
hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
|
|
45174
|
-
itemDataVersion: {
|
|
45175
|
-
major: 0,
|
|
45176
|
-
minor: 1
|
|
45177
|
-
},
|
|
45178
|
-
question: {
|
|
45179
|
-
content: `${randomPhrase}`,
|
|
45180
|
-
images: {},
|
|
45181
|
-
widgets: {
|
|
45182
|
-
expression1: {
|
|
45183
|
-
alignment: "default",
|
|
45184
|
-
graded: false,
|
|
45185
|
-
options: {
|
|
45186
|
-
answerForms: [{
|
|
45187
|
-
considered: "wrong",
|
|
45188
|
-
form: false,
|
|
45189
|
-
key: 0,
|
|
45190
|
-
simplify: false,
|
|
45191
|
-
value: `${randomString}`
|
|
45192
|
-
}],
|
|
45193
|
-
ariaLabel: "Answer",
|
|
45194
|
-
buttonSets: ["basic"],
|
|
45195
|
-
functions: ["f", "g", "h"],
|
|
45196
|
-
static: true,
|
|
45197
|
-
times: false,
|
|
45198
|
-
visibleLabel: "Answer"
|
|
45199
|
-
},
|
|
45200
|
-
static: true,
|
|
45201
|
-
type: "expression",
|
|
45202
|
-
version: {
|
|
45203
|
-
major: 1,
|
|
45204
|
-
minor: 0
|
|
45205
|
-
}
|
|
45206
|
-
}
|
|
45207
|
-
}
|
|
45208
|
-
}
|
|
45209
|
-
});
|
|
45210
|
-
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
45211
|
-
const parsedTestJSON = jsonParse(testJSON);
|
|
45212
|
-
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
45213
|
-
return perseusCore.approximateDeepEqual(parsedTestItemData, testingObject);
|
|
45214
|
-
}
|
|
45215
|
-
function buildRandomString() {
|
|
45216
|
-
let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
45217
|
-
let randomString = "";
|
|
45218
|
-
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
45219
|
-
for (let i = 0; i < randomLength; i++) {
|
|
45220
|
-
const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
45221
|
-
randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
|
|
45222
|
-
}
|
|
45223
|
-
return randomString;
|
|
45224
|
-
}
|
|
45225
|
-
function buildRandomPhrase() {
|
|
45226
|
-
const phrases = [];
|
|
45227
|
-
const randomLength = Math.floor(Math.random() * 10) + 5;
|
|
45228
|
-
for (let i = 0; i < randomLength; i++) {
|
|
45229
|
-
phrases.push(buildRandomString(i === 0));
|
|
45230
|
-
}
|
|
45231
|
-
const modifierStart = ["**", "$"];
|
|
45232
|
-
const modifierEnd = ["**", "$"];
|
|
45233
|
-
const modifierIndex = Math.floor(Math.random() * modifierStart.length);
|
|
45234
|
-
return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
|
|
45235
|
-
}
|
|
45236
|
-
function buildTestData(testObject) {
|
|
45237
|
-
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
45238
|
-
}
|
|
45239
|
-
|
|
45240
|
-
function success(value) {
|
|
45241
|
-
return {
|
|
45242
|
-
type: "success",
|
|
45243
|
-
value
|
|
45244
|
-
};
|
|
45245
|
-
}
|
|
45246
|
-
function failure(detail) {
|
|
45247
|
-
return {
|
|
45248
|
-
type: "failure",
|
|
45249
|
-
detail
|
|
45250
|
-
};
|
|
45251
|
-
}
|
|
45252
|
-
function isFailure(result) {
|
|
45253
|
-
return result.type === "failure";
|
|
45254
|
-
}
|
|
45255
|
-
function isSuccess(result) {
|
|
45256
|
-
return result.type === "success";
|
|
45257
|
-
}
|
|
45258
|
-
|
|
45259
|
-
// Result's `all` function is similar to Promise.all: given an array of
|
|
45260
|
-
// results, it returns success if all succeeded, and failure if any failed.
|
|
45261
|
-
function all(results) {
|
|
45262
|
-
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
45263
|
-
const values = [];
|
|
45264
|
-
const failureDetails = [];
|
|
45265
|
-
for (const result of results) {
|
|
45266
|
-
if (result.type === "success") {
|
|
45267
|
-
values.push(result.value);
|
|
45268
|
-
} else {
|
|
45269
|
-
failureDetails.push(result.detail);
|
|
45270
|
-
}
|
|
45271
|
-
}
|
|
45272
|
-
if (failureDetails.length > 0) {
|
|
45273
|
-
return failure(failureDetails.reduce(combineFailureDetails));
|
|
45274
|
-
}
|
|
45275
|
-
return success(values);
|
|
45276
|
-
}
|
|
45277
|
-
|
|
45278
|
-
class ErrorTrackingParseContext {
|
|
45279
|
-
constructor(path) {
|
|
45280
|
-
this.path = path;
|
|
45281
|
-
}
|
|
45282
|
-
failure(expected, badValue) {
|
|
45283
|
-
return failure([{
|
|
45284
|
-
expected: wrapInArray(expected),
|
|
45285
|
-
badValue,
|
|
45286
|
-
path: this.path
|
|
45287
|
-
}]);
|
|
45288
|
-
}
|
|
45289
|
-
forSubtree(key) {
|
|
45290
|
-
return new ErrorTrackingParseContext([...this.path, key]);
|
|
45291
|
-
}
|
|
45292
|
-
success(value) {
|
|
45293
|
-
return success(value);
|
|
45294
|
-
}
|
|
45295
|
-
}
|
|
45296
|
-
function wrapInArray(a) {
|
|
45297
|
-
return Array.isArray(a) ? a : [a];
|
|
45298
|
-
}
|
|
45299
|
-
|
|
45300
|
-
function formatPath(path) {
|
|
45301
|
-
return "(root)" + path.map(formatPathSegment).join("");
|
|
45302
|
-
}
|
|
45303
|
-
function formatPathSegment(segment) {
|
|
45304
|
-
if (typeof segment === "string") {
|
|
45305
|
-
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
45306
|
-
}
|
|
45307
|
-
return `[${segment.toString()}]`;
|
|
45308
|
-
}
|
|
45309
|
-
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
45310
|
-
|
|
45311
|
-
function message(failure) {
|
|
45312
|
-
const expected = conjoin(failure.expected);
|
|
45313
|
-
const path = formatPath(failure.path);
|
|
45314
|
-
const badValue = JSON.stringify(failure.badValue);
|
|
45315
|
-
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
45316
|
-
}
|
|
45317
|
-
function conjoin(items) {
|
|
45318
|
-
switch (items.length) {
|
|
45319
|
-
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
45320
|
-
case 1:
|
|
45321
|
-
return items[0];
|
|
45322
|
-
case 2:
|
|
45323
|
-
return items.join(" or ");
|
|
45324
|
-
default:
|
|
45325
|
-
{
|
|
45326
|
-
const allButLast = items.slice(0, items.length - 1);
|
|
45327
|
-
const last = items[items.length - 1];
|
|
45328
|
-
return allButLast.join(", ") + ", or " + last;
|
|
45329
|
-
}
|
|
45330
|
-
}
|
|
45331
|
-
}
|
|
45332
|
-
|
|
45333
|
-
function parse(value, parser) {
|
|
45334
|
-
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
45335
|
-
if (isFailure(result)) {
|
|
45336
|
-
return failure(result.detail.map(message).join("; "));
|
|
45337
|
-
}
|
|
45338
|
-
return result;
|
|
45339
|
-
}
|
|
45340
|
-
|
|
45341
|
-
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
45342
|
-
|
|
45343
|
-
function array(elementParser) {
|
|
45344
|
-
return (rawValue, ctx) => {
|
|
45345
|
-
if (!Array.isArray(rawValue)) {
|
|
45346
|
-
return ctx.failure("array", rawValue);
|
|
45347
|
-
}
|
|
45348
|
-
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
45349
|
-
return all(elementResults, concat);
|
|
45350
|
-
};
|
|
45351
|
-
}
|
|
45352
|
-
function concat(a, b) {
|
|
45353
|
-
return [...a, ...b];
|
|
45354
|
-
}
|
|
45355
|
-
|
|
45356
|
-
function boolean(rawValue, ctx) {
|
|
45357
|
-
if (typeof rawValue === "boolean") {
|
|
45358
|
-
return ctx.success(rawValue);
|
|
45359
|
-
}
|
|
45360
|
-
return ctx.failure("boolean", rawValue);
|
|
45361
|
-
}
|
|
45362
|
-
|
|
45363
|
-
function constant(acceptedValue) {
|
|
45364
|
-
return (rawValue, ctx) => {
|
|
45365
|
-
if (rawValue !== acceptedValue) {
|
|
45366
|
-
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
45367
|
-
}
|
|
45368
|
-
return ctx.success(acceptedValue);
|
|
45369
|
-
};
|
|
45370
|
-
}
|
|
45371
|
-
|
|
45372
|
-
function enumeration() {
|
|
45373
|
-
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
45374
|
-
acceptedValues[_key] = arguments[_key];
|
|
45375
|
-
}
|
|
45376
|
-
return (rawValue, ctx) => {
|
|
45377
|
-
if (typeof rawValue === "string") {
|
|
45378
|
-
const index = acceptedValues.indexOf(rawValue);
|
|
45379
|
-
if (index > -1) {
|
|
45380
|
-
return ctx.success(acceptedValues[index]);
|
|
45381
|
-
}
|
|
45382
|
-
}
|
|
45383
|
-
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
45384
|
-
return ctx.failure(expected, rawValue);
|
|
45385
|
-
};
|
|
45386
|
-
}
|
|
45387
|
-
|
|
45388
|
-
function isObject(x) {
|
|
45389
|
-
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
45390
|
-
}
|
|
45391
|
-
|
|
45392
|
-
function nullable(parseValue) {
|
|
45393
|
-
return (rawValue, ctx) => {
|
|
45394
|
-
if (rawValue === null) {
|
|
45395
|
-
return ctx.success(rawValue);
|
|
45396
|
-
}
|
|
45397
|
-
return parseValue(rawValue, ctx);
|
|
45398
|
-
};
|
|
45399
|
-
}
|
|
45400
|
-
|
|
45401
|
-
const number = (rawValue, ctx) => {
|
|
45402
|
-
if (typeof rawValue === "number") {
|
|
45403
|
-
return ctx.success(rawValue);
|
|
45404
|
-
}
|
|
45405
|
-
return ctx.failure("number", rawValue);
|
|
45406
|
-
};
|
|
45407
|
-
|
|
45408
|
-
function object(schema) {
|
|
45409
|
-
return (rawValue, ctx) => {
|
|
45410
|
-
if (!isObject(rawValue)) {
|
|
45411
|
-
return ctx.failure("object", rawValue);
|
|
45412
|
-
}
|
|
45413
|
-
const ret = {
|
|
45414
|
-
...rawValue
|
|
45415
|
-
};
|
|
45416
|
-
const mismatches = [];
|
|
45417
|
-
for (const [prop, propParser] of Object.entries(schema)) {
|
|
45418
|
-
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
45419
|
-
if (isSuccess(result)) {
|
|
45420
|
-
if (result.value !== undefined || prop in rawValue) {
|
|
45421
|
-
ret[prop] = result.value;
|
|
45422
|
-
}
|
|
45423
|
-
} else {
|
|
45424
|
-
mismatches.push(...result.detail);
|
|
45425
|
-
}
|
|
45426
|
-
}
|
|
45427
|
-
if (mismatches.length > 0) {
|
|
45428
|
-
return failure(mismatches);
|
|
45429
|
-
}
|
|
45430
|
-
return ctx.success(ret);
|
|
45431
|
-
};
|
|
45432
|
-
}
|
|
45433
|
-
|
|
45434
|
-
function optional(parseValue) {
|
|
45435
|
-
return (rawValue, ctx) => {
|
|
45436
|
-
if (rawValue === undefined) {
|
|
45437
|
-
return ctx.success(rawValue);
|
|
45438
|
-
}
|
|
45439
|
-
return parseValue(rawValue, ctx);
|
|
45440
|
-
};
|
|
45441
|
-
}
|
|
45442
|
-
|
|
45443
|
-
function pair(parseA, parseB) {
|
|
45444
|
-
return (rawValue, ctx) => {
|
|
45445
|
-
if (!Array.isArray(rawValue)) {
|
|
45446
|
-
return ctx.failure("array", rawValue);
|
|
45447
|
-
}
|
|
45448
|
-
if (rawValue.length !== 2) {
|
|
45449
|
-
return ctx.failure("array of length 2", rawValue);
|
|
45450
|
-
}
|
|
45451
|
-
const [rawA, rawB] = rawValue;
|
|
45452
|
-
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
45453
|
-
if (isFailure(resultA)) {
|
|
45454
|
-
return resultA;
|
|
45455
|
-
}
|
|
45456
|
-
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
45457
|
-
if (isFailure(resultB)) {
|
|
45458
|
-
return resultB;
|
|
45459
|
-
}
|
|
45460
|
-
return ctx.success([resultA.value, resultB.value]);
|
|
45461
|
-
};
|
|
45462
|
-
}
|
|
45463
|
-
|
|
45464
|
-
function pipeParsers(p) {
|
|
45465
|
-
return new ParserPipeline(p);
|
|
45466
|
-
}
|
|
45467
|
-
class ParserPipeline {
|
|
45468
|
-
constructor(parser) {
|
|
45469
|
-
this.parser = parser;
|
|
45470
|
-
}
|
|
45471
|
-
then(nextParser) {
|
|
45472
|
-
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
45473
|
-
}
|
|
45474
|
-
}
|
|
45475
|
-
function composeParsers(parserA, parserB) {
|
|
45476
|
-
return (rawValue, ctx) => {
|
|
45477
|
-
const partialResult = parserA(rawValue, ctx);
|
|
45478
|
-
if (isFailure(partialResult)) {
|
|
45479
|
-
return partialResult;
|
|
45480
|
-
}
|
|
45481
|
-
return parserB(partialResult.value, ctx);
|
|
45482
|
-
};
|
|
45483
|
-
}
|
|
45484
|
-
|
|
45485
|
-
function record(parseKey, parseValue) {
|
|
45486
|
-
return (rawValue, ctx) => {
|
|
45487
|
-
if (!isObject(rawValue)) {
|
|
45488
|
-
return ctx.failure("object", rawValue);
|
|
45489
|
-
}
|
|
45490
|
-
const result = {};
|
|
45491
|
-
const mismatches = [];
|
|
45492
|
-
for (const [key, value] of Object.entries(rawValue)) {
|
|
45493
|
-
const entryCtx = ctx.forSubtree(key);
|
|
45494
|
-
const keyResult = parseKey(key, entryCtx);
|
|
45495
|
-
if (isFailure(keyResult)) {
|
|
45496
|
-
mismatches.push(...keyResult.detail);
|
|
45497
|
-
}
|
|
45498
|
-
const valueResult = parseValue(value, entryCtx);
|
|
45499
|
-
if (isFailure(valueResult)) {
|
|
45500
|
-
mismatches.push(...valueResult.detail);
|
|
45501
|
-
}
|
|
45502
|
-
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
45503
|
-
result[keyResult.value] = valueResult.value;
|
|
45504
|
-
}
|
|
45505
|
-
}
|
|
45506
|
-
if (mismatches.length > 0) {
|
|
45507
|
-
return failure(mismatches);
|
|
45508
|
-
}
|
|
45509
|
-
return ctx.success(result);
|
|
45510
|
-
};
|
|
45511
|
-
}
|
|
45512
|
-
|
|
45513
|
-
const string = (rawValue, ctx) => {
|
|
45514
|
-
if (typeof rawValue === "string") {
|
|
45515
|
-
return ctx.success(rawValue);
|
|
45516
|
-
}
|
|
45517
|
-
return ctx.failure("string", rawValue);
|
|
45518
|
-
};
|
|
45519
|
-
|
|
45520
|
-
function trio(parseA, parseB, parseC) {
|
|
45521
|
-
return (rawValue, ctx) => {
|
|
45522
|
-
if (!Array.isArray(rawValue)) {
|
|
45523
|
-
return ctx.failure("array", rawValue);
|
|
45524
|
-
}
|
|
45525
|
-
if (rawValue.length !== 3) {
|
|
45526
|
-
return ctx.failure("array of length 3", rawValue);
|
|
45527
|
-
}
|
|
45528
|
-
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
45529
|
-
if (isFailure(resultA)) {
|
|
45530
|
-
return resultA;
|
|
45531
|
-
}
|
|
45532
|
-
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
45533
|
-
if (isFailure(resultB)) {
|
|
45534
|
-
return resultB;
|
|
45535
|
-
}
|
|
45536
|
-
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
45537
|
-
if (isFailure(resultC)) {
|
|
45538
|
-
return resultC;
|
|
45539
|
-
}
|
|
45540
|
-
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
45541
|
-
};
|
|
45542
|
-
}
|
|
45543
|
-
|
|
45544
|
-
function union(parseBranch) {
|
|
45545
|
-
return new UnionBuilder(parseBranch);
|
|
45546
|
-
}
|
|
45547
|
-
class UnionBuilder {
|
|
45548
|
-
constructor(parser) {
|
|
45549
|
-
this.parser = parser;
|
|
45550
|
-
}
|
|
45551
|
-
or(newBranch) {
|
|
45552
|
-
return new UnionBuilder(either(this.parser, newBranch));
|
|
45553
|
-
}
|
|
45554
|
-
}
|
|
45555
|
-
function either(parseA, parseB) {
|
|
45556
|
-
return (rawValue, ctx) => {
|
|
45557
|
-
const resultA = parseA(rawValue, ctx);
|
|
45558
|
-
if (isSuccess(resultA)) {
|
|
45559
|
-
return resultA;
|
|
45560
|
-
}
|
|
45561
|
-
return parseB(rawValue, ctx);
|
|
45562
|
-
};
|
|
45563
|
-
}
|
|
45564
|
-
|
|
45565
|
-
function defaulted(parser, fallback) {
|
|
45566
|
-
return (rawValue, ctx) => {
|
|
45567
|
-
if (rawValue == null) {
|
|
45568
|
-
return success(fallback(rawValue));
|
|
45569
|
-
}
|
|
45570
|
-
return parser(rawValue, ctx);
|
|
45571
|
-
};
|
|
45572
|
-
}
|
|
45573
|
-
|
|
45574
|
-
const parseImages = defaulted(record(string, object({
|
|
45575
|
-
width: number,
|
|
45576
|
-
height: number
|
|
45577
|
-
})), () => ({}));
|
|
45578
|
-
|
|
45579
|
-
function parseWidget(parseType, parseOptions) {
|
|
45580
|
-
return object({
|
|
45581
|
-
type: parseType,
|
|
45582
|
-
static: optional(boolean),
|
|
45583
|
-
graded: optional(boolean),
|
|
45584
|
-
alignment: optional(string),
|
|
45585
|
-
options: parseOptions,
|
|
45586
|
-
key: optional(number),
|
|
45587
|
-
version: optional(object({
|
|
45588
|
-
major: number,
|
|
45589
|
-
minor: number
|
|
45590
|
-
}))
|
|
45591
|
-
});
|
|
45592
|
-
}
|
|
45593
|
-
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
45594
|
-
return object({
|
|
45595
|
-
type: parseType,
|
|
45596
|
-
static: optional(boolean),
|
|
45597
|
-
graded: optional(boolean),
|
|
45598
|
-
alignment: optional(string),
|
|
45599
|
-
options: parseOptions,
|
|
45600
|
-
key: optional(number),
|
|
45601
|
-
version: parseVersion
|
|
45602
|
-
});
|
|
45603
|
-
}
|
|
45604
|
-
|
|
45605
|
-
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
45606
|
-
items: array(string),
|
|
45607
|
-
categories: array(string),
|
|
45608
|
-
randomizeItems: defaulted(boolean, () => false),
|
|
45609
|
-
static: defaulted(boolean, () => false),
|
|
45610
|
-
values: array(defaulted(number, () => 0)),
|
|
45611
|
-
highlightLint: optional(boolean),
|
|
45612
|
-
linterContext: optional(object({
|
|
45613
|
-
contentType: string,
|
|
45614
|
-
paths: array(string),
|
|
45615
|
-
stack: array(string)
|
|
45616
|
-
}))
|
|
45617
|
-
}));
|
|
45618
|
-
|
|
45619
|
-
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
45620
|
-
programID: string,
|
|
45621
|
-
programType: any,
|
|
45622
|
-
settings: array(object({
|
|
45623
|
-
name: string,
|
|
45624
|
-
value: string
|
|
45625
|
-
})),
|
|
45626
|
-
showEditor: boolean,
|
|
45627
|
-
showButtons: boolean,
|
|
45628
|
-
height: number,
|
|
45629
|
-
static: defaulted(boolean, () => false)
|
|
45630
|
-
}));
|
|
45631
|
-
|
|
45632
|
-
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
45633
|
-
togglePrompt: string,
|
|
45634
|
-
definition: string,
|
|
45635
|
-
static: defaulted(boolean, () => false)
|
|
45636
|
-
}));
|
|
45637
|
-
|
|
45638
|
-
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
45639
|
-
placeholder: defaulted(string, () => ""),
|
|
45640
|
-
ariaLabel: optional(string),
|
|
45641
|
-
visibleLabel: optional(string),
|
|
45642
|
-
static: defaulted(boolean, () => false),
|
|
45643
|
-
choices: array(object({
|
|
45644
|
-
content: string,
|
|
45645
|
-
correct: boolean
|
|
45646
|
-
}))
|
|
45647
|
-
}));
|
|
45648
|
-
|
|
45649
|
-
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
45650
|
-
showPrompt: string,
|
|
45651
|
-
hidePrompt: string,
|
|
45652
|
-
explanation: string,
|
|
45653
|
-
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
45654
|
-
// referenced before it is defined. There is an import cycle between
|
|
45655
|
-
// this file and widgets-map.ts that could cause it to be undefined.
|
|
45656
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
45657
|
-
static: defaulted(boolean, () => false)
|
|
45658
|
-
}));
|
|
45659
|
-
|
|
45660
|
-
// Given a function, creates a PartialParser that converts one type to another
|
|
45661
|
-
// using that function. The returned parser never fails.
|
|
45662
|
-
function convert(f) {
|
|
45663
|
-
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
45664
|
-
}
|
|
45665
|
-
|
|
45666
|
-
/**
|
|
45667
|
-
* Creates a parser for a widget options type with multiple major versions. Old
|
|
45668
|
-
* versions are migrated to the latest version. The parse fails if the input
|
|
45669
|
-
* data does not match any of the versions.
|
|
45670
|
-
*
|
|
45671
|
-
* @example
|
|
45672
|
-
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
45673
|
-
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
45674
|
-
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
45675
|
-
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
45676
|
-
* .parser;
|
|
45677
|
-
*
|
|
45678
|
-
* @param latestMajorVersion the latest major version of the widget options.
|
|
45679
|
-
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
45680
|
-
* options.
|
|
45681
|
-
* @returns a builder object, to which migrations from earlier versions can be
|
|
45682
|
-
* added. Migrations must be added in "reverse chronological" order as in the
|
|
45683
|
-
* example above.
|
|
45684
|
-
*/
|
|
45685
|
-
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
45686
|
-
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
45687
|
-
}
|
|
45688
|
-
class VersionedWidgetOptionsParserBuilder {
|
|
45689
|
-
parser;
|
|
45690
|
-
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
45691
|
-
this.migrateToLatest = migrateToLatest;
|
|
45692
|
-
this.parseOtherVersions = parseOtherVersions;
|
|
45693
|
-
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
45694
|
-
this.parser = (raw, ctx) => {
|
|
45695
|
-
if (!isObject(raw)) {
|
|
45696
|
-
return ctx.failure("object", raw);
|
|
45697
|
-
}
|
|
45698
|
-
const versionParseResult = parseVersionedObject(raw, ctx);
|
|
45699
|
-
if (isFailure(versionParseResult)) {
|
|
45700
|
-
return versionParseResult;
|
|
45701
|
-
}
|
|
45702
|
-
if (versionParseResult.value.version.major !== majorVersion) {
|
|
45703
|
-
return this.parseOtherVersions(raw, ctx);
|
|
45704
|
-
}
|
|
45705
|
-
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
45706
|
-
};
|
|
45707
|
-
}
|
|
45708
|
-
|
|
45709
|
-
/**
|
|
45710
|
-
* Add a migration from an old version of the widget options.
|
|
45711
|
-
*/
|
|
45712
|
-
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
45713
|
-
const parseOtherVersions = this.parser;
|
|
45714
|
-
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
45715
|
-
return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
|
|
45716
|
-
}
|
|
45717
|
-
}
|
|
45718
|
-
const parseVersionedObject = object({
|
|
45719
|
-
version: defaulted(object({
|
|
45720
|
-
major: number,
|
|
45721
|
-
minor: number
|
|
45722
|
-
}), () => ({
|
|
45723
|
-
major: 0,
|
|
45724
|
-
minor: 0
|
|
45725
|
-
}))
|
|
45726
|
-
});
|
|
45727
|
-
|
|
45728
|
-
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
45729
|
-
const parsePossiblyInvalidAnswerForm = object({
|
|
45730
|
-
// `value` is the possibly invalid part of this. It should always be a
|
|
45731
|
-
// string, but some answer forms don't have it. The Expression widget
|
|
45732
|
-
// ignores invalid values, so we can safely filter them out during parsing.
|
|
45733
|
-
value: optional(string),
|
|
45734
|
-
form: defaulted(boolean, () => false),
|
|
45735
|
-
simplify: defaulted(boolean, () => false),
|
|
45736
|
-
considered: enumeration("correct", "wrong", "ungraded"),
|
|
45737
|
-
key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
|
|
45738
|
-
});
|
|
45739
|
-
function removeInvalidAnswerForms(possiblyInvalid) {
|
|
45740
|
-
const valid = [];
|
|
45741
|
-
for (const answerForm of possiblyInvalid) {
|
|
45742
|
-
const {
|
|
45743
|
-
value
|
|
45744
|
-
} = answerForm;
|
|
45745
|
-
if (value != null) {
|
|
45746
|
-
// Copying the object seems to be needed to make TypeScript happy
|
|
45747
|
-
valid.push({
|
|
45748
|
-
...answerForm,
|
|
45749
|
-
value
|
|
45750
|
-
});
|
|
45751
|
-
}
|
|
45752
|
-
}
|
|
45753
|
-
return valid;
|
|
45754
|
-
}
|
|
45755
|
-
|
|
45756
|
-
// NOTE(benchristel): I copied the default buttonSets from
|
|
45757
|
-
// expression.tsx. See the parse-perseus-json/README.md for
|
|
45758
|
-
// an explanation of why we want to duplicate the default here.
|
|
45759
|
-
const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
45760
|
-
const version1 = object({
|
|
45761
|
-
major: constant(1),
|
|
45762
|
-
minor: number
|
|
45763
|
-
});
|
|
45764
|
-
const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
|
|
45765
|
-
answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
|
|
45766
|
-
functions: array(string),
|
|
45767
|
-
times: boolean,
|
|
45768
|
-
visibleLabel: optional(string),
|
|
45769
|
-
ariaLabel: optional(string),
|
|
45770
|
-
buttonSets: parseButtonSets,
|
|
45771
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45772
|
-
}));
|
|
45773
|
-
const version0 = optional(object({
|
|
45774
|
-
major: constant(0),
|
|
45775
|
-
minor: number
|
|
45776
|
-
}));
|
|
45777
|
-
const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
|
|
45778
|
-
functions: array(string),
|
|
45779
|
-
times: boolean,
|
|
45780
|
-
visibleLabel: optional(string),
|
|
45781
|
-
ariaLabel: optional(string),
|
|
45782
|
-
form: boolean,
|
|
45783
|
-
simplify: boolean,
|
|
45784
|
-
value: string,
|
|
45785
|
-
buttonSets: parseButtonSets,
|
|
45786
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45787
|
-
}));
|
|
45788
|
-
function migrateV0ToV1(widget) {
|
|
45789
|
-
const {
|
|
45790
|
-
options
|
|
45791
|
-
} = widget;
|
|
45792
|
-
return {
|
|
45793
|
-
...widget,
|
|
45794
|
-
version: {
|
|
45795
|
-
major: 1,
|
|
45796
|
-
minor: 0
|
|
45797
|
-
},
|
|
45798
|
-
options: {
|
|
45799
|
-
times: options.times,
|
|
45800
|
-
buttonSets: options.buttonSets,
|
|
45801
|
-
functions: options.functions,
|
|
45802
|
-
buttonsVisible: options.buttonsVisible,
|
|
45803
|
-
visibleLabel: options.visibleLabel,
|
|
45804
|
-
ariaLabel: options.ariaLabel,
|
|
45805
|
-
answerForms: [{
|
|
45806
|
-
considered: "correct",
|
|
45807
|
-
form: options.form,
|
|
45808
|
-
simplify: options.simplify,
|
|
45809
|
-
value: options.value
|
|
45810
|
-
}]
|
|
45811
|
-
}
|
|
45812
|
-
};
|
|
45813
|
-
}
|
|
45814
|
-
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
45815
|
-
|
|
45816
|
-
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
45817
|
-
const parseGradedGroupWidgetOptions = object({
|
|
45818
|
-
title: defaulted(string, () => ""),
|
|
45819
|
-
hasHint: optional(nullable(boolean)),
|
|
45820
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45821
|
-
// The anonymous function below ensures that we don't try to access
|
|
45822
|
-
// parsePerseusRenderer before it's defined.
|
|
45823
|
-
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
45824
|
-
content: string,
|
|
45825
|
-
// This module has an import cycle with parseWidgetsMap.
|
|
45826
|
-
// The anonymous function below ensures that we don't try to access
|
|
45827
|
-
// parseWidgetsMap before it's defined.
|
|
45828
|
-
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
45829
|
-
widgetEnabled: optional(nullable(boolean)),
|
|
45830
|
-
immutableWidgets: optional(nullable(boolean)),
|
|
45831
|
-
images: record(string, object({
|
|
45832
|
-
width: number,
|
|
45833
|
-
height: number
|
|
45834
|
-
}))
|
|
45835
|
-
});
|
|
45836
|
-
const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
|
|
45837
|
-
|
|
45838
|
-
const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
|
|
45839
|
-
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
45840
|
-
}));
|
|
45841
|
-
|
|
45842
|
-
/**
|
|
45843
|
-
* discriminatedUnion() should be preferred over union() when parsing a
|
|
45844
|
-
* discriminated union type, because discriminatedUnion() produces more
|
|
45845
|
-
* understandable failure messages. It takes the discriminant as the source of
|
|
45846
|
-
* truth for which variant is to be parsed, and expects the other data to match
|
|
45847
|
-
* that variant.
|
|
45848
|
-
*/
|
|
45849
|
-
function discriminatedUnionOn(discriminantKey) {
|
|
45850
|
-
const noMoreBranches = (raw, ctx) => {
|
|
45851
|
-
if (!isObject(raw)) {
|
|
45852
|
-
return ctx.failure("object", raw);
|
|
45853
|
-
}
|
|
45854
|
-
return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
|
|
45855
|
-
};
|
|
45856
|
-
return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
|
|
45857
|
-
}
|
|
45858
|
-
class DiscriminatedUnionBuilder {
|
|
45859
|
-
constructor(discriminantKey, parser) {
|
|
45860
|
-
this.discriminantKey = discriminantKey;
|
|
45861
|
-
this.parser = parser;
|
|
45862
|
-
}
|
|
45863
|
-
withBranch(discriminantValue, parseNewVariant) {
|
|
45864
|
-
const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
|
|
45865
|
-
return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
|
|
45866
|
-
}
|
|
45867
|
-
}
|
|
45868
|
-
function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
|
|
45869
|
-
return (raw, ctx) => {
|
|
45870
|
-
if (!isObject(raw)) {
|
|
45871
|
-
return ctx.failure("object", raw);
|
|
45872
|
-
}
|
|
45873
|
-
if (raw[discriminantKey] === discriminantValue) {
|
|
45874
|
-
return parseVariant(raw, ctx);
|
|
45875
|
-
}
|
|
45876
|
-
return parseOtherBranches(raw, ctx);
|
|
45877
|
-
};
|
|
45878
|
-
}
|
|
45879
|
-
|
|
45880
|
-
const pairOfNumbers$3 = pair(number, number);
|
|
45881
|
-
const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
|
|
45882
|
-
const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
45883
|
-
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
45884
|
-
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
45885
|
-
type: constant("absolute_value"),
|
|
45886
|
-
coords: nullable(pairOfPoints)
|
|
45887
|
-
})).withBranch("exponential", object({
|
|
45888
|
-
type: constant("exponential"),
|
|
45889
|
-
asymptote: pairOfPoints,
|
|
45890
|
-
coords: nullable(pairOfPoints)
|
|
45891
|
-
})).withBranch("linear", object({
|
|
45892
|
-
type: constant("linear"),
|
|
45893
|
-
coords: nullable(pairOfPoints)
|
|
45894
|
-
})).withBranch("logarithm", object({
|
|
45895
|
-
type: constant("logarithm"),
|
|
45896
|
-
asymptote: pairOfPoints,
|
|
45897
|
-
coords: nullable(pairOfPoints)
|
|
45898
|
-
})).withBranch("quadratic", object({
|
|
45899
|
-
type: constant("quadratic"),
|
|
45900
|
-
coords: nullable(pairOfPoints)
|
|
45901
|
-
})).withBranch("sinusoid", object({
|
|
45902
|
-
type: constant("sinusoid"),
|
|
45903
|
-
coords: nullable(pairOfPoints)
|
|
45904
|
-
})).withBranch("tangent", object({
|
|
45905
|
-
type: constant("tangent"),
|
|
45906
|
-
coords: nullable(pairOfPoints)
|
|
45907
|
-
})).parser,
|
|
45908
|
-
graph: object({
|
|
45909
|
-
backgroundImage: object({
|
|
45910
|
-
bottom: optional(number),
|
|
45911
|
-
height: optional(number),
|
|
45912
|
-
left: optional(number),
|
|
45913
|
-
scale: optional(number),
|
|
45914
|
-
url: optional(nullable(string)),
|
|
45915
|
-
width: optional(number)
|
|
45916
|
-
}),
|
|
45917
|
-
box: optional(pairOfNumbers$3),
|
|
45918
|
-
editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
|
|
45919
|
-
gridStep: optional(pairOfNumbers$3),
|
|
45920
|
-
labels: pair(string, string),
|
|
45921
|
-
markings: enumeration("graph", "none", "grid"),
|
|
45922
|
-
range: pair(pairOfNumbers$3, pairOfNumbers$3),
|
|
45923
|
-
rulerLabel: constant(""),
|
|
45924
|
-
rulerTicks: number,
|
|
45925
|
-
showProtractor: optional(boolean),
|
|
45926
|
-
showRuler: optional(boolean),
|
|
45927
|
-
showTooltips: optional(boolean),
|
|
45928
|
-
snapStep: optional(pairOfNumbers$3),
|
|
45929
|
-
step: pairOfNumbers$3,
|
|
45930
|
-
valid: optional(union(boolean).or(string).parser)
|
|
45931
|
-
})
|
|
45932
|
-
}));
|
|
45933
|
-
|
|
45934
|
-
const parseGroupWidget = parseWidget(constant("group"),
|
|
45935
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45936
|
-
// The anonymous function below ensures that we don't try to access
|
|
45937
|
-
// parsePerseusRenderer before it's defined.
|
|
45938
|
-
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
45939
|
-
|
|
45940
|
-
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
45941
|
-
url: string,
|
|
45942
|
-
settings: optional(array(object({
|
|
45943
|
-
name: string,
|
|
45944
|
-
value: string
|
|
45945
|
-
}))),
|
|
45946
|
-
width: union(number).or(string).parser,
|
|
45947
|
-
height: union(number).or(string).parser,
|
|
45948
|
-
allowFullScreen: defaulted(boolean, () => false),
|
|
45949
|
-
allowTopNavigation: optional(boolean),
|
|
45950
|
-
static: defaulted(boolean, () => false)
|
|
45951
|
-
}));
|
|
45952
|
-
|
|
45953
|
-
const stringToNumber = (rawValue, ctx) => {
|
|
45954
|
-
if (typeof rawValue === "number") {
|
|
45955
|
-
return ctx.success(rawValue);
|
|
45956
|
-
}
|
|
45957
|
-
const parsedNumber = +rawValue;
|
|
45958
|
-
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
45959
|
-
return ctx.failure("a number or numeric string", rawValue);
|
|
45960
|
-
}
|
|
45961
|
-
return ctx.success(parsedNumber);
|
|
45962
|
-
};
|
|
45963
|
-
|
|
45964
|
-
function emptyToZero(x) {
|
|
45965
|
-
return x === "" ? 0 : x;
|
|
45966
|
-
}
|
|
45967
|
-
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
45968
|
-
// In this specific case, empty string is equivalent to zero. An empty
|
|
45969
|
-
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
45970
|
-
// CSS will treat NaN as invalid and default to 0 instead.
|
|
45971
|
-
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
45972
|
-
const parsePerseusImageBackground = object({
|
|
45973
|
-
url: optional(nullable(string)),
|
|
45974
|
-
width: optional(imageDimensionToNumber),
|
|
45975
|
-
height: optional(imageDimensionToNumber),
|
|
45976
|
-
top: optional(imageDimensionToNumber),
|
|
45977
|
-
left: optional(imageDimensionToNumber),
|
|
45978
|
-
bottom: optional(imageDimensionToNumber),
|
|
45979
|
-
scale: optional(imageDimensionToNumber)
|
|
45980
|
-
});
|
|
45981
|
-
|
|
45982
|
-
const pairOfNumbers$2 = pair(number, number);
|
|
45983
|
-
const parseImageWidget = parseWidget(constant("image"), object({
|
|
45984
|
-
title: optional(string),
|
|
45985
|
-
caption: optional(string),
|
|
45986
|
-
alt: optional(string),
|
|
45987
|
-
backgroundImage: parsePerseusImageBackground,
|
|
45988
|
-
static: optional(boolean),
|
|
45989
|
-
labels: optional(array(object({
|
|
45990
|
-
content: string,
|
|
45991
|
-
alignment: string,
|
|
45992
|
-
coordinates: array(number)
|
|
45993
|
-
}))),
|
|
45994
|
-
range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
|
|
45995
|
-
box: optional(pairOfNumbers$2)
|
|
45996
|
-
}));
|
|
45997
|
-
|
|
45998
|
-
const booleanToString = (rawValue, ctx) => {
|
|
45999
|
-
if (typeof rawValue === "boolean") {
|
|
46000
|
-
return ctx.success(String(rawValue));
|
|
46001
|
-
}
|
|
46002
|
-
return ctx.failure("boolean", rawValue);
|
|
46003
|
-
};
|
|
46004
|
-
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
46005
|
-
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
46006
|
-
inexact: optional(boolean),
|
|
46007
|
-
maxError: optional(union(number).or(string).parser),
|
|
46008
|
-
rightAlign: optional(boolean),
|
|
46009
|
-
simplify: enumeration("required", "optional", "enforced"),
|
|
46010
|
-
size: enumeration("normal", "small"),
|
|
46011
|
-
// TODO(benchristel): there are some content items where value is a
|
|
46012
|
-
// boolean, even though that makes no sense. We should figure out if
|
|
46013
|
-
// those content items are actually published anywhere, and consider
|
|
46014
|
-
// updating them.
|
|
46015
|
-
value: union(number).or(string).or(booleanToString).parser,
|
|
46016
|
-
customKeypad: optional(boolean)
|
|
46017
|
-
}));
|
|
46018
|
-
|
|
46019
|
-
const pairOfNumbers$1 = pair(number, number);
|
|
46020
|
-
const stringOrEmpty = defaulted(string, () => "");
|
|
46021
|
-
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
46022
|
-
const parseFunctionElement = object({
|
|
46023
|
-
type: constant("function"),
|
|
46024
|
-
key: parseKey,
|
|
46025
|
-
options: object({
|
|
46026
|
-
value: string,
|
|
46027
|
-
funcName: string,
|
|
46028
|
-
rangeMin: string,
|
|
46029
|
-
rangeMax: string,
|
|
46030
|
-
color: string,
|
|
46031
|
-
strokeDasharray: string,
|
|
46032
|
-
strokeWidth: number
|
|
46033
|
-
})
|
|
46034
|
-
});
|
|
46035
|
-
const parseLabelElement = object({
|
|
46036
|
-
type: constant("label"),
|
|
46037
|
-
key: parseKey,
|
|
46038
|
-
options: object({
|
|
46039
|
-
label: string,
|
|
46040
|
-
color: string,
|
|
46041
|
-
coordX: string,
|
|
46042
|
-
coordY: string
|
|
46043
|
-
})
|
|
46044
|
-
});
|
|
46045
|
-
const parseLineElement = object({
|
|
46046
|
-
type: constant("line"),
|
|
46047
|
-
key: parseKey,
|
|
46048
|
-
options: object({
|
|
46049
|
-
color: string,
|
|
46050
|
-
startX: string,
|
|
46051
|
-
startY: string,
|
|
46052
|
-
endX: string,
|
|
46053
|
-
endY: string,
|
|
46054
|
-
strokeDasharray: string,
|
|
46055
|
-
strokeWidth: number,
|
|
46056
|
-
arrows: string
|
|
46057
|
-
})
|
|
46058
|
-
});
|
|
46059
|
-
const parseMovableLineElement = object({
|
|
46060
|
-
type: constant("movable-line"),
|
|
46061
|
-
key: parseKey,
|
|
46062
|
-
options: object({
|
|
46063
|
-
startX: string,
|
|
46064
|
-
startY: string,
|
|
46065
|
-
startSubscript: number,
|
|
46066
|
-
endX: string,
|
|
46067
|
-
endY: string,
|
|
46068
|
-
endSubscript: number,
|
|
46069
|
-
constraint: string,
|
|
46070
|
-
snap: number,
|
|
46071
|
-
constraintFn: string,
|
|
46072
|
-
constraintXMin: string,
|
|
46073
|
-
constraintXMax: string,
|
|
46074
|
-
constraintYMin: string,
|
|
46075
|
-
constraintYMax: string
|
|
46076
|
-
})
|
|
46077
|
-
});
|
|
46078
|
-
const parseMovablePointElement = object({
|
|
46079
|
-
type: constant("movable-point"),
|
|
46080
|
-
key: parseKey,
|
|
46081
|
-
options: object({
|
|
46082
|
-
startX: string,
|
|
46083
|
-
startY: string,
|
|
46084
|
-
varSubscript: number,
|
|
46085
|
-
constraint: string,
|
|
46086
|
-
snap: number,
|
|
46087
|
-
constraintFn: string,
|
|
46088
|
-
constraintXMin: stringOrEmpty,
|
|
46089
|
-
constraintXMax: stringOrEmpty,
|
|
46090
|
-
constraintYMin: stringOrEmpty,
|
|
46091
|
-
constraintYMax: stringOrEmpty
|
|
46092
|
-
})
|
|
46093
|
-
});
|
|
46094
|
-
const parseParametricElement = object({
|
|
46095
|
-
type: constant("parametric"),
|
|
46096
|
-
key: parseKey,
|
|
46097
|
-
options: object({
|
|
46098
|
-
x: string,
|
|
46099
|
-
y: string,
|
|
46100
|
-
rangeMin: string,
|
|
46101
|
-
rangeMax: string,
|
|
46102
|
-
color: string,
|
|
46103
|
-
strokeDasharray: string,
|
|
46104
|
-
strokeWidth: number
|
|
46105
|
-
})
|
|
46106
|
-
});
|
|
46107
|
-
const parsePointElement = object({
|
|
46108
|
-
type: constant("point"),
|
|
46109
|
-
key: parseKey,
|
|
46110
|
-
options: object({
|
|
46111
|
-
color: string,
|
|
46112
|
-
coordX: string,
|
|
46113
|
-
coordY: string
|
|
46114
|
-
})
|
|
46115
|
-
});
|
|
46116
|
-
const parseRectangleElement = object({
|
|
46117
|
-
type: constant("rectangle"),
|
|
46118
|
-
key: parseKey,
|
|
46119
|
-
options: object({
|
|
46120
|
-
color: string,
|
|
46121
|
-
coordX: string,
|
|
46122
|
-
coordY: string,
|
|
46123
|
-
width: string,
|
|
46124
|
-
height: string
|
|
46125
|
-
})
|
|
46126
|
-
});
|
|
46127
|
-
const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
46128
|
-
static: defaulted(boolean, () => false),
|
|
46129
|
-
graph: object({
|
|
46130
|
-
editableSettings: optional(array(enumeration("canvas", "graph"))),
|
|
46131
|
-
box: pairOfNumbers$1,
|
|
46132
|
-
labels: array(string),
|
|
46133
|
-
range: pair(pairOfNumbers$1, pairOfNumbers$1),
|
|
46134
|
-
gridStep: pairOfNumbers$1,
|
|
46135
|
-
markings: enumeration("graph", "grid", "none"),
|
|
46136
|
-
snapStep: optional(pairOfNumbers$1),
|
|
46137
|
-
valid: optional(union(boolean).or(string).parser),
|
|
46138
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
46139
|
-
showProtractor: optional(boolean),
|
|
46140
|
-
showRuler: optional(boolean),
|
|
46141
|
-
rulerLabel: optional(string),
|
|
46142
|
-
rulerTicks: optional(number),
|
|
46143
|
-
tickStep: pairOfNumbers$1
|
|
46144
|
-
}),
|
|
46145
|
-
elements: array(discriminatedUnionOn("type").withBranch("function", parseFunctionElement).withBranch("label", parseLabelElement).withBranch("line", parseLineElement).withBranch("movable-line", parseMovableLineElement).withBranch("movable-point", parseMovablePointElement).withBranch("parametric", parseParametricElement).withBranch("point", parsePointElement).withBranch("rectangle", parseRectangleElement).parser)
|
|
46146
|
-
}));
|
|
46147
|
-
|
|
46148
|
-
// Used to represent 2-D points and ranges
|
|
46149
|
-
const pairOfNumbers = pair(number, number);
|
|
46150
|
-
const parsePerseusGraphTypeAngle = object({
|
|
46151
|
-
type: constant("angle"),
|
|
46152
|
-
showAngles: optional(boolean),
|
|
46153
|
-
allowReflexAngles: optional(boolean),
|
|
46154
|
-
angleOffsetDeg: optional(number),
|
|
46155
|
-
snapDegrees: optional(number),
|
|
46156
|
-
match: optional(constant("congruent")),
|
|
46157
|
-
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
46158
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
46159
|
-
});
|
|
46160
|
-
const parsePerseusGraphTypeCircle = object({
|
|
46161
|
-
type: constant("circle"),
|
|
46162
|
-
center: optional(pairOfNumbers),
|
|
46163
|
-
radius: optional(number),
|
|
46164
|
-
startCoords: optional(object({
|
|
46165
|
-
center: pairOfNumbers,
|
|
46166
|
-
radius: number
|
|
46167
|
-
})),
|
|
46168
|
-
// TODO: remove coord? it's legacy.
|
|
46169
|
-
coord: optional(pairOfNumbers)
|
|
46170
|
-
});
|
|
46171
|
-
const parsePerseusGraphTypeLinear = object({
|
|
46172
|
-
type: constant("linear"),
|
|
46173
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
46174
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
46175
|
-
// TODO: remove coord? it's legacy.
|
|
46176
|
-
coord: optional(pairOfNumbers)
|
|
46177
|
-
});
|
|
46178
|
-
const parsePerseusGraphTypeLinearSystem = object({
|
|
46179
|
-
type: constant("linear-system"),
|
|
46180
|
-
// TODO(benchristel): default coords to empty array?
|
|
46181
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
46182
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
46183
|
-
// TODO: remove coord? it's legacy.
|
|
46184
|
-
coord: optional(pairOfNumbers)
|
|
46185
|
-
});
|
|
46186
|
-
const parsePerseusGraphTypeNone = object({
|
|
46187
|
-
type: constant("none")
|
|
46188
|
-
});
|
|
46189
|
-
const parsePerseusGraphTypePoint = object({
|
|
46190
|
-
type: constant("point"),
|
|
46191
|
-
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
46192
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
46193
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46194
|
-
// TODO: remove coord? it's legacy.
|
|
46195
|
-
coord: optional(pairOfNumbers)
|
|
46196
|
-
});
|
|
46197
|
-
const parsePerseusGraphTypePolygon = object({
|
|
46198
|
-
type: constant("polygon"),
|
|
46199
|
-
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
46200
|
-
showAngles: optional(boolean),
|
|
46201
|
-
showSides: optional(boolean),
|
|
46202
|
-
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
46203
|
-
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
46204
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46205
|
-
// TODO: remove coord? it's legacy.
|
|
46206
|
-
coord: optional(pairOfNumbers)
|
|
46207
|
-
});
|
|
46208
|
-
const parsePerseusGraphTypeQuadratic = object({
|
|
46209
|
-
type: constant("quadratic"),
|
|
46210
|
-
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
46211
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
46212
|
-
// TODO: remove coord? it's legacy.
|
|
46213
|
-
coord: optional(pairOfNumbers)
|
|
46214
|
-
});
|
|
46215
|
-
const parsePerseusGraphTypeRay = object({
|
|
46216
|
-
type: constant("ray"),
|
|
46217
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
46218
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
46219
|
-
// TODO: remove coord? it's legacy.
|
|
46220
|
-
coord: optional(pairOfNumbers)
|
|
46221
|
-
});
|
|
46222
|
-
const parsePerseusGraphTypeSegment = object({
|
|
46223
|
-
type: constant("segment"),
|
|
46224
|
-
// TODO(benchristel): default numSegments?
|
|
46225
|
-
numSegments: optional(number),
|
|
46226
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
46227
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
46228
|
-
// TODO: remove coord? it's legacy.
|
|
46229
|
-
coord: optional(pairOfNumbers)
|
|
46230
|
-
});
|
|
46231
|
-
const parsePerseusGraphTypeSinusoid = object({
|
|
46232
|
-
type: constant("sinusoid"),
|
|
46233
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
46234
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46235
|
-
// TODO: remove coord? it's legacy.
|
|
46236
|
-
coord: optional(pairOfNumbers)
|
|
46237
|
-
});
|
|
46238
|
-
const parsePerseusGraphType = discriminatedUnionOn("type").withBranch("angle", parsePerseusGraphTypeAngle).withBranch("circle", parsePerseusGraphTypeCircle).withBranch("linear", parsePerseusGraphTypeLinear).withBranch("linear-system", parsePerseusGraphTypeLinearSystem).withBranch("none", parsePerseusGraphTypeNone).withBranch("point", parsePerseusGraphTypePoint).withBranch("polygon", parsePerseusGraphTypePolygon).withBranch("quadratic", parsePerseusGraphTypeQuadratic).withBranch("ray", parsePerseusGraphTypeRay).withBranch("segment", parsePerseusGraphTypeSegment).withBranch("sinusoid", parsePerseusGraphTypeSinusoid).parser;
|
|
46239
|
-
const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
|
|
46240
|
-
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
46241
|
-
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
46242
|
-
const parseLockedLabelType = object({
|
|
46243
|
-
type: constant("label"),
|
|
46244
|
-
coord: pairOfNumbers,
|
|
46245
|
-
text: string,
|
|
46246
|
-
color: parseLockedFigureColor,
|
|
46247
|
-
size: enumeration("small", "medium", "large")
|
|
46248
|
-
});
|
|
46249
|
-
const parseLockedPointType = object({
|
|
46250
|
-
type: constant("point"),
|
|
46251
|
-
coord: pairOfNumbers,
|
|
46252
|
-
color: parseLockedFigureColor,
|
|
46253
|
-
filled: boolean,
|
|
46254
|
-
// TODO(benchristel): default labels to empty array?
|
|
46255
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46256
|
-
ariaLabel: optional(string)
|
|
46257
|
-
});
|
|
46258
|
-
const parseLockedLineType = object({
|
|
46259
|
-
type: constant("line"),
|
|
46260
|
-
kind: enumeration("line", "ray", "segment"),
|
|
46261
|
-
points: pair(parseLockedPointType, parseLockedPointType),
|
|
46262
|
-
color: parseLockedFigureColor,
|
|
46263
|
-
lineStyle: parseLockedLineStyle,
|
|
46264
|
-
showPoint1: defaulted(boolean, () => false),
|
|
46265
|
-
showPoint2: defaulted(boolean, () => false),
|
|
46266
|
-
// TODO(benchristel): default labels to empty array?
|
|
46267
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46268
|
-
ariaLabel: optional(string)
|
|
46269
|
-
});
|
|
46270
|
-
const parseLockedVectorType = object({
|
|
46271
|
-
type: constant("vector"),
|
|
46272
|
-
points: pair(pairOfNumbers, pairOfNumbers),
|
|
46273
|
-
color: parseLockedFigureColor,
|
|
46274
|
-
// TODO(benchristel): default labels to empty array?
|
|
46275
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46276
|
-
ariaLabel: optional(string)
|
|
46277
|
-
});
|
|
46278
|
-
const parseLockedEllipseType = object({
|
|
46279
|
-
type: constant("ellipse"),
|
|
46280
|
-
center: pairOfNumbers,
|
|
46281
|
-
radius: pairOfNumbers,
|
|
46282
|
-
angle: number,
|
|
46283
|
-
color: parseLockedFigureColor,
|
|
46284
|
-
fillStyle: parseLockedFigureFillType,
|
|
46285
|
-
strokeStyle: parseLockedLineStyle,
|
|
46286
|
-
// TODO(benchristel): default labels to empty array?
|
|
46287
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46288
|
-
ariaLabel: optional(string)
|
|
46289
|
-
});
|
|
46290
|
-
const parseLockedPolygonType = object({
|
|
46291
|
-
type: constant("polygon"),
|
|
46292
|
-
points: array(pairOfNumbers),
|
|
46293
|
-
color: parseLockedFigureColor,
|
|
46294
|
-
showVertices: boolean,
|
|
46295
|
-
fillStyle: parseLockedFigureFillType,
|
|
46296
|
-
strokeStyle: parseLockedLineStyle,
|
|
46297
|
-
// TODO(benchristel): default labels to empty array?
|
|
46298
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46299
|
-
ariaLabel: optional(string)
|
|
46300
|
-
});
|
|
46301
|
-
const parseLockedFunctionType = object({
|
|
46302
|
-
type: constant("function"),
|
|
46303
|
-
color: parseLockedFigureColor,
|
|
46304
|
-
strokeStyle: parseLockedLineStyle,
|
|
46305
|
-
equation: string,
|
|
46306
|
-
directionalAxis: enumeration("x", "y"),
|
|
46307
|
-
domain: optional(pairOfNumbers),
|
|
46308
|
-
// TODO(benchristel): default labels to empty array?
|
|
46309
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46310
|
-
ariaLabel: optional(string)
|
|
46311
|
-
});
|
|
46312
|
-
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;
|
|
46313
|
-
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
46314
|
-
step: pairOfNumbers,
|
|
46315
|
-
// TODO(benchristel): rather than making gridStep and snapStep
|
|
46316
|
-
// optional, we should duplicate the defaulting logic from the
|
|
46317
|
-
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
46318
|
-
// why.
|
|
46319
|
-
gridStep: optional(pairOfNumbers),
|
|
46320
|
-
snapStep: optional(pairOfNumbers),
|
|
46321
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
46322
|
-
markings: enumeration("graph", "grid", "none"),
|
|
46323
|
-
labels: optional(array(string)),
|
|
46324
|
-
showProtractor: boolean,
|
|
46325
|
-
showRuler: optional(boolean),
|
|
46326
|
-
showTooltips: optional(boolean),
|
|
46327
|
-
rulerLabel: optional(string),
|
|
46328
|
-
rulerTicks: optional(number),
|
|
46329
|
-
range: pair(pairOfNumbers, pairOfNumbers),
|
|
46330
|
-
// NOTE(benchristel): I copied the default graph from
|
|
46331
|
-
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
46332
|
-
// an explanation of why we want to duplicate the default here.
|
|
46333
|
-
graph: defaulted(parsePerseusGraphType, () => ({
|
|
46334
|
-
type: "linear"
|
|
46335
|
-
})),
|
|
46336
|
-
correct: parsePerseusGraphType,
|
|
46337
|
-
// TODO(benchristel): default lockedFigures to empty array
|
|
46338
|
-
lockedFigures: optional(array(parseLockedFigure)),
|
|
46339
|
-
fullGraphLabel: optional(string),
|
|
46340
|
-
fullGraphAriaDescription: optional(string)
|
|
46341
|
-
}));
|
|
46342
|
-
|
|
46343
|
-
const parseLabelImageWidget = parseWidget(constant("label-image"), object({
|
|
46344
|
-
choices: array(string),
|
|
46345
|
-
imageUrl: string,
|
|
46346
|
-
imageAlt: string,
|
|
46347
|
-
imageHeight: number,
|
|
46348
|
-
imageWidth: number,
|
|
46349
|
-
markers: array(object({
|
|
46350
|
-
answers: array(string),
|
|
46351
|
-
label: string,
|
|
46352
|
-
x: number,
|
|
46353
|
-
y: number
|
|
46354
|
-
})),
|
|
46355
|
-
hideChoicesFromInstructions: boolean,
|
|
46356
|
-
multipleAnswers: boolean,
|
|
46357
|
-
static: defaulted(boolean, () => false)
|
|
46358
|
-
}));
|
|
46359
|
-
|
|
46360
|
-
const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
46361
|
-
labels: array(string),
|
|
46362
|
-
left: array(string),
|
|
46363
|
-
right: array(string),
|
|
46364
|
-
orderMatters: boolean,
|
|
46365
|
-
padding: boolean
|
|
46366
|
-
}));
|
|
46367
|
-
|
|
46368
|
-
const numberOrString = union(number).or(string).parser;
|
|
46369
|
-
const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
|
|
46370
|
-
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
46371
|
-
prefix: optional(string),
|
|
46372
|
-
suffix: optional(string),
|
|
46373
|
-
answers: array(array(numeric)),
|
|
46374
|
-
cursorPosition: optional(array(number)),
|
|
46375
|
-
matrixBoardSize: array(number),
|
|
46376
|
-
static: optional(boolean)
|
|
46377
|
-
}));
|
|
46378
|
-
|
|
46379
|
-
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
46380
|
-
// The default value for image comes from measurer.tsx.
|
|
46381
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46382
|
-
// defaults here.
|
|
46383
|
-
image: defaulted(parsePerseusImageBackground, () => ({
|
|
46384
|
-
url: null,
|
|
46385
|
-
top: 0,
|
|
46386
|
-
left: 0
|
|
46387
|
-
})),
|
|
46388
|
-
showProtractor: boolean,
|
|
46389
|
-
showRuler: boolean,
|
|
46390
|
-
rulerLabel: string,
|
|
46391
|
-
rulerTicks: number,
|
|
46392
|
-
rulerPixels: number,
|
|
46393
|
-
rulerLength: number,
|
|
46394
|
-
box: pair(number, number),
|
|
46395
|
-
// TODO(benchristel): static is not used. Remove it?
|
|
46396
|
-
static: defaulted(boolean, () => false)
|
|
46397
|
-
}));
|
|
46398
|
-
|
|
46399
|
-
const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
|
|
46400
|
-
widgetId: string,
|
|
46401
|
-
rotationAngle: optional(number),
|
|
46402
|
-
smiles: optional(string)
|
|
46403
|
-
}));
|
|
46404
|
-
|
|
46405
|
-
const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
|
|
46406
|
-
const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
46407
|
-
range: array(number),
|
|
46408
|
-
labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
|
|
46409
|
-
labelStyle: string,
|
|
46410
|
-
labelTicks: boolean,
|
|
46411
|
-
isTickCtrl: optional(nullable(boolean)),
|
|
46412
|
-
divisionRange: array(number),
|
|
46413
|
-
numDivisions: optional(nullable(number)),
|
|
46414
|
-
// NOTE(benchristel): I copied the default snapDivisions from
|
|
46415
|
-
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
46416
|
-
// an explanation of why we want to duplicate the default here.
|
|
46417
|
-
snapDivisions: defaulted(number, () => 2),
|
|
46418
|
-
tickStep: optional(nullable(number)),
|
|
46419
|
-
correctRel: optional(nullable(string)),
|
|
46420
|
-
correctX: nullable(number),
|
|
46421
|
-
initialX: optional(nullable(number)),
|
|
46422
|
-
showTooltip: optional(boolean),
|
|
46423
|
-
static: defaulted(boolean, () => false)
|
|
46424
|
-
}));
|
|
46425
|
-
|
|
46426
|
-
const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
|
|
46427
|
-
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
46428
|
-
answers: array(object({
|
|
46429
|
-
message: string,
|
|
46430
|
-
// TODO(benchristel): value should never be null or undefined,
|
|
46431
|
-
// but we have some content where it is anyway. If we backfill
|
|
46432
|
-
// the data, simplify this.
|
|
46433
|
-
value: optional(nullable(number)),
|
|
46434
|
-
status: string,
|
|
46435
|
-
answerForms: optional(array(parseMathFormat)),
|
|
46436
|
-
strict: boolean,
|
|
46437
|
-
maxError: optional(nullable(number)),
|
|
46438
|
-
// TODO(benchristel): simplify should never be a boolean, but we
|
|
46439
|
-
// have some content where it is anyway. If we ever backfill
|
|
46440
|
-
// the data, we should simplify `simplify`.
|
|
46441
|
-
simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
|
|
46442
|
-
})),
|
|
46443
|
-
labelText: optional(string),
|
|
46444
|
-
size: string,
|
|
46445
|
-
coefficient: defaulted(boolean, () => false),
|
|
46446
|
-
rightAlign: optional(boolean),
|
|
46447
|
-
static: defaulted(boolean, () => false),
|
|
46448
|
-
answerForms: optional(array(object({
|
|
46449
|
-
name: parseMathFormat,
|
|
46450
|
-
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
46451
|
-
})))
|
|
46452
|
-
}));
|
|
46453
|
-
|
|
46454
|
-
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
46455
|
-
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
46456
|
-
// it's defined.
|
|
46457
|
-
function parseRenderer(rawValue, ctx) {
|
|
46458
|
-
return parsePerseusRenderer(rawValue, ctx);
|
|
46459
|
-
}
|
|
46460
|
-
const largeToAuto = (height, ctx) => {
|
|
46461
|
-
if (height === "large") {
|
|
46462
|
-
return ctx.success("auto");
|
|
46463
|
-
}
|
|
46464
|
-
return ctx.success(height);
|
|
46465
|
-
};
|
|
46466
|
-
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
46467
|
-
options: defaulted(array(parseRenderer), () => []),
|
|
46468
|
-
correctOptions: array(parseRenderer),
|
|
46469
|
-
otherOptions: array(parseRenderer),
|
|
46470
|
-
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
46471
|
-
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
46472
|
-
}));
|
|
46473
|
-
|
|
46474
|
-
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
46475
|
-
passageNumber: number,
|
|
46476
|
-
referenceNumber: number,
|
|
46477
|
-
summaryText: optional(string)
|
|
46478
|
-
}));
|
|
46479
|
-
|
|
46480
|
-
const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
46481
|
-
footnotes: defaulted(string, () => ""),
|
|
46482
|
-
passageText: string,
|
|
46483
|
-
passageTitle: defaulted(string, () => ""),
|
|
46484
|
-
showLineNumbers: boolean,
|
|
46485
|
-
static: defaulted(boolean, () => false)
|
|
46486
|
-
}));
|
|
46487
|
-
|
|
46488
|
-
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
46489
|
-
url: string,
|
|
46490
|
-
description: string
|
|
46491
|
-
}));
|
|
46492
|
-
|
|
46493
|
-
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
46494
|
-
labels: array(string),
|
|
46495
|
-
categories: array(string),
|
|
46496
|
-
type: enumeration(...perseusCore.plotterPlotTypes),
|
|
46497
|
-
maxY: number,
|
|
46498
|
-
// The default value for scaleY comes from plotter.tsx.
|
|
46499
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46500
|
-
// defaults here.
|
|
46501
|
-
scaleY: defaulted(number, () => 1),
|
|
46502
|
-
labelInterval: optional(nullable(number)),
|
|
46503
|
-
// The default value for snapsPerLine comes from plotter.tsx.
|
|
46504
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46505
|
-
// defaults here.
|
|
46506
|
-
snapsPerLine: defaulted(number, () => 2),
|
|
46507
|
-
starting: array(number),
|
|
46508
|
-
correct: array(number),
|
|
46509
|
-
picUrl: optional(nullable(string)),
|
|
46510
|
-
picSize: optional(nullable(number)),
|
|
46511
|
-
picBoxHeight: optional(nullable(number)),
|
|
46512
|
-
// NOTE(benchristel): I copied the default plotDimensions from
|
|
46513
|
-
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
46514
|
-
// of why we want to duplicate the defaults here.
|
|
46515
|
-
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
46516
|
-
}));
|
|
46517
|
-
|
|
46518
|
-
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
46519
|
-
programID: string,
|
|
46520
|
-
height: number
|
|
46521
|
-
}));
|
|
46522
|
-
|
|
46523
|
-
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
46524
|
-
choices: array(object({
|
|
46525
|
-
content: defaulted(string, () => ""),
|
|
46526
|
-
clue: optional(string),
|
|
46527
|
-
correct: optional(boolean),
|
|
46528
|
-
isNoneOfTheAbove: optional(boolean),
|
|
46529
|
-
// deprecated
|
|
46530
|
-
// There is an import cycle between radio-widget.ts and
|
|
46531
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
46532
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
46533
|
-
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
46534
|
-
})),
|
|
46535
|
-
hasNoneOfTheAbove: optional(boolean),
|
|
46536
|
-
countChoices: optional(boolean),
|
|
46537
|
-
randomize: optional(boolean),
|
|
46538
|
-
multipleSelect: optional(boolean),
|
|
46539
|
-
deselectEnabled: optional(boolean),
|
|
46540
|
-
// deprecated
|
|
46541
|
-
onePerLine: optional(boolean),
|
|
46542
|
-
// deprecated
|
|
46543
|
-
displayCount: optional(any),
|
|
46544
|
-
// v0 props
|
|
46545
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
46546
|
-
noneOfTheAbove: optional(constant(false))
|
|
46547
|
-
}));
|
|
46548
|
-
|
|
46549
|
-
const parseSorterWidget = parseWidget(constant("sorter"), object({
|
|
46550
|
-
correct: array(string),
|
|
46551
|
-
padding: boolean,
|
|
46552
|
-
layout: enumeration("horizontal", "vertical")
|
|
46553
|
-
}));
|
|
46554
|
-
|
|
46555
|
-
const parseTableWidget = parseWidget(constant("table"), object({
|
|
46556
|
-
headers: array(string),
|
|
46557
|
-
rows: number,
|
|
46558
|
-
columns: number,
|
|
46559
|
-
answers: array(array(string))
|
|
46560
|
-
}));
|
|
46561
|
-
|
|
46562
|
-
const parseVideoWidget = parseWidget(constant("video"), object({
|
|
46563
|
-
location: string,
|
|
46564
|
-
static: optional(boolean)
|
|
46565
|
-
}));
|
|
46566
|
-
|
|
46567
|
-
const parseWidgetsMap = (rawValue, ctx) => {
|
|
46568
|
-
if (!isObject(rawValue)) {
|
|
46569
|
-
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
46570
|
-
}
|
|
46571
|
-
const widgetsMap = {};
|
|
46572
|
-
for (const key of Object.keys(rawValue)) {
|
|
46573
|
-
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
46574
|
-
// but it's the only way I could find to make TypeScript check the key
|
|
46575
|
-
// against the widget type.
|
|
46576
|
-
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
46577
|
-
if (isFailure(entryResult)) {
|
|
46578
|
-
return entryResult;
|
|
46579
|
-
}
|
|
46580
|
-
}
|
|
46581
|
-
return ctx.success(widgetsMap);
|
|
46582
|
-
};
|
|
46583
|
-
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
46584
|
-
let [id, widget] = _ref;
|
|
46585
|
-
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
46586
|
-
if (isFailure(idComponentsResult)) {
|
|
46587
|
-
return idComponentsResult;
|
|
46588
|
-
}
|
|
46589
|
-
const [type, n] = idComponentsResult.value;
|
|
46590
|
-
function parseAndAssign(key, parse) {
|
|
46591
|
-
const widgetResult = parse(widget, ctx);
|
|
46592
|
-
if (isFailure(widgetResult)) {
|
|
46593
|
-
return widgetResult;
|
|
46594
|
-
}
|
|
46595
|
-
widgetMap[key] = widgetResult.value;
|
|
46596
|
-
return ctx.success(undefined);
|
|
46597
|
-
}
|
|
46598
|
-
switch (type) {
|
|
46599
|
-
case "categorizer":
|
|
46600
|
-
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
46601
|
-
case "cs-program":
|
|
46602
|
-
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
46603
|
-
case "definition":
|
|
46604
|
-
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
46605
|
-
case "dropdown":
|
|
46606
|
-
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
46607
|
-
case "explanation":
|
|
46608
|
-
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
46609
|
-
case "expression":
|
|
46610
|
-
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
46611
|
-
case "grapher":
|
|
46612
|
-
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
46613
|
-
case "group":
|
|
46614
|
-
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
46615
|
-
case "graded-group":
|
|
46616
|
-
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
46617
|
-
case "graded-group-set":
|
|
46618
|
-
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
46619
|
-
case "iframe":
|
|
46620
|
-
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
46621
|
-
case "image":
|
|
46622
|
-
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
46623
|
-
case "input-number":
|
|
46624
|
-
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
46625
|
-
case "interaction":
|
|
46626
|
-
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
46627
|
-
case "interactive-graph":
|
|
46628
|
-
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
46629
|
-
case "label-image":
|
|
46630
|
-
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
46631
|
-
case "matcher":
|
|
46632
|
-
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
46633
|
-
case "matrix":
|
|
46634
|
-
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
46635
|
-
case "measurer":
|
|
46636
|
-
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
46637
|
-
case "molecule-renderer":
|
|
46638
|
-
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
46639
|
-
case "number-line":
|
|
46640
|
-
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
46641
|
-
case "numeric-input":
|
|
46642
|
-
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
46643
|
-
case "orderer":
|
|
46644
|
-
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
46645
|
-
case "passage":
|
|
46646
|
-
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
46647
|
-
case "passage-ref":
|
|
46648
|
-
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
46649
|
-
case "passage-ref-target":
|
|
46650
|
-
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
46651
|
-
// used in test content. See:
|
|
46652
|
-
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
46653
|
-
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
46654
|
-
case "phet-simulation":
|
|
46655
|
-
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
46656
|
-
case "plotter":
|
|
46657
|
-
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
46658
|
-
case "python-program":
|
|
46659
|
-
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
46660
|
-
case "radio":
|
|
46661
|
-
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
46662
|
-
case "sorter":
|
|
46663
|
-
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
46664
|
-
case "table":
|
|
46665
|
-
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
46666
|
-
case "video":
|
|
46667
|
-
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
46668
|
-
case "sequence":
|
|
46669
|
-
// sequence is a deprecated widget type, and the corresponding
|
|
46670
|
-
// widget component no longer exists.
|
|
46671
|
-
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
46672
|
-
case "lights-puzzle":
|
|
46673
|
-
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
46674
|
-
case "simulator":
|
|
46675
|
-
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
46676
|
-
case "transformer":
|
|
46677
|
-
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
46678
|
-
default:
|
|
46679
|
-
if (getWidget(type)) {
|
|
46680
|
-
// @ts-expect-error - 'type' is not a valid widget type
|
|
46681
|
-
return parseAndAssign(`${type} ${n}`, any);
|
|
46682
|
-
}
|
|
46683
|
-
return ctx.failure("a valid widget type", type);
|
|
46684
|
-
}
|
|
46685
|
-
};
|
|
46686
|
-
const parseDeprecatedWidget = parseWidget(
|
|
46687
|
-
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
46688
|
-
(_, ctx) => ctx.success("deprecated-standin"),
|
|
46689
|
-
// Allow any widget options
|
|
46690
|
-
object({}));
|
|
46691
|
-
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
46692
|
-
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
46693
|
-
return ctx.failure("a string representing a positive integer", rawValue);
|
|
46694
|
-
}
|
|
46695
|
-
return ctx.success(+rawValue);
|
|
46696
|
-
};
|
|
46697
|
-
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
46698
|
-
|
|
46699
|
-
const parsePerseusRenderer = defaulted(object({
|
|
46700
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
46701
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
46702
|
-
content: defaulted(string, () => ""),
|
|
46703
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
46704
|
-
// `group` widget can contain another renderer.
|
|
46705
|
-
// The anonymous function below ensures that we don't try to access
|
|
46706
|
-
// parseWidgetsMap before it's defined.
|
|
46707
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
46708
|
-
metadata: optional(array(string)),
|
|
46709
|
-
images: parseImages
|
|
46710
|
-
}),
|
|
46711
|
-
// Default value
|
|
46712
|
-
() => ({
|
|
46713
|
-
content: "",
|
|
46714
|
-
widgets: {},
|
|
46715
|
-
images: {}
|
|
46716
|
-
}));
|
|
46717
|
-
|
|
46718
|
-
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
46719
|
-
|
|
46720
|
-
const parseHint = object({
|
|
46721
|
-
replace: optional(boolean),
|
|
46722
|
-
content: string,
|
|
46723
|
-
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
46724
|
-
metadata: optional(array(string)),
|
|
46725
|
-
images: parseImages
|
|
46726
|
-
});
|
|
46727
|
-
|
|
46728
|
-
const parsePerseusItem$1 = object({
|
|
46729
|
-
question: parsePerseusRenderer,
|
|
46730
|
-
hints: defaulted(array(parseHint), () => []),
|
|
46731
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
|
|
46732
|
-
itemDataVersion: optional(object({
|
|
46733
|
-
major: number,
|
|
46734
|
-
minor: number
|
|
46735
|
-
})),
|
|
46736
|
-
// Deprecated field
|
|
46737
|
-
answer: any
|
|
46738
|
-
});
|
|
46739
|
-
|
|
46740
|
-
// Some answerAreas have extra fields, like:
|
|
46741
|
-
//
|
|
46742
|
-
// "answerArea": {
|
|
46743
|
-
// "type": "multiple",
|
|
46744
|
-
// "options": {
|
|
46745
|
-
// "content": "",
|
|
46746
|
-
// "images": {},
|
|
46747
|
-
// "widgets": {}
|
|
46748
|
-
// }
|
|
46749
|
-
// }
|
|
46750
|
-
//
|
|
46751
|
-
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
46752
|
-
// migration function removes them.
|
|
46753
|
-
function migrateAnswerArea(rawValue, ctx) {
|
|
46754
|
-
const {
|
|
46755
|
-
type: _,
|
|
46756
|
-
options: __,
|
|
46757
|
-
...rest
|
|
46758
|
-
} = rawValue;
|
|
46759
|
-
return ctx.success(rest);
|
|
46760
|
-
}
|
|
46761
|
-
|
|
46762
|
-
/**
|
|
46763
|
-
* Helper to parse PerseusItem JSON
|
|
46764
|
-
* Why not just use JSON.parse? We want:
|
|
46765
|
-
* - To make sure types are correct
|
|
46766
|
-
* - To give us a central place to validate/transform output if needed
|
|
46767
|
-
* @deprecated - use parseAndMigratePerseusItem instead
|
|
46768
|
-
* @param {string} json - the stringified PerseusItem JSON
|
|
46769
|
-
* @returns {PerseusItem} the parsed PerseusItem object
|
|
46770
|
-
*/
|
|
46771
|
-
function parsePerseusItem(json) {
|
|
46772
|
-
// Try to block a cheating vector which relies on monkey-patching
|
|
46773
|
-
// JSON.parse
|
|
46774
|
-
if (isRealJSONParse(JSON.parse)) {
|
|
46775
|
-
return JSON.parse(json);
|
|
46776
|
-
}
|
|
46777
|
-
throw new Error("Something went wrong.");
|
|
46778
|
-
}
|
|
46779
|
-
/**
|
|
46780
|
-
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
46781
|
-
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
46782
|
-
* data.
|
|
46783
|
-
*
|
|
46784
|
-
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
46785
|
-
* failure, it will contain an error message describing where in the tree
|
|
46786
|
-
* parsing failed.
|
|
46787
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46788
|
-
*/
|
|
46789
|
-
function parseAndMigratePerseusItem(json) {
|
|
46790
|
-
throwErrorIfCheatingDetected();
|
|
46791
|
-
const object = JSON.parse(json);
|
|
46792
|
-
const result = parse(object, parsePerseusItem$1);
|
|
46793
|
-
if (isFailure(result)) {
|
|
46794
|
-
return failure({
|
|
46795
|
-
message: result.detail,
|
|
46796
|
-
invalidObject: object
|
|
46797
|
-
});
|
|
46798
|
-
}
|
|
46799
|
-
return result;
|
|
46800
|
-
}
|
|
46801
|
-
|
|
46802
|
-
/**
|
|
46803
|
-
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
46804
|
-
* latest schema, and runtime-typechecks the result.
|
|
46805
|
-
*
|
|
46806
|
-
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
46807
|
-
* failure, it will contain an error message describing where in the tree
|
|
46808
|
-
* parsing failed.
|
|
46809
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46810
|
-
*/
|
|
46811
|
-
function parseAndMigratePerseusArticle(json) {
|
|
46812
|
-
throwErrorIfCheatingDetected();
|
|
46813
|
-
const object = JSON.parse(json);
|
|
46814
|
-
const result = parse(object, parsePerseusArticle);
|
|
46815
|
-
if (isFailure(result)) {
|
|
46816
|
-
return failure({
|
|
46817
|
-
message: result.detail,
|
|
46818
|
-
invalidObject: object
|
|
46819
|
-
});
|
|
46820
|
-
}
|
|
46821
|
-
return result;
|
|
46822
|
-
}
|
|
46823
|
-
|
|
46824
|
-
/**
|
|
46825
|
-
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
46826
|
-
*/
|
|
46827
|
-
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
46828
|
-
function throwErrorIfCheatingDetected() {
|
|
46829
|
-
if (!isRealJSONParse(JSON.parse)) {
|
|
46830
|
-
throw new Error("Something went wrong.");
|
|
46831
|
-
}
|
|
46832
|
-
}
|
|
46833
|
-
|
|
46834
45356
|
const genericPerseusItemData = {
|
|
46835
45357
|
question: {
|
|
46836
45358
|
content: "",
|
|
@@ -47052,6 +45574,26 @@ const ScoringUtil = {
|
|
|
47052
45574
|
* Types
|
|
47053
45575
|
*/
|
|
47054
45576
|
|
|
45577
|
+
Object.defineProperty(exports, 'isFailure', {
|
|
45578
|
+
enumerable: true,
|
|
45579
|
+
get: function () { return perseusCore.isFailure; }
|
|
45580
|
+
});
|
|
45581
|
+
Object.defineProperty(exports, 'isSuccess', {
|
|
45582
|
+
enumerable: true,
|
|
45583
|
+
get: function () { return perseusCore.isSuccess; }
|
|
45584
|
+
});
|
|
45585
|
+
Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
|
|
45586
|
+
enumerable: true,
|
|
45587
|
+
get: function () { return perseusCore.parseAndMigratePerseusArticle; }
|
|
45588
|
+
});
|
|
45589
|
+
Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
|
|
45590
|
+
enumerable: true,
|
|
45591
|
+
get: function () { return perseusCore.parseAndMigratePerseusItem; }
|
|
45592
|
+
});
|
|
45593
|
+
Object.defineProperty(exports, 'parsePerseusItem', {
|
|
45594
|
+
enumerable: true,
|
|
45595
|
+
get: function () { return perseusCore.parsePerseusItem; }
|
|
45596
|
+
});
|
|
47055
45597
|
exports.ApiOptions = ApiOptions;
|
|
47056
45598
|
exports.ArticleRenderer = ArticleRenderer;
|
|
47057
45599
|
exports.BaseRadio = BaseRadio;
|
|
@@ -47077,7 +45619,6 @@ exports.PerseusI18nContext = PerseusI18nContext;
|
|
|
47077
45619
|
exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
|
|
47078
45620
|
exports.PerseusMarkdown = PerseusMarkdown;
|
|
47079
45621
|
exports.PlotterWidget = Plotter$1;
|
|
47080
|
-
exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
|
|
47081
45622
|
exports.Radio = Radio;
|
|
47082
45623
|
exports.Renderer = Renderer;
|
|
47083
45624
|
exports.ScoringUtil = ScoringUtil;
|
|
@@ -47086,7 +45627,6 @@ exports.TableWidget = Table$1;
|
|
|
47086
45627
|
exports.Util = Util;
|
|
47087
45628
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
47088
45629
|
exports.Widgets = widgets$1;
|
|
47089
|
-
exports.addWidget = addWidget;
|
|
47090
45630
|
exports.apiVersion = apiVersion;
|
|
47091
45631
|
exports.bodyXsmallBold = bodyXsmallBold;
|
|
47092
45632
|
exports.components = components;
|
|
@@ -47117,8 +45657,6 @@ exports.getSegmentCoords = getSegmentCoords;
|
|
|
47117
45657
|
exports.getSinusoidCoords = getSinusoidCoords;
|
|
47118
45658
|
exports.getValidWidgetIds = getValidWidgetIds;
|
|
47119
45659
|
exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
|
|
47120
|
-
exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
|
|
47121
|
-
exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
|
|
47122
45660
|
exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
|
|
47123
45661
|
exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
|
|
47124
45662
|
exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;
|
|
@@ -47127,9 +45665,7 @@ exports.iconTrash = iconTrash;
|
|
|
47127
45665
|
exports.init = init;
|
|
47128
45666
|
exports.injectWidgets = injectWidgets;
|
|
47129
45667
|
exports.interactiveSizes = interactiveSizes$1;
|
|
47130
|
-
exports.isFailure = isFailure;
|
|
47131
45668
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
47132
|
-
exports.isSuccess = isSuccess;
|
|
47133
45669
|
exports.isWidgetIdInContent = isWidgetIdInContent;
|
|
47134
45670
|
exports.isWrongAnswerSupported = isWrongAnswerSupported;
|
|
47135
45671
|
exports.itemDataVersion = itemDataVersion;
|
|
@@ -47138,10 +45674,7 @@ exports.libVersion = libVersion;
|
|
|
47138
45674
|
exports.makeSafeUrl = makeSafeUrl;
|
|
47139
45675
|
exports.mathOnlyParser = mathOnlyParser;
|
|
47140
45676
|
exports.mediaQueries = mediaQueries;
|
|
47141
|
-
exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
|
|
47142
|
-
exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
|
|
47143
45677
|
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
47144
|
-
exports.parsePerseusItem = parsePerseusItem;
|
|
47145
45678
|
exports.preprocessTex = preprocessTex;
|
|
47146
45679
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
47147
45680
|
exports.scorePerseusItem = scorePerseusItem;
|