@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/es/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
-
import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize,
|
|
3
|
+
import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, getWidgetIdsFromContent, mapObject, passageRefLogic, radioLogic, getCategorizerPublicWidgetOptions, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, measurerLogic, getOrdererPublicWidgetOptions, addLibraryVersionToPerseusDebug, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
|
|
4
|
+
export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
|
|
4
5
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
5
6
|
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
6
|
-
import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
|
|
7
|
+
import { scoreExpression, validateExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, validateRadio, scoreCategorizer, validateCategorizer, scoreCSProgram, scoreDropdown, validateDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, validateMatrix, scoreNumberLine, validateNumberLine, scoreOrderer, validateOrderer, scorePlotter, validatePlotter, scoreSorter, validateSorter, scoreTable, validateTable } from '@khanacademy/perseus-score';
|
|
7
8
|
import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
8
9
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
9
10
|
import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -22,7 +23,7 @@ import { v4 } from 'uuid';
|
|
|
22
23
|
import PropTypes from 'prop-types';
|
|
23
24
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
25
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import { point, KhanMath, number
|
|
26
|
+
import { point, KhanMath, number, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
|
|
26
27
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
27
28
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
28
29
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -58,191 +59,6 @@ function _extends() {
|
|
|
58
59
|
* The translated strings that are used to render Perseus.
|
|
59
60
|
*/
|
|
60
61
|
|
|
61
|
-
/**
|
|
62
|
-
* Untranslated strings used in Perseus. To be used by an external
|
|
63
|
-
* translator to produce translated strings, passed in as `PerseusStrings`.
|
|
64
|
-
* !! Note: Ensure that all escape sequences are double-escaped. (e.g. `\\text` -> `\\\\text`)
|
|
65
|
-
*/
|
|
66
|
-
const strings = {
|
|
67
|
-
closeKeypad: "close math keypad",
|
|
68
|
-
openKeypad: "open math keypad",
|
|
69
|
-
mathInputBox: "Math input box",
|
|
70
|
-
removeHighlight: "Remove highlight",
|
|
71
|
-
addHighlight: "Add highlight",
|
|
72
|
-
hintPos: "Hint #%(pos)s",
|
|
73
|
-
errorRendering: "Error rendering: %(error)s",
|
|
74
|
-
APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "12 pi or " + "2/3 pi",
|
|
75
|
-
EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
|
|
76
|
-
NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
|
|
77
|
-
MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\\\%</code> at the end.",
|
|
78
|
-
MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
|
|
79
|
-
WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
|
|
80
|
-
WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
|
|
81
|
-
invalidSelection: "Make sure you select something for every row.",
|
|
82
|
-
ERROR_TITLE: "Oops!",
|
|
83
|
-
ERROR_MESSAGE: "Sorry, I don't understand that!",
|
|
84
|
-
hints: "Hints",
|
|
85
|
-
getAnotherHint: "Get another hint",
|
|
86
|
-
deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
|
|
87
|
-
keepTrying: "Keep trying",
|
|
88
|
-
tryAgain: "Try again",
|
|
89
|
-
check: "Check",
|
|
90
|
-
correctExcited: "Correct!",
|
|
91
|
-
nextQuestion: "Next question",
|
|
92
|
-
skipToTitle: "Skip to %(title)s",
|
|
93
|
-
current: "Current",
|
|
94
|
-
correct: "Correct",
|
|
95
|
-
correctSelected: "Correct (selected)",
|
|
96
|
-
correctCrossedOut: "Correct (but you crossed it out)",
|
|
97
|
-
incorrect: "Incorrect",
|
|
98
|
-
incorrectSelected: "Incorrect (selected)",
|
|
99
|
-
hideExplanation: "Hide explanation",
|
|
100
|
-
explain: "Explain",
|
|
101
|
-
INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
|
|
102
|
-
DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
|
|
103
|
-
DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
|
|
104
|
-
integerExample: "an integer, like $6$",
|
|
105
|
-
properExample: "a *proper* fraction, like $1/2$ or $6/10$",
|
|
106
|
-
simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
|
|
107
|
-
improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
|
|
108
|
-
simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
|
|
109
|
-
mixedExample: "a mixed number, like $1\\\\ 3/4$",
|
|
110
|
-
decimalExample: "an *exact* decimal, like $0.75$",
|
|
111
|
-
percentExample: "a percent, like $12.34\\\\%$",
|
|
112
|
-
piExample: "a multiple of pi, like $12$ pi or $2/3$ pi",
|
|
113
|
-
yourAnswer: "**Your answer should be** ",
|
|
114
|
-
yourAnswerLabel: "Your answer:",
|
|
115
|
-
addPoints: "Click to add points",
|
|
116
|
-
addVertices: "Click to add vertices",
|
|
117
|
-
tapMultiple: "Tap each dot on the image to select all answers that apply.",
|
|
118
|
-
tapSingle: "Tap each dot on the image to select an answer.",
|
|
119
|
-
clickMultiple: "Click each dot on the image to select all answers that apply.",
|
|
120
|
-
clickSingle: "Click each dot on the image to select an answer.",
|
|
121
|
-
choices: "Choices:",
|
|
122
|
-
answers: {
|
|
123
|
-
one: "%(num)s answer",
|
|
124
|
-
other: "%(num)s answers"
|
|
125
|
-
},
|
|
126
|
-
hideAnswersToggleLabel: "Hide answer choices",
|
|
127
|
-
moves: {
|
|
128
|
-
one: "Moves: %(num)s",
|
|
129
|
-
other: "Moves: %(num)s"
|
|
130
|
-
},
|
|
131
|
-
clickTiles: "Click on the tiles to change the lights.",
|
|
132
|
-
turnOffLights: "You must turn on all of the lights to continue.",
|
|
133
|
-
fillAllCells: "Make sure you fill in all cells in the matrix.",
|
|
134
|
-
molecularDrawing: "A molecular structure drawing. SMILES notation: %(content)s",
|
|
135
|
-
switchDirection: "Switch direction",
|
|
136
|
-
circleOpen: "Make circle open",
|
|
137
|
-
circleFilled: "Make circle filled",
|
|
138
|
-
numDivisions: "Number of divisions:",
|
|
139
|
-
divisions: "Please make sure the number of divisions is in the range %(divRangeString)s.",
|
|
140
|
-
lineRange: "lines %(lineRange)s",
|
|
141
|
-
lineNumber: "line %(lineNumber)s",
|
|
142
|
-
symbolPassage: "The symbol %(questionSymbol)s indicates that question %(questionNumber)s references this portion of the passage.",
|
|
143
|
-
symbolQuestion: " The symbol %(sentenceSymbol)s indicates that the following sentence is referenced in a question.",
|
|
144
|
-
lineLabel: {
|
|
145
|
-
context: "a label next to a reading passage to denote the line number",
|
|
146
|
-
message: "Line"
|
|
147
|
-
},
|
|
148
|
-
beginningPassage: "Beginning of reading passage.",
|
|
149
|
-
beginningFootnotes: "Beginning of reading passage footnotes.",
|
|
150
|
-
endPassage: "End of reading passage.",
|
|
151
|
-
questionMarker: "[Marker for question %(number)s]",
|
|
152
|
-
circleMarker: "[Circle marker %(number)s]",
|
|
153
|
-
sentenceMarker: "[Sentence %(number)s]",
|
|
154
|
-
dragHandles: "Drag handles to make graph",
|
|
155
|
-
tapAddPoints: "Tap to add points",
|
|
156
|
-
false: "False",
|
|
157
|
-
true: "True",
|
|
158
|
-
no: "No",
|
|
159
|
-
yes: "Yes",
|
|
160
|
-
chooseCorrectNum: "Please choose the correct number of answers.",
|
|
161
|
-
notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
|
|
162
|
-
noneOfTheAbove: "None of the above",
|
|
163
|
-
chooseNumAnswers: "Choose %(numCorrect)s answers:",
|
|
164
|
-
chooseAllAnswers: "Choose all answers that apply:",
|
|
165
|
-
chooseOneAnswer: "Choose 1 answer:",
|
|
166
|
-
choiceCheckedCorrect: "(Choice %(letter)s, Checked, Correct)",
|
|
167
|
-
choiceCrossedOutCorrect: "(Choice %(letter)s, Crossed out, Correct)",
|
|
168
|
-
choiceCorrect: "(Choice %(letter)s, Correct)",
|
|
169
|
-
choiceCheckedIncorrect: "(Choice %(letter)s, Checked, Incorrect)",
|
|
170
|
-
choiceCrossedOutIncorrect: "(Choice %(letter)s, Crossed out, Incorrect)",
|
|
171
|
-
choiceIncorrect: "(Choice %(letter)s, Incorrect)",
|
|
172
|
-
choiceChecked: "(Choice %(letter)s, Checked)",
|
|
173
|
-
choiceCrossedOut: "(Choice %(letter)s, Crossed out)",
|
|
174
|
-
choice: "(Choice %(letter)s)",
|
|
175
|
-
crossOut: "Cross out",
|
|
176
|
-
crossOutOption: "Cross out option",
|
|
177
|
-
crossOutChoice: "Cross out Choice %(letter)s",
|
|
178
|
-
bringBack: "Bring back",
|
|
179
|
-
openMenuForChoice: "Open menu for Choice %(letter)s",
|
|
180
|
-
letters: {
|
|
181
|
-
context: "This is a list of single-character labels that will appear in front of multiple-choice options. For instance, a multiple-choice question with three options would display (A) first option (B) second option (C) third option. There must be spaces between each of the different characters. The characters will show up next to options in the order that they are listed here. Most multiple choice questions have 5 or fewer options.",
|
|
182
|
-
message: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
|
|
183
|
-
},
|
|
184
|
-
rightArrow: "Reaction arrow pointing to the right.",
|
|
185
|
-
dontUnderstandUnits: "I couldn't understand those units.",
|
|
186
|
-
checkSigFigs: "Check your significant figures.",
|
|
187
|
-
answerNumericallyIncorrect: "That answer is numerically incorrect.",
|
|
188
|
-
checkUnits: "Check your units.",
|
|
189
|
-
dontUnderstand: "I don't understand that",
|
|
190
|
-
loading: "Loading...",
|
|
191
|
-
videoTranscript: "See video transcript",
|
|
192
|
-
somethingWrong: "Something went wrong.",
|
|
193
|
-
videoWrapper: "Khan Academy video wrapper",
|
|
194
|
-
mathInputTitle: "mathematics keyboard",
|
|
195
|
-
mathInputDescription: "Use keyboard/mouse to interact with math-based input fields",
|
|
196
|
-
sin: "sin",
|
|
197
|
-
cos: "cos",
|
|
198
|
-
tan: "tan",
|
|
199
|
-
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
200
|
-
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
201
|
-
selectAnAnswer: "Select an answer",
|
|
202
|
-
// The following strings are used for interactive graph SR descriptions.
|
|
203
|
-
addPoint: "Add Point",
|
|
204
|
-
removePoint: "Remove Point",
|
|
205
|
-
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
206
|
-
closePolygon: "Close shape",
|
|
207
|
-
openPolygon: "Re-open shape",
|
|
208
|
-
srInteractiveElements: "Interactive elements: %(elements)s",
|
|
209
|
-
srNoInteractiveElements: "No interactive elements",
|
|
210
|
-
// TODO(LEMS-2660): The following strings are ones that will need
|
|
211
|
-
// translation tickets after all interactive graph SR strings have
|
|
212
|
-
// been finalized. Remove this comment after the tickets have been
|
|
213
|
-
// created.
|
|
214
|
-
srPointAtCoordinates: "Point %(num)s at %(x)s comma %(y)s",
|
|
215
|
-
srCircleGraph: "A circle on a coordinate plane.",
|
|
216
|
-
srCircleShape: "Circle. The center point is at %(centerX)s comma %(centerY)s.",
|
|
217
|
-
srCircleRadiusPoint: "Radius point at %(radiusPointX)s comma %(radiusPointY)s.",
|
|
218
|
-
srCircleRadius: "Circle radius is %(radius)s.",
|
|
219
|
-
srCircleOuterPoints: "Points on the circle at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, %(point3X)s comma %(point3Y)s, %(point4X)s comma %(point4Y)s.",
|
|
220
|
-
srLinearGraph: "A line on a coordinate plane.",
|
|
221
|
-
srLinearGraphPoints: "The line has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
222
|
-
srLinearGraphSlopeIncreasing: "Its slope increases from left to right.",
|
|
223
|
-
srLinearGraphSlopeDecreasing: "Its slope decreases from left to right.",
|
|
224
|
-
srLinearGraphSlopeHorizontal: "Its slope is zero.",
|
|
225
|
-
srLinearGraphSlopeVertical: "Its slope is undefined.",
|
|
226
|
-
srLinearGraphXOnlyIntercept: "The line crosses the X-axis at %(xIntercept)s comma 0.",
|
|
227
|
-
srLinearGraphYOnlyIntercept: "The line crosses the Y-axis at 0 comma %(yIntercept)s.",
|
|
228
|
-
srLinearGraphBothIntercepts: "The line crosses the X-axis at %(xIntercept)s comma 0 and the Y-axis at 0 comma %(yIntercept)s.",
|
|
229
|
-
srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
|
|
230
|
-
srLinearGrabHandle: "Line from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
231
|
-
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
232
|
-
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
|
-
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
|
-
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
|
|
235
|
-
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
236
|
-
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
237
|
-
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
238
|
-
srRayGraph: "A ray on a coordinate plane.",
|
|
239
|
-
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
240
|
-
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
241
|
-
srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
|
|
242
|
-
srRayTerminalPoint: "Through point at %(x)s comma %(y)s."
|
|
243
|
-
// The above strings are used for interactive graph SR descriptions.
|
|
244
|
-
};
|
|
245
|
-
|
|
246
62
|
/**
|
|
247
63
|
* Mock strings for the Perseus package, to be used for tests and Storybook.
|
|
248
64
|
*/
|
|
@@ -509,6 +325,43 @@ const mockStrings = {
|
|
|
509
325
|
endingSideX,
|
|
510
326
|
endingSideY
|
|
511
327
|
}) => `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}.`,
|
|
328
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
329
|
+
srMultipleSegmentGraphAriaLabel: ({
|
|
330
|
+
countOfSegments
|
|
331
|
+
}) => `${countOfSegments} segments on a coordinate plane.`,
|
|
332
|
+
srMultipleSegmentIndividualLabel: ({
|
|
333
|
+
point1X,
|
|
334
|
+
point1Y,
|
|
335
|
+
point2X,
|
|
336
|
+
point2Y,
|
|
337
|
+
indexOfSegment
|
|
338
|
+
}) => `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
|
|
339
|
+
srSingleSegmentLabel: ({
|
|
340
|
+
point1X,
|
|
341
|
+
point1Y,
|
|
342
|
+
point2X,
|
|
343
|
+
point2Y
|
|
344
|
+
}) => `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
|
|
345
|
+
srSegmentLength: ({
|
|
346
|
+
length
|
|
347
|
+
}) => `Segment length ${length} units.`,
|
|
348
|
+
srSingleSegmentGraphEndpointAriaLabel: ({
|
|
349
|
+
endpointNumber,
|
|
350
|
+
x,
|
|
351
|
+
y
|
|
352
|
+
}) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`,
|
|
353
|
+
srMultipleSegmentGraphEndpointAriaLabel: ({
|
|
354
|
+
endpointNumber,
|
|
355
|
+
x,
|
|
356
|
+
y,
|
|
357
|
+
indexOfSegment
|
|
358
|
+
}) => `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`,
|
|
359
|
+
srSegmentGrabHandle: ({
|
|
360
|
+
point1X,
|
|
361
|
+
point1Y,
|
|
362
|
+
point2X,
|
|
363
|
+
point2Y
|
|
364
|
+
}) => `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
|
|
512
365
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
513
366
|
srLinearSystemPoints: ({
|
|
514
367
|
lineNumber,
|
|
@@ -543,30 +396,79 @@ const mockStrings = {
|
|
|
543
396
|
srRayTerminalPoint: ({
|
|
544
397
|
x,
|
|
545
398
|
y
|
|
546
|
-
}) => `Through point at ${x} comma ${y}
|
|
399
|
+
}) => `Through point at ${x} comma ${y}.`,
|
|
400
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
401
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
402
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
403
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
404
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
405
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
406
|
+
srQuadraticGraphVertexQuadrant: ({
|
|
407
|
+
quadrant
|
|
408
|
+
}) => `Vertex is in quadrant ${quadrant}.`,
|
|
409
|
+
srQuadraticTwoXIntercepts: ({
|
|
410
|
+
intercept1,
|
|
411
|
+
intercept2
|
|
412
|
+
}) => `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`,
|
|
413
|
+
srQuadraticOneXIntercept: ({
|
|
414
|
+
intercept
|
|
415
|
+
}) => `The X-intercept is at ${intercept} comma 0.`,
|
|
416
|
+
srQuadraticYIntercept: ({
|
|
417
|
+
intercept
|
|
418
|
+
}) => `The Y-intercept is at 0 comma ${intercept}.`,
|
|
419
|
+
srQuadraticPointOrigin: ({
|
|
420
|
+
pointNumber
|
|
421
|
+
}) => `Point ${pointNumber} on parabola at the origin.`,
|
|
422
|
+
srQuadraticPointAxis: ({
|
|
423
|
+
pointNumber,
|
|
424
|
+
x,
|
|
425
|
+
y
|
|
426
|
+
}) => `Point ${pointNumber} on parabola at ${x} comma ${y}.`,
|
|
427
|
+
srQuadraticPointQuadrant: ({
|
|
428
|
+
pointNumber,
|
|
429
|
+
x,
|
|
430
|
+
y,
|
|
431
|
+
quadrant
|
|
432
|
+
}) => `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`,
|
|
433
|
+
srQuadraticInteractiveElements: ({
|
|
434
|
+
point1X,
|
|
435
|
+
point1Y,
|
|
436
|
+
point2X,
|
|
437
|
+
point2Y,
|
|
438
|
+
point3X,
|
|
439
|
+
point3Y
|
|
440
|
+
}) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`
|
|
547
441
|
// The above strings are used for interactive graph SR descriptions.
|
|
548
442
|
};
|
|
549
443
|
|
|
550
444
|
// This type helps us make sure all error codes are mapped to strings
|
|
551
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Map an error string to a PerseusStrings key
|
|
448
|
+
* that we can use to get the translated error message
|
|
449
|
+
*/
|
|
552
450
|
const errorToString = {
|
|
553
|
-
MISSING_PERCENT_ERROR:
|
|
554
|
-
NEEDS_TO_BE_SIMPLIFIED_ERROR:
|
|
555
|
-
APPROXIMATED_PI_ERROR:
|
|
556
|
-
EXTRA_SYMBOLS_ERROR:
|
|
557
|
-
WRONG_CASE_ERROR:
|
|
558
|
-
WRONG_LETTER_ERROR:
|
|
559
|
-
MULTIPLICATION_SIGN_ERROR:
|
|
560
|
-
INVALID_SELECTION_ERROR:
|
|
561
|
-
CHOOSE_CORRECT_NUM_ERROR:
|
|
562
|
-
NOT_NONE_ABOVE_ERROR:
|
|
563
|
-
FILL_ALL_CELLS_ERROR:
|
|
564
|
-
};
|
|
565
|
-
function mapErrorToString(
|
|
451
|
+
MISSING_PERCENT_ERROR: "MISSING_PERCENT_ERROR",
|
|
452
|
+
NEEDS_TO_BE_SIMPLIFIED_ERROR: "NEEDS_TO_BE_SIMPLFIED_ERROR",
|
|
453
|
+
APPROXIMATED_PI_ERROR: "APPROXIMATED_PI_ERROR",
|
|
454
|
+
EXTRA_SYMBOLS_ERROR: "EXTRA_SYMBOLS_ERROR",
|
|
455
|
+
WRONG_CASE_ERROR: "WRONG_CASE_ERROR",
|
|
456
|
+
WRONG_LETTER_ERROR: "WRONG_LETTER_ERROR",
|
|
457
|
+
MULTIPLICATION_SIGN_ERROR: "MULTIPLICATION_SIGN_ERROR",
|
|
458
|
+
INVALID_SELECTION_ERROR: "invalidSelection",
|
|
459
|
+
CHOOSE_CORRECT_NUM_ERROR: "chooseCorrectNum",
|
|
460
|
+
NOT_NONE_ABOVE_ERROR: "notNoneOfTheAbove",
|
|
461
|
+
FILL_ALL_CELLS_ERROR: "fillAllCells"
|
|
462
|
+
};
|
|
463
|
+
function mapErrorToString(
|
|
464
|
+
// the string representing an error code
|
|
465
|
+
err,
|
|
466
|
+
// the translated Perseus strings
|
|
467
|
+
translatedStrings) {
|
|
566
468
|
if (!err) {
|
|
567
469
|
return err;
|
|
568
470
|
}
|
|
569
|
-
return errorToString[err] || err;
|
|
471
|
+
return translatedStrings[errorToString[err]] || err;
|
|
570
472
|
}
|
|
571
473
|
|
|
572
474
|
/**
|
|
@@ -1830,23 +1732,6 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
1830
1732
|
times: widgetOptions.times
|
|
1831
1733
|
};
|
|
1832
1734
|
};
|
|
1833
|
-
const propUpgrades$2 = {
|
|
1834
|
-
/* c8 ignore next */
|
|
1835
|
-
"1": v0props => ({
|
|
1836
|
-
times: v0props.times,
|
|
1837
|
-
buttonSets: v0props.buttonSets,
|
|
1838
|
-
functions: v0props.functions,
|
|
1839
|
-
buttonsVisible: v0props.buttonsVisible,
|
|
1840
|
-
visibleLabel: v0props.visibleLabel,
|
|
1841
|
-
ariaLabel: v0props.ariaLabel,
|
|
1842
|
-
answerForms: [{
|
|
1843
|
-
considered: "correct",
|
|
1844
|
-
form: v0props.form,
|
|
1845
|
-
simplify: v0props.simplify,
|
|
1846
|
-
value: v0props.value
|
|
1847
|
-
}]
|
|
1848
|
-
})
|
|
1849
|
-
};
|
|
1850
1735
|
const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
1851
1736
|
const deps = useDependencies();
|
|
1852
1737
|
return /*#__PURE__*/React.createElement(Expression, _extends({
|
|
@@ -1886,17 +1771,18 @@ var Expression$1 = {
|
|
|
1886
1771
|
ariaLabel
|
|
1887
1772
|
};
|
|
1888
1773
|
},
|
|
1889
|
-
version:
|
|
1890
|
-
|
|
1891
|
-
minor: 0
|
|
1892
|
-
},
|
|
1893
|
-
propUpgrades: propUpgrades$2,
|
|
1774
|
+
version: expressionLogic.version,
|
|
1775
|
+
propUpgrades: expressionLogic.widgetOptionsUpgrades,
|
|
1894
1776
|
// For use by the editor
|
|
1895
1777
|
isLintable: true,
|
|
1896
1778
|
// TODO(LEMS-2656): remove TS suppression
|
|
1897
1779
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1898
1780
|
scorer: scoreExpression,
|
|
1899
1781
|
// TODO(LEMS-2656): remove TS suppression
|
|
1782
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1783
|
+
validator: validateExpression,
|
|
1784
|
+
getPublicWidgetOptions: getExpressionPublicWidgetOptions,
|
|
1785
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
1900
1786
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
1901
1787
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
1902
1788
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
@@ -8580,7 +8466,7 @@ class Graphie$1 {
|
|
|
8580
8466
|
// We round the coordinates to make testing easier, because trig
|
|
8581
8467
|
// operations return unround numbers.
|
|
8582
8468
|
// TODO(benchristel): move rounding to scalePoint?
|
|
8583
|
-
const round = x => number
|
|
8469
|
+
const round = x => number.round(x, 6);
|
|
8584
8470
|
const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
|
|
8585
8471
|
const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
|
|
8586
8472
|
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
@@ -17336,7 +17222,7 @@ const inlineNodeTypes = {
|
|
|
17336
17222
|
|
|
17337
17223
|
// @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'.
|
|
17338
17224
|
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
17339
|
-
const parse$
|
|
17225
|
+
const parse$3 = (source, state) => {
|
|
17340
17226
|
const paragraphedSource = source + "\n\n";
|
|
17341
17227
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
17342
17228
|
inline: false
|
|
@@ -17419,14 +17305,14 @@ const getContent = ast => {
|
|
|
17419
17305
|
*/
|
|
17420
17306
|
const characterCount = source => {
|
|
17421
17307
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
17422
|
-
const ast = parse$
|
|
17308
|
+
const ast = parse$3(source);
|
|
17423
17309
|
const content = getContent(ast).join("");
|
|
17424
17310
|
return content.length;
|
|
17425
17311
|
};
|
|
17426
17312
|
var PerseusMarkdown = {
|
|
17427
17313
|
characterCount: characterCount,
|
|
17428
17314
|
traverseContent: traverseContent,
|
|
17429
|
-
parse: parse$
|
|
17315
|
+
parse: parse$3,
|
|
17430
17316
|
parseInline: inlineParser,
|
|
17431
17317
|
reactFor: SimpleMarkdown.reactFor,
|
|
17432
17318
|
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
@@ -17568,138 +17454,6 @@ guess, state) {
|
|
|
17568
17454
|
});
|
|
17569
17455
|
}
|
|
17570
17456
|
|
|
17571
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
17572
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
17573
|
-
|
|
17574
|
-
/**
|
|
17575
|
-
* Regex for widget placeholders in a string.
|
|
17576
|
-
*
|
|
17577
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
17578
|
-
* Second capture group is the widget type (ex. "radio)
|
|
17579
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
17580
|
-
*/
|
|
17581
|
-
function getWidgetRegex() {
|
|
17582
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
17583
|
-
}
|
|
17584
|
-
|
|
17585
|
-
/**
|
|
17586
|
-
* Add a widget placeholder using the widget ID.
|
|
17587
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
17588
|
-
*
|
|
17589
|
-
* @param {string} id
|
|
17590
|
-
* @returns {string}
|
|
17591
|
-
*/
|
|
17592
|
-
function addWidget(id) {
|
|
17593
|
-
return `[[☃ ${id}]]`;
|
|
17594
|
-
}
|
|
17595
|
-
|
|
17596
|
-
/**
|
|
17597
|
-
* Get a widget type by a widget's ID
|
|
17598
|
-
* (please don't derive type from ID)
|
|
17599
|
-
*
|
|
17600
|
-
* @param {string} widgetId the ID of the widget
|
|
17601
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17602
|
-
* @returns {string} the widget type (ie "radio")
|
|
17603
|
-
*/
|
|
17604
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
17605
|
-
var _widget$type;
|
|
17606
|
-
const widget = widgetMap[WidgetId];
|
|
17607
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
17608
|
-
}
|
|
17609
|
-
|
|
17610
|
-
/**
|
|
17611
|
-
* Does the content have a specific type of widget?
|
|
17612
|
-
*
|
|
17613
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
17614
|
-
* @param {string} content the string to search through
|
|
17615
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17616
|
-
* @returns {boolean} if the content includes the widget type
|
|
17617
|
-
*/
|
|
17618
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
17619
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
17620
|
-
}
|
|
17621
|
-
|
|
17622
|
-
/**
|
|
17623
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
17624
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
17625
|
-
*
|
|
17626
|
-
* Content should contain Perseus widget placeholders,
|
|
17627
|
-
* which look like: '[[☃ radio 1]]'.
|
|
17628
|
-
*
|
|
17629
|
-
* @param {string} content
|
|
17630
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
17631
|
-
*/
|
|
17632
|
-
function getWidgetIdsFromContent(content) {
|
|
17633
|
-
const widgets = [];
|
|
17634
|
-
const localWidgetRegex = getWidgetRegex();
|
|
17635
|
-
let match = localWidgetRegex.exec(content);
|
|
17636
|
-
while (match !== null) {
|
|
17637
|
-
widgets.push(match[1]);
|
|
17638
|
-
match = localWidgetRegex.exec(content);
|
|
17639
|
-
}
|
|
17640
|
-
return widgets;
|
|
17641
|
-
}
|
|
17642
|
-
|
|
17643
|
-
/**
|
|
17644
|
-
* Get a list of widget IDs from content,
|
|
17645
|
-
* but only for specific widget types
|
|
17646
|
-
*
|
|
17647
|
-
* @param {string} type the type of widget (ie "radio")
|
|
17648
|
-
* @param {string} content the string to parse
|
|
17649
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17650
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
17651
|
-
*/
|
|
17652
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
17653
|
-
const rv = [];
|
|
17654
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
17655
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
17656
|
-
const widget = widgetMap[widgetId];
|
|
17657
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
17658
|
-
rv.push(widgetId);
|
|
17659
|
-
}
|
|
17660
|
-
});
|
|
17661
|
-
return rv;
|
|
17662
|
-
}
|
|
17663
|
-
|
|
17664
|
-
/**
|
|
17665
|
-
* Pull the widget map out of ItemData
|
|
17666
|
-
*
|
|
17667
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
17668
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
17669
|
-
*/
|
|
17670
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
17671
|
-
return itemData.question.widgets;
|
|
17672
|
-
}
|
|
17673
|
-
|
|
17674
|
-
/**
|
|
17675
|
-
* Get the widget information from a WidgetMap
|
|
17676
|
-
*
|
|
17677
|
-
* @param {string} widgetId the ID of the widget
|
|
17678
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
17679
|
-
*/
|
|
17680
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
17681
|
-
var _widgetMap$widgetId;
|
|
17682
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
17683
|
-
}
|
|
17684
|
-
|
|
17685
|
-
/**
|
|
17686
|
-
* Get select widgets from a widget map.
|
|
17687
|
-
*
|
|
17688
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
17689
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
17690
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
17691
|
-
*/
|
|
17692
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
17693
|
-
const widgets = {};
|
|
17694
|
-
widgetIds.forEach(widgetId => {
|
|
17695
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
17696
|
-
if (widget) {
|
|
17697
|
-
widgets[widgetId] = widget;
|
|
17698
|
-
}
|
|
17699
|
-
});
|
|
17700
|
-
return widgets;
|
|
17701
|
-
}
|
|
17702
|
-
|
|
17703
17457
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17704
17458
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17705
17459
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -17804,13 +17558,17 @@ const getWidgetExport = name => {
|
|
|
17804
17558
|
var _widgets$name3;
|
|
17805
17559
|
return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
|
|
17806
17560
|
};
|
|
17561
|
+
const getWidgetValidator = name => {
|
|
17562
|
+
var _widgets$name$validat, _widgets$name4;
|
|
17563
|
+
return (_widgets$name$validat = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.validator) != null ? _widgets$name$validat : null;
|
|
17564
|
+
};
|
|
17807
17565
|
const getWidgetScorer = name => {
|
|
17808
|
-
var _widgets$name$scorer, _widgets$
|
|
17809
|
-
return (_widgets$name$scorer = (_widgets$
|
|
17566
|
+
var _widgets$name$scorer, _widgets$name5;
|
|
17567
|
+
return (_widgets$name$scorer = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.scorer) != null ? _widgets$name$scorer : null;
|
|
17810
17568
|
};
|
|
17811
17569
|
const getPublicWidgetOptionsFunction = name => {
|
|
17812
|
-
var _widgets$name$getPubl, _widgets$
|
|
17813
|
-
return (_widgets$name$getPubl = (_widgets$
|
|
17570
|
+
var _widgets$name$getPubl, _widgets$name6;
|
|
17571
|
+
return (_widgets$name$getPubl = (_widgets$name6 = widgets[name]) == null ? void 0 : _widgets$name6.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
17814
17572
|
};
|
|
17815
17573
|
const getEditor = name => {
|
|
17816
17574
|
return _.has(editors, name) ? editors[name] : null;
|
|
@@ -18115,6 +17873,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18115
17873
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
18116
17874
|
getWidget: getWidget,
|
|
18117
17875
|
getWidgetExport: getWidgetExport,
|
|
17876
|
+
getWidgetValidator: getWidgetValidator,
|
|
18118
17877
|
getWidgetScorer: getWidgetScorer,
|
|
18119
17878
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18120
17879
|
getEditor: getEditor,
|
|
@@ -18137,7 +17896,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18137
17896
|
});
|
|
18138
17897
|
|
|
18139
17898
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
18140
|
-
// @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; }'.
|
|
18141
17899
|
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18142
17900
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
18143
17901
|
const newValues = {};
|
|
@@ -18152,22 +17910,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
|
18152
17910
|
}
|
|
18153
17911
|
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
18154
17912
|
}
|
|
17913
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
18155
17914
|
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
18156
17915
|
});
|
|
18157
17916
|
}
|
|
17917
|
+
|
|
17918
|
+
/**
|
|
17919
|
+
* Checks the given user input to see if any answerable widgets have not been
|
|
17920
|
+
* "filled in" (ie. if they're empty). Another way to think about this
|
|
17921
|
+
* function is that its a check to see if we can score the provided input.
|
|
17922
|
+
*/
|
|
18158
17923
|
function emptyWidgetsFunctional(widgets,
|
|
18159
17924
|
// This is a port of old code, I'm not sure why
|
|
18160
17925
|
// we need widgetIds vs the keys of the widgets object
|
|
18161
17926
|
widgetIds, userInputMap, strings, locale) {
|
|
18162
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
18163
17927
|
return widgetIds.filter(id => {
|
|
18164
|
-
const widget =
|
|
18165
|
-
if (!widget || widget.static) {
|
|
17928
|
+
const widget = widgets[id];
|
|
17929
|
+
if (!widget || widget.static === true) {
|
|
18166
17930
|
// Static widgets shouldn't count as empty
|
|
18167
17931
|
return false;
|
|
18168
17932
|
}
|
|
18169
|
-
const
|
|
18170
|
-
const
|
|
17933
|
+
const validator = getWidgetValidator(widget.type);
|
|
17934
|
+
const userInput = userInputMap[id];
|
|
17935
|
+
const validationData = widget.options;
|
|
17936
|
+
const score = validator == null ? void 0 : validator(userInput, validationData, strings, locale);
|
|
18171
17937
|
if (score) {
|
|
18172
17938
|
return scoreIsEmpty(score);
|
|
18173
17939
|
}
|
|
@@ -18201,13 +17967,18 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
18201
17967
|
});
|
|
18202
17968
|
const widgetScores = {};
|
|
18203
17969
|
gradedWidgetIds.forEach(id => {
|
|
17970
|
+
var _validator;
|
|
18204
17971
|
const widget = upgradedWidgets[id];
|
|
18205
17972
|
if (!widget) {
|
|
18206
17973
|
return;
|
|
18207
17974
|
}
|
|
18208
17975
|
const userInput = userInputMap[id];
|
|
17976
|
+
const validator = getWidgetValidator(widget.type);
|
|
18209
17977
|
const scorer = getWidgetScorer(widget.type);
|
|
18210
|
-
|
|
17978
|
+
|
|
17979
|
+
// We do validation (empty checks) first and then scoring. If
|
|
17980
|
+
// validation fails, it's result is itself a PerseusScore.
|
|
17981
|
+
const score = (_validator = validator == null ? void 0 : validator(userInput, widget.options, strings, locale)) != null ? _validator : scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
|
|
18211
17982
|
if (score != null) {
|
|
18212
17983
|
widgetScores[id] = score;
|
|
18213
17984
|
}
|
|
@@ -19607,7 +19378,7 @@ class Renderer extends React.Component {
|
|
|
19607
19378
|
const apiOptions = this.getApiOptions();
|
|
19608
19379
|
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19609
19380
|
|
|
19610
|
-
// The widget needs access to its "
|
|
19381
|
+
// The widget needs access to its "scoring data" at all times when in review
|
|
19611
19382
|
// mode (which is really just part of its widget info).
|
|
19612
19383
|
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19613
19384
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
@@ -19938,7 +19709,7 @@ function _objectWithoutPropertiesLoose(r, e) {
|
|
|
19938
19709
|
return t;
|
|
19939
19710
|
}
|
|
19940
19711
|
|
|
19941
|
-
const _excluded$
|
|
19712
|
+
const _excluded$4 = ["position", "visibility", "left", "top"];
|
|
19942
19713
|
const zIndex = 10;
|
|
19943
19714
|
let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
|
|
19944
19715
|
HorizontalDirection["Left"] = "left";
|
|
@@ -19989,7 +19760,7 @@ const TooltipArrow = _ref => {
|
|
|
19989
19760
|
left = 100,
|
|
19990
19761
|
top = 0
|
|
19991
19762
|
} = _ref,
|
|
19992
|
-
props = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
19763
|
+
props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
|
|
19993
19764
|
// TODO(aria): Think about adding a box-shadow to the triangle here
|
|
19994
19765
|
// See http://css-tricks.com/triangle-with-shadow/
|
|
19995
19766
|
|
|
@@ -20363,7 +20134,7 @@ InputWithExamples.defaultProps = {
|
|
|
20363
20134
|
className: ""
|
|
20364
20135
|
};
|
|
20365
20136
|
|
|
20366
|
-
const _excluded$
|
|
20137
|
+
const _excluded$3 = ["keypadElement", "onFocus", "value"];
|
|
20367
20138
|
class SimpleKeypadInput extends React.Component {
|
|
20368
20139
|
constructor(...args) {
|
|
20369
20140
|
super(...args);
|
|
@@ -20408,7 +20179,7 @@ class SimpleKeypadInput extends React.Component {
|
|
|
20408
20179
|
onFocus,
|
|
20409
20180
|
value
|
|
20410
20181
|
} = _this$props,
|
|
20411
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
20182
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
|
|
20412
20183
|
return (
|
|
20413
20184
|
/*#__PURE__*/
|
|
20414
20185
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
@@ -22745,7 +22516,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
22745
22516
|
// TODO(LEMS-2656): remove TS suppression
|
|
22746
22517
|
// @ts-expect-error: State is not assignable to ParseState
|
|
22747
22518
|
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
22748
|
-
const parse$
|
|
22519
|
+
const parse$2 = (source, state) => {
|
|
22749
22520
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
22750
22521
|
state = state || {};
|
|
22751
22522
|
const paragraphedSource = source + "\n\n";
|
|
@@ -22755,7 +22526,7 @@ const parse$3 = (source, state) => {
|
|
|
22755
22526
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
22756
22527
|
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
22757
22528
|
var PassageMarkdown = {
|
|
22758
|
-
parse: parse$
|
|
22529
|
+
parse: parse$2,
|
|
22759
22530
|
output,
|
|
22760
22531
|
START_REF_PREFIX,
|
|
22761
22532
|
END_REF_PREFIX,
|
|
@@ -23312,10 +23083,7 @@ var PassageRef$1 = {
|
|
|
23312
23083
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23313
23084
|
summaryText: widgetOptions.summaryText
|
|
23314
23085
|
}),
|
|
23315
|
-
version:
|
|
23316
|
-
major: 0,
|
|
23317
|
-
minor: 1
|
|
23318
|
-
},
|
|
23086
|
+
version: passageRefLogic.version,
|
|
23319
23087
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23320
23088
|
scorer: () => scoreNoop()
|
|
23321
23089
|
};
|
|
@@ -24297,14 +24065,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
|
|
|
24297
24065
|
forwardedRef: ref
|
|
24298
24066
|
})));
|
|
24299
24067
|
|
|
24300
|
-
const _excluded$
|
|
24068
|
+
const _excluded$2 = ["content", "forwardedRef", "showContent"];
|
|
24301
24069
|
const ChoiceNoneAbove = function ChoiceNoneAbove(_ref) {
|
|
24302
24070
|
let {
|
|
24303
24071
|
content,
|
|
24304
24072
|
forwardedRef,
|
|
24305
24073
|
showContent = true
|
|
24306
24074
|
} = _ref,
|
|
24307
|
-
rest = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
24075
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
24308
24076
|
const {
|
|
24309
24077
|
strings
|
|
24310
24078
|
} = usePerseusI18n();
|
|
@@ -24968,7 +24736,7 @@ class Radio$1 extends React.Component {
|
|
|
24968
24736
|
// Current versions of the radio widget always pass in the
|
|
24969
24737
|
// "correct" value through the choices. Old serialized state
|
|
24970
24738
|
// for radio widgets doesn't have this though, so we have to
|
|
24971
|
-
// pull the correctness out of the review mode
|
|
24739
|
+
// pull the correctness out of the review mode scoring data. This
|
|
24972
24740
|
// only works because all of the places we use
|
|
24973
24741
|
// `restoreSerializedState()` also turn on reviewMode, but is
|
|
24974
24742
|
// fine for now.
|
|
@@ -25014,7 +24782,6 @@ Radio$1.defaultProps = {
|
|
|
25014
24782
|
showSolutions: "none"
|
|
25015
24783
|
};
|
|
25016
24784
|
|
|
25017
|
-
const _excluded$4 = ["noneOfTheAbove"];
|
|
25018
24785
|
const {
|
|
25019
24786
|
shuffle: shuffle$2,
|
|
25020
24787
|
random
|
|
@@ -25101,20 +24868,6 @@ const transform = (widgetOptions, strings, problemNum) => {
|
|
|
25101
24868
|
selectedChoices: _.pluck(choices, "correct")
|
|
25102
24869
|
};
|
|
25103
24870
|
};
|
|
25104
|
-
const propUpgrades$1 = {
|
|
25105
|
-
"1": v0props => {
|
|
25106
|
-
const {
|
|
25107
|
-
noneOfTheAbove
|
|
25108
|
-
} = v0props,
|
|
25109
|
-
rest = _objectWithoutPropertiesLoose(v0props, _excluded$4);
|
|
25110
|
-
if (noneOfTheAbove) {
|
|
25111
|
-
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
25112
|
-
}
|
|
25113
|
-
return _extends({}, rest, {
|
|
25114
|
-
hasNoneOfTheAbove: false
|
|
25115
|
-
});
|
|
25116
|
-
}
|
|
25117
|
-
};
|
|
25118
24871
|
var Radio = {
|
|
25119
24872
|
name: "radio",
|
|
25120
24873
|
displayName: "Radio / Multiple choice",
|
|
@@ -25122,15 +24875,15 @@ var Radio = {
|
|
|
25122
24875
|
widget: Radio$1,
|
|
25123
24876
|
transform: transform,
|
|
25124
24877
|
staticTransform: transform,
|
|
25125
|
-
version:
|
|
25126
|
-
|
|
25127
|
-
minor: 0
|
|
25128
|
-
},
|
|
25129
|
-
propUpgrades: propUpgrades$1,
|
|
24878
|
+
version: radioLogic.version,
|
|
24879
|
+
propUpgrades: radioLogic.widgetOptionsUpgrades,
|
|
25130
24880
|
isLintable: true,
|
|
25131
24881
|
// TODO(LEMS-2656): remove TS suppression
|
|
25132
24882
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25133
|
-
scorer: scoreRadio
|
|
24883
|
+
scorer: scoreRadio,
|
|
24884
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
24885
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
24886
|
+
validator: validateRadio
|
|
25134
24887
|
};
|
|
25135
24888
|
|
|
25136
24889
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -25149,24 +24902,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
25149
24902
|
};
|
|
25150
24903
|
};
|
|
25151
24904
|
|
|
25152
|
-
/**
|
|
25153
|
-
* For details on the individual options, see the
|
|
25154
|
-
* PerseusCategorizerWidgetOptions type
|
|
25155
|
-
*/
|
|
25156
|
-
|
|
25157
|
-
/**
|
|
25158
|
-
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
25159
|
-
* the public options that should be exposed to the client.
|
|
25160
|
-
*/
|
|
25161
|
-
function getCategorizerPublicWidgetOptions(options) {
|
|
25162
|
-
return {
|
|
25163
|
-
items: options.items,
|
|
25164
|
-
categories: options.categories,
|
|
25165
|
-
randomizeItems: options.randomizeItems,
|
|
25166
|
-
static: options.static
|
|
25167
|
-
};
|
|
25168
|
-
}
|
|
25169
|
-
|
|
25170
24905
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
25171
24906
|
class Categorizer extends React.Component {
|
|
25172
24907
|
constructor(..._args) {
|
|
@@ -25346,6 +25081,9 @@ var Categorizer$1 = {
|
|
|
25346
25081
|
// TODO(LEMS-2656): remove TS suppression
|
|
25347
25082
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25348
25083
|
scorer: scoreCategorizer,
|
|
25084
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25085
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25086
|
+
validator: validateCategorizer,
|
|
25349
25087
|
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
25350
25088
|
};
|
|
25351
25089
|
|
|
@@ -25783,7 +25521,10 @@ var Dropdown$1 = {
|
|
|
25783
25521
|
transform: optionsTransform,
|
|
25784
25522
|
// TODO(LEMS-2656): remove TS suppression
|
|
25785
25523
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25786
|
-
scorer: scoreDropdown
|
|
25524
|
+
scorer: scoreDropdown,
|
|
25525
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25526
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25527
|
+
validator: validateDropdown
|
|
25787
25528
|
};
|
|
25788
25529
|
|
|
25789
25530
|
const getPromptJSON$j = renderProps => {
|
|
@@ -26132,7 +25873,7 @@ class GradedGroup extends React.Component {
|
|
|
26132
25873
|
DEFAULT_INVALID_MESSAGE_2
|
|
26133
25874
|
} = this.context.strings;
|
|
26134
25875
|
const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
|
|
26135
|
-
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}`;
|
|
25876
|
+
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}`;
|
|
26136
25877
|
this.setState({
|
|
26137
25878
|
status: status,
|
|
26138
25879
|
message: message,
|
|
@@ -26974,7 +26715,7 @@ class FunctionGrapher extends React.Component {
|
|
|
26974
26715
|
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
26975
26716
|
// Always enforce that this is a function
|
|
26976
26717
|
const isFunction = _.all(this._coords(), (otherCoord, j) => {
|
|
26977
|
-
return i === j || !otherCoord || !number
|
|
26718
|
+
return i === j || !otherCoord || !number.equal(coord[0], otherCoord[0]);
|
|
26978
26719
|
});
|
|
26979
26720
|
|
|
26980
26721
|
// Evaluate this criteria before per-point
|
|
@@ -27282,11 +27023,22 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27282
27023
|
|
|
27283
27024
|
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27284
27025
|
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27285
|
-
function scoreGroup(userInput,
|
|
27286
|
-
const scores = scoreWidgetsFunctional(
|
|
27026
|
+
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27027
|
+
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27287
27028
|
return flattenScores(scores);
|
|
27288
27029
|
}
|
|
27289
27030
|
|
|
27031
|
+
function validateGroup(userInput, validationData, strings, locale) {
|
|
27032
|
+
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27033
|
+
if (emptyWidgets.length === 0) {
|
|
27034
|
+
return null;
|
|
27035
|
+
}
|
|
27036
|
+
return {
|
|
27037
|
+
type: "invalid",
|
|
27038
|
+
message: null
|
|
27039
|
+
};
|
|
27040
|
+
}
|
|
27041
|
+
|
|
27290
27042
|
// exports has no 'transform'
|
|
27291
27043
|
|
|
27292
27044
|
class Group extends React.Component {
|
|
@@ -27442,8 +27194,11 @@ var Group$1 = {
|
|
|
27442
27194
|
widget: Group,
|
|
27443
27195
|
traverseChildWidgets: traverseChildWidgets,
|
|
27444
27196
|
// TODO(LEMS-2656): remove TS suppression
|
|
27445
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type '
|
|
27197
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27446
27198
|
scorer: scoreGroup,
|
|
27199
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27200
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27201
|
+
validator: validateGroup,
|
|
27447
27202
|
hidden: true,
|
|
27448
27203
|
isLintable: true
|
|
27449
27204
|
};
|
|
@@ -29779,7 +29534,7 @@ const styles$d = StyleSheet.create({
|
|
|
29779
29534
|
}
|
|
29780
29535
|
});
|
|
29781
29536
|
|
|
29782
|
-
const _excluded$
|
|
29537
|
+
const _excluded$1 = ["onFormatChange", "checkValidity", "useArrowKeys"];
|
|
29783
29538
|
const {
|
|
29784
29539
|
firstNumericalParse,
|
|
29785
29540
|
captureScratchpadTouchStart
|
|
@@ -29917,7 +29672,7 @@ class NumberInput extends React.Component {
|
|
|
29917
29672
|
};
|
|
29918
29673
|
}
|
|
29919
29674
|
componentDidUpdate(prevProps) {
|
|
29920
|
-
if (!number
|
|
29675
|
+
if (!number.equal(this.getValue(), this.props.value)) {
|
|
29921
29676
|
this._setValue(this.props.value, this.state.format);
|
|
29922
29677
|
}
|
|
29923
29678
|
}
|
|
@@ -29933,7 +29688,7 @@ class NumberInput extends React.Component {
|
|
|
29933
29688
|
classes = classes + " " + this.props.className;
|
|
29934
29689
|
}
|
|
29935
29690
|
const _this$props = this.props,
|
|
29936
|
-
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
29691
|
+
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
|
|
29937
29692
|
return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
|
|
29938
29693
|
className: classes,
|
|
29939
29694
|
type: "text",
|
|
@@ -30331,6 +30086,96 @@ function getInterceptStringForLine(line, strings, locale) {
|
|
|
30331
30086
|
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30332
30087
|
});
|
|
30333
30088
|
}
|
|
30089
|
+
function getCoordQuadrant(coord) {
|
|
30090
|
+
const [unroundedX, unroundedY] = coord;
|
|
30091
|
+
const x = Number(unroundedX.toFixed(3));
|
|
30092
|
+
const y = Number(unroundedY.toFixed(3));
|
|
30093
|
+
if (x === 0 && y === 0) {
|
|
30094
|
+
return "origin";
|
|
30095
|
+
}
|
|
30096
|
+
if (y === 0) {
|
|
30097
|
+
return "x-axis";
|
|
30098
|
+
}
|
|
30099
|
+
if (x === 0) {
|
|
30100
|
+
return "y-axis";
|
|
30101
|
+
}
|
|
30102
|
+
if (x > 0 && y > 0) {
|
|
30103
|
+
return 1;
|
|
30104
|
+
}
|
|
30105
|
+
if (x < 0 && y > 0) {
|
|
30106
|
+
return 2;
|
|
30107
|
+
}
|
|
30108
|
+
if (x < 0 && y < 0) {
|
|
30109
|
+
return 3;
|
|
30110
|
+
}
|
|
30111
|
+
return 4;
|
|
30112
|
+
}
|
|
30113
|
+
function getQuadraticVertexString(vertex, strings) {
|
|
30114
|
+
const location = getCoordQuadrant(vertex);
|
|
30115
|
+
switch (location) {
|
|
30116
|
+
case "origin":
|
|
30117
|
+
return strings.srQuadraticGraphVertexOrigin;
|
|
30118
|
+
case "x-axis":
|
|
30119
|
+
return strings.srQuadraticGraphVertexXAxis;
|
|
30120
|
+
case "y-axis":
|
|
30121
|
+
return strings.srQuadraticGraphVertexYAxis;
|
|
30122
|
+
default:
|
|
30123
|
+
return strings.srQuadraticGraphVertexQuadrant({
|
|
30124
|
+
quadrant: location
|
|
30125
|
+
});
|
|
30126
|
+
}
|
|
30127
|
+
}
|
|
30128
|
+
function getQuadraticPointString(pointNumber, coord, strings, locale) {
|
|
30129
|
+
const location = getCoordQuadrant(coord);
|
|
30130
|
+
const [x, y] = coord;
|
|
30131
|
+
switch (location) {
|
|
30132
|
+
case "origin":
|
|
30133
|
+
return strings.srQuadraticPointOrigin({
|
|
30134
|
+
pointNumber: pointNumber
|
|
30135
|
+
});
|
|
30136
|
+
case "x-axis":
|
|
30137
|
+
case "y-axis":
|
|
30138
|
+
return strings.srQuadraticPointAxis({
|
|
30139
|
+
pointNumber: pointNumber,
|
|
30140
|
+
x: srFormatNumber(x, locale),
|
|
30141
|
+
y: srFormatNumber(y, locale)
|
|
30142
|
+
});
|
|
30143
|
+
default:
|
|
30144
|
+
// return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
|
|
30145
|
+
return strings.srQuadraticPointQuadrant({
|
|
30146
|
+
pointNumber: pointNumber,
|
|
30147
|
+
quadrant: location,
|
|
30148
|
+
x: srFormatNumber(x, locale),
|
|
30149
|
+
y: srFormatNumber(y, locale)
|
|
30150
|
+
});
|
|
30151
|
+
}
|
|
30152
|
+
}
|
|
30153
|
+
function getQuadraticXIntercepts(
|
|
30154
|
+
// Coefficients of the quadratic equation
|
|
30155
|
+
a, b, c) {
|
|
30156
|
+
// If a is 0, the equation is a line and has maybe only one x-intercept.
|
|
30157
|
+
if (a === 0) {
|
|
30158
|
+
// If b is 0, the equation a horizontal line and has no x-intercepts.
|
|
30159
|
+
if (b === 0) {
|
|
30160
|
+
return [];
|
|
30161
|
+
}
|
|
30162
|
+
return [-c / b];
|
|
30163
|
+
}
|
|
30164
|
+
|
|
30165
|
+
// Find the x-intercepts by plugging in y = 0 into the quadratic equation.
|
|
30166
|
+
// 0 = ax^2 + bx + c
|
|
30167
|
+
// x = (-b ± sqrt(b^2 - 4ac)) / 2a
|
|
30168
|
+
const discriminant = b * b - 4 * a * c;
|
|
30169
|
+
if (discriminant < 0) {
|
|
30170
|
+
return [];
|
|
30171
|
+
}
|
|
30172
|
+
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
|
|
30173
|
+
const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
|
|
30174
|
+
if (x1 === x2) {
|
|
30175
|
+
return [x1];
|
|
30176
|
+
}
|
|
30177
|
+
return [x1, x2];
|
|
30178
|
+
}
|
|
30334
30179
|
|
|
30335
30180
|
const {
|
|
30336
30181
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
@@ -30748,12 +30593,12 @@ const MafsCssTransformWrapper = ({
|
|
|
30748
30593
|
}
|
|
30749
30594
|
}, children);
|
|
30750
30595
|
|
|
30751
|
-
const _excluded
|
|
30596
|
+
const _excluded = ["children"];
|
|
30752
30597
|
const TextLabel = _ref => {
|
|
30753
30598
|
let {
|
|
30754
30599
|
children
|
|
30755
30600
|
} = _ref,
|
|
30756
|
-
rest = _objectWithoutPropertiesLoose(_ref, _excluded
|
|
30601
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
|
|
30757
30602
|
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
30758
30603
|
size: 16,
|
|
30759
30604
|
svgTextProps: {
|
|
@@ -32135,7 +31980,9 @@ function renderLinearGraph(state, dispatch) {
|
|
|
32135
31980
|
graphState: state,
|
|
32136
31981
|
dispatch: dispatch
|
|
32137
31982
|
}),
|
|
32138
|
-
interactiveElementsDescription:
|
|
31983
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearGraphDescription, {
|
|
31984
|
+
state: state
|
|
31985
|
+
})
|
|
32139
31986
|
};
|
|
32140
31987
|
}
|
|
32141
31988
|
const LinearGraph = (props, key) => {
|
|
@@ -32154,33 +32001,29 @@ const LinearGraph = (props, key) => {
|
|
|
32154
32001
|
const interceptDescriptionId = id + "-intercept";
|
|
32155
32002
|
const slopeDescriptionId = id + "-slope";
|
|
32156
32003
|
|
|
32157
|
-
// Aria
|
|
32158
|
-
const
|
|
32159
|
-
|
|
32160
|
-
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32165
|
-
|
|
32166
|
-
|
|
32167
|
-
point2X: srFormatNumber(line[1][0], locale),
|
|
32168
|
-
point2Y: srFormatNumber(line[1][1], locale)
|
|
32004
|
+
// Aria strings
|
|
32005
|
+
const {
|
|
32006
|
+
srLinearGraph,
|
|
32007
|
+
srLinearGraphPoints,
|
|
32008
|
+
srLinearGrabHandle,
|
|
32009
|
+
slopeString,
|
|
32010
|
+
interceptString
|
|
32011
|
+
} = describeLinearGraph(props.graphState, {
|
|
32012
|
+
strings,
|
|
32013
|
+
locale
|
|
32169
32014
|
});
|
|
32170
|
-
const slopeString = getSlopeStringForLine(line, strings);
|
|
32171
|
-
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32172
32015
|
|
|
32173
32016
|
// Linear graphs only have one line
|
|
32174
32017
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
32175
32018
|
// and another action for moving multiple lines
|
|
32176
32019
|
return /*#__PURE__*/React.createElement("g", {
|
|
32177
32020
|
// Outer line minimal description
|
|
32178
|
-
"aria-label":
|
|
32021
|
+
"aria-label": srLinearGraph,
|
|
32179
32022
|
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
32180
32023
|
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
32181
32024
|
key: 0,
|
|
32182
32025
|
ariaLabels: {
|
|
32183
|
-
grabHandleAriaLabel:
|
|
32026
|
+
grabHandleAriaLabel: srLinearGrabHandle
|
|
32184
32027
|
},
|
|
32185
32028
|
ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
|
|
32186
32029
|
points: line,
|
|
@@ -32196,7 +32039,7 @@ const LinearGraph = (props, key) => {
|
|
|
32196
32039
|
}), /*#__PURE__*/React.createElement("g", {
|
|
32197
32040
|
id: pointsDescriptionId,
|
|
32198
32041
|
style: a11y.srOnly
|
|
32199
|
-
},
|
|
32042
|
+
}, srLinearGraphPoints), /*#__PURE__*/React.createElement("g", {
|
|
32200
32043
|
id: interceptDescriptionId,
|
|
32201
32044
|
style: a11y.srOnly
|
|
32202
32045
|
}, interceptString), /*#__PURE__*/React.createElement("g", {
|
|
@@ -32204,6 +32047,55 @@ const LinearGraph = (props, key) => {
|
|
|
32204
32047
|
style: a11y.srOnly
|
|
32205
32048
|
}, slopeString));
|
|
32206
32049
|
};
|
|
32050
|
+
function LinearGraphDescription({
|
|
32051
|
+
state
|
|
32052
|
+
}) {
|
|
32053
|
+
// The reason that LinearGraphDescription is a component (rather than a
|
|
32054
|
+
// function that returns a string) is because it needs to use a
|
|
32055
|
+
// hook: `usePerseusI18n`.
|
|
32056
|
+
const i18n = usePerseusI18n();
|
|
32057
|
+
const strings = describeLinearGraph(state, i18n);
|
|
32058
|
+
return strings.srLinearInteractiveElement;
|
|
32059
|
+
}
|
|
32060
|
+
|
|
32061
|
+
// Exported for testing
|
|
32062
|
+
function describeLinearGraph(state, i18n) {
|
|
32063
|
+
const {
|
|
32064
|
+
coords: line
|
|
32065
|
+
} = state;
|
|
32066
|
+
const {
|
|
32067
|
+
strings,
|
|
32068
|
+
locale
|
|
32069
|
+
} = i18n;
|
|
32070
|
+
|
|
32071
|
+
// Aria label strings
|
|
32072
|
+
const srLinearGraph = strings.srLinearGraph;
|
|
32073
|
+
const srLinearGraphPoints = strings.srLinearGraphPoints({
|
|
32074
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32075
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32076
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32077
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32078
|
+
});
|
|
32079
|
+
const srLinearGrabHandle = strings.srLinearGrabHandle({
|
|
32080
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32081
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32082
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32083
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32084
|
+
});
|
|
32085
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32086
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32087
|
+
const srLinearInteractiveElement = strings.srInteractiveElements({
|
|
32088
|
+
elements: [srLinearGraph, srLinearGraphPoints].join(" ")
|
|
32089
|
+
});
|
|
32090
|
+
return {
|
|
32091
|
+
srLinearGraph,
|
|
32092
|
+
srLinearGraphPoints,
|
|
32093
|
+
srLinearGrabHandle,
|
|
32094
|
+
slopeString,
|
|
32095
|
+
interceptString,
|
|
32096
|
+
srLinearInteractiveElement
|
|
32097
|
+
};
|
|
32098
|
+
}
|
|
32207
32099
|
|
|
32208
32100
|
function renderLinearSystemGraph(state, dispatch) {
|
|
32209
32101
|
return {
|
|
@@ -32752,13 +32644,13 @@ const hasFocusVisible = element => {
|
|
|
32752
32644
|
}
|
|
32753
32645
|
};
|
|
32754
32646
|
|
|
32755
|
-
function renderQuadraticGraph(state, dispatch) {
|
|
32647
|
+
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
32756
32648
|
return {
|
|
32757
32649
|
graph: /*#__PURE__*/React.createElement(QuadraticGraph, {
|
|
32758
32650
|
graphState: state,
|
|
32759
32651
|
dispatch: dispatch
|
|
32760
32652
|
}),
|
|
32761
|
-
interactiveElementsDescription:
|
|
32653
|
+
interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
|
|
32762
32654
|
};
|
|
32763
32655
|
}
|
|
32764
32656
|
function QuadraticGraph(props) {
|
|
@@ -32769,6 +32661,14 @@ function QuadraticGraph(props) {
|
|
|
32769
32661
|
const {
|
|
32770
32662
|
coords
|
|
32771
32663
|
} = graphState;
|
|
32664
|
+
const {
|
|
32665
|
+
strings,
|
|
32666
|
+
locale
|
|
32667
|
+
} = usePerseusI18n();
|
|
32668
|
+
const id = React.useId();
|
|
32669
|
+
const quadraticDirectionId = id + "-direction";
|
|
32670
|
+
const quadraticVertexId = id + "-vertex";
|
|
32671
|
+
const quadraticInterceptsId = id + "-intercepts";
|
|
32772
32672
|
|
|
32773
32673
|
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
32774
32674
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
@@ -32784,15 +32684,46 @@ function QuadraticGraph(props) {
|
|
|
32784
32684
|
|
|
32785
32685
|
// Calculate the y value based on the current x value
|
|
32786
32686
|
const y = x => (a * x + b) * x + c;
|
|
32787
|
-
|
|
32687
|
+
|
|
32688
|
+
// Aria strings
|
|
32689
|
+
const {
|
|
32690
|
+
srQuadraticGraph,
|
|
32691
|
+
srQuadraticDirection,
|
|
32692
|
+
srQuadraticVertex,
|
|
32693
|
+
srQuadraticXIntercepts,
|
|
32694
|
+
srQuadraticYIntercept
|
|
32695
|
+
} = describeQuadraticGraph(graphState, {
|
|
32696
|
+
strings,
|
|
32697
|
+
locale
|
|
32698
|
+
});
|
|
32699
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32700
|
+
// Outer graph minimal description
|
|
32701
|
+
"aria-label": srQuadraticGraph,
|
|
32702
|
+
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
32703
|
+
}, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
32788
32704
|
y: y,
|
|
32789
32705
|
color: color.blue
|
|
32790
|
-
}), coords.map((coord, i) =>
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32706
|
+
}), coords.map((coord, i) => {
|
|
32707
|
+
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
32708
|
+
// Vertex might not exist if the quadratic graph is a line
|
|
32709
|
+
const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
|
|
32710
|
+
return /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
32711
|
+
key: "point-" + i,
|
|
32712
|
+
ariaLabel: `${srQuadraticPoint}${srVertex}`,
|
|
32713
|
+
point: coord,
|
|
32714
|
+
sequenceNumber: i + 1,
|
|
32715
|
+
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
32716
|
+
});
|
|
32717
|
+
}), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
|
|
32718
|
+
id: quadraticDirectionId,
|
|
32719
|
+
style: a11y.srOnly
|
|
32720
|
+
}, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React.createElement("g", {
|
|
32721
|
+
id: quadraticVertexId,
|
|
32722
|
+
style: a11y.srOnly
|
|
32723
|
+
}, srQuadraticVertex), /*#__PURE__*/React.createElement("g", {
|
|
32724
|
+
id: quadraticInterceptsId,
|
|
32725
|
+
style: a11y.srOnly
|
|
32726
|
+
}, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
|
|
32796
32727
|
}
|
|
32797
32728
|
|
|
32798
32729
|
// Get the quadratic coefficients from the 3 control points
|
|
@@ -32815,6 +32746,60 @@ const getQuadraticCoefficients$2 = coords => {
|
|
|
32815
32746
|
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;
|
|
32816
32747
|
return [a, b, c];
|
|
32817
32748
|
};
|
|
32749
|
+
function getQuadraticGraphDescription(state, i18n) {
|
|
32750
|
+
const strings = describeQuadraticGraph(state, i18n);
|
|
32751
|
+
return strings.srQuadraticInteractiveElements;
|
|
32752
|
+
}
|
|
32753
|
+
// Exported for testing
|
|
32754
|
+
function describeQuadraticGraph(state, i18n) {
|
|
32755
|
+
const {
|
|
32756
|
+
strings,
|
|
32757
|
+
locale
|
|
32758
|
+
} = i18n;
|
|
32759
|
+
const coeffs = getQuadraticCoefficients$2(state.coords);
|
|
32760
|
+
const [a, b, c] = coeffs != null ? coeffs : [0, 0, 0];
|
|
32761
|
+
const vertex = [-b / (2 * a), c - b * b / (4 * a)];
|
|
32762
|
+
const xIntercepts = getQuadraticXIntercepts(a, b, c);
|
|
32763
|
+
|
|
32764
|
+
// Aria label strings
|
|
32765
|
+
const srQuadraticGraph = strings.srQuadraticGraph;
|
|
32766
|
+
const srQuadraticFaceUp = strings.srQuadraticFaceUp;
|
|
32767
|
+
const srQuadraticFaceDown = strings.srQuadraticFaceDown;
|
|
32768
|
+
// The graph only has a direction if it is not a line.
|
|
32769
|
+
const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
|
|
32770
|
+
// Only describe vertex if the quadratic graph is not a line.
|
|
32771
|
+
// (Undefined means the quadratic graph is a line and has no vertex.)
|
|
32772
|
+
const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
|
|
32773
|
+
// Undefined means the quadratic graph has no x-intercepts,
|
|
32774
|
+
// such as when the graph is a horizontal line.
|
|
32775
|
+
const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
|
|
32776
|
+
intercept1: srFormatNumber(xIntercepts[0], locale),
|
|
32777
|
+
intercept2: srFormatNumber(xIntercepts[1], locale)
|
|
32778
|
+
}) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
|
|
32779
|
+
intercept: srFormatNumber(xIntercepts[0], locale)
|
|
32780
|
+
}) : undefined;
|
|
32781
|
+
const srQuadraticYIntercept = strings.srQuadraticYIntercept({
|
|
32782
|
+
intercept: srFormatNumber(c, locale)
|
|
32783
|
+
});
|
|
32784
|
+
const srQuadraticInteractiveElements = strings.srInteractiveElements({
|
|
32785
|
+
elements: strings.srQuadraticInteractiveElements({
|
|
32786
|
+
point1X: srFormatNumber(state.coords[0][0], locale),
|
|
32787
|
+
point1Y: srFormatNumber(state.coords[0][1], locale),
|
|
32788
|
+
point2X: srFormatNumber(state.coords[1][0], locale),
|
|
32789
|
+
point2Y: srFormatNumber(state.coords[1][1], locale),
|
|
32790
|
+
point3X: srFormatNumber(state.coords[2][0], locale),
|
|
32791
|
+
point3Y: srFormatNumber(state.coords[2][1], locale)
|
|
32792
|
+
})
|
|
32793
|
+
});
|
|
32794
|
+
return {
|
|
32795
|
+
srQuadraticGraph,
|
|
32796
|
+
srQuadraticDirection,
|
|
32797
|
+
srQuadraticVertex,
|
|
32798
|
+
srQuadraticXIntercepts,
|
|
32799
|
+
srQuadraticYIntercept,
|
|
32800
|
+
srQuadraticInteractiveElements
|
|
32801
|
+
};
|
|
32802
|
+
}
|
|
32818
32803
|
|
|
32819
32804
|
function renderRayGraph(state, dispatch) {
|
|
32820
32805
|
return {
|
|
@@ -32940,17 +32925,69 @@ function renderSegmentGraph(state, dispatch) {
|
|
|
32940
32925
|
graphState: state,
|
|
32941
32926
|
dispatch: dispatch
|
|
32942
32927
|
}),
|
|
32943
|
-
interactiveElementsDescription:
|
|
32928
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(SegmentGraphDescription, {
|
|
32929
|
+
state: state
|
|
32930
|
+
})
|
|
32944
32931
|
};
|
|
32945
32932
|
}
|
|
32946
|
-
const SegmentGraph =
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
32933
|
+
const SegmentGraph = ({
|
|
32934
|
+
dispatch,
|
|
32935
|
+
graphState
|
|
32936
|
+
}) => {
|
|
32950
32937
|
const {
|
|
32951
32938
|
coords: segments
|
|
32952
|
-
} =
|
|
32953
|
-
|
|
32939
|
+
} = graphState;
|
|
32940
|
+
const {
|
|
32941
|
+
strings,
|
|
32942
|
+
locale
|
|
32943
|
+
} = usePerseusI18n();
|
|
32944
|
+
const segmentUniqueId = React.useId();
|
|
32945
|
+
const lengthDescriptionId = segmentUniqueId + "-length";
|
|
32946
|
+
const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
|
|
32947
|
+
function getWholeSegmentGraphAriaLabel() {
|
|
32948
|
+
return (segments == null ? void 0 : segments.length) > 1 ? strings.srMultipleSegmentGraphAriaLabel({
|
|
32949
|
+
countOfSegments: segments.length
|
|
32950
|
+
}) : strings.srSingleSegmentGraphAriaLabel;
|
|
32951
|
+
}
|
|
32952
|
+
const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
|
|
32953
|
+
function getIndividualSegmentAriaLabel(segment, index) {
|
|
32954
|
+
if (segments.length === 1) {
|
|
32955
|
+
return strings.srSingleSegmentLabel({
|
|
32956
|
+
point1X: srFormatNumber(segments[0][0][X], locale),
|
|
32957
|
+
point1Y: srFormatNumber(segments[0][0][Y], locale),
|
|
32958
|
+
point2X: srFormatNumber(segments[0][1][X], locale),
|
|
32959
|
+
point2Y: srFormatNumber(segments[0][1][Y], locale)
|
|
32960
|
+
});
|
|
32961
|
+
}
|
|
32962
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
32963
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
32964
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
32965
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
32966
|
+
point2Y: srFormatNumber(segment[1][Y], locale),
|
|
32967
|
+
indexOfSegment: index + 1
|
|
32968
|
+
});
|
|
32969
|
+
}
|
|
32970
|
+
function getWholeSegmentGraphAriaDescription() {
|
|
32971
|
+
return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
|
|
32972
|
+
}
|
|
32973
|
+
function formatSegment(endpointNumber, x, y, index) {
|
|
32974
|
+
const segObj = {
|
|
32975
|
+
endpointNumber: endpointNumber,
|
|
32976
|
+
x: srFormatNumber(x, locale),
|
|
32977
|
+
y: srFormatNumber(y, locale)
|
|
32978
|
+
};
|
|
32979
|
+
return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel(_extends({}, segObj, {
|
|
32980
|
+
indexOfSegment: index
|
|
32981
|
+
})) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
|
|
32982
|
+
}
|
|
32983
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32984
|
+
"aria-label": wholeSegmentGraphAriaLabel,
|
|
32985
|
+
"aria-describedby": wholeGraphDescriptionId
|
|
32986
|
+
}, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement("g", {
|
|
32987
|
+
"aria-label": getIndividualSegmentAriaLabel(segment, i),
|
|
32988
|
+
"aria-describedby": lengthDescriptionId,
|
|
32989
|
+
key: `${segmentUniqueId}-${i}`
|
|
32990
|
+
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
32954
32991
|
key: i,
|
|
32955
32992
|
points: segment,
|
|
32956
32993
|
onMoveLine: delta => {
|
|
@@ -32958,9 +32995,57 @@ const SegmentGraph = props => {
|
|
|
32958
32995
|
},
|
|
32959
32996
|
onMovePoint: (endpointIndex, destination) => {
|
|
32960
32997
|
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
32998
|
+
},
|
|
32999
|
+
ariaLabels: {
|
|
33000
|
+
point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
|
|
33001
|
+
point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
|
|
33002
|
+
grabHandleAriaLabel: strings.srSegmentGrabHandle({
|
|
33003
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33004
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33005
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33006
|
+
point2Y: srFormatNumber(segment[1][Y], locale)
|
|
33007
|
+
})
|
|
32961
33008
|
}
|
|
32962
|
-
})
|
|
33009
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
33010
|
+
id: lengthDescriptionId,
|
|
33011
|
+
style: a11y.srOnly
|
|
33012
|
+
}, strings.srSegmentLength({
|
|
33013
|
+
length: srFormatNumber(getLengthOfSegment(segment), locale)
|
|
33014
|
+
})))), /*#__PURE__*/React.createElement("g", {
|
|
33015
|
+
style: a11y.srOnly,
|
|
33016
|
+
id: wholeGraphDescriptionId
|
|
33017
|
+
}, getWholeSegmentGraphAriaDescription()));
|
|
32963
33018
|
};
|
|
33019
|
+
function getLengthOfSegment(segment) {
|
|
33020
|
+
return point.distanceToPoint(...segment);
|
|
33021
|
+
}
|
|
33022
|
+
function SegmentGraphDescription({
|
|
33023
|
+
state
|
|
33024
|
+
}) {
|
|
33025
|
+
// The reason that SegmentGraphDescription is a component (rather than a
|
|
33026
|
+
// function that returns a string) is because it needs to use a
|
|
33027
|
+
// hook: `usePerseusI18n`.
|
|
33028
|
+
const i18n = usePerseusI18n();
|
|
33029
|
+
return describeSegmentGraph(state, i18n);
|
|
33030
|
+
}
|
|
33031
|
+
|
|
33032
|
+
// Exported for testing
|
|
33033
|
+
function describeSegmentGraph(state, i18n) {
|
|
33034
|
+
const {
|
|
33035
|
+
strings,
|
|
33036
|
+
locale
|
|
33037
|
+
} = i18n;
|
|
33038
|
+
const segmentDescriptions = state.coords.map(([point1, point2], index) => strings.srMultipleSegmentIndividualLabel({
|
|
33039
|
+
point1X: srFormatNumber(point1[X], locale),
|
|
33040
|
+
point1Y: srFormatNumber(point1[Y], locale),
|
|
33041
|
+
point2X: srFormatNumber(point2[X], locale),
|
|
33042
|
+
point2Y: srFormatNumber(point2[Y], locale),
|
|
33043
|
+
indexOfSegment: index + 1
|
|
33044
|
+
}));
|
|
33045
|
+
return strings.srInteractiveElements({
|
|
33046
|
+
elements: segmentDescriptions.join(" ")
|
|
33047
|
+
});
|
|
33048
|
+
}
|
|
32964
33049
|
|
|
32965
33050
|
function renderSinusoidGraph(state, dispatch) {
|
|
32966
33051
|
return {
|
|
@@ -33236,9 +33321,10 @@ const MafsGraph = props => {
|
|
|
33236
33321
|
x: viewboxX,
|
|
33237
33322
|
y: viewboxY
|
|
33238
33323
|
};
|
|
33324
|
+
const i18n = usePerseusI18n();
|
|
33239
33325
|
const {
|
|
33240
33326
|
strings
|
|
33241
|
-
} =
|
|
33327
|
+
} = i18n;
|
|
33242
33328
|
const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
|
|
33243
33329
|
useOnMountEffect(() => {
|
|
33244
33330
|
analytics.onAnalyticsEvent({
|
|
@@ -33255,7 +33341,8 @@ const MafsGraph = props => {
|
|
|
33255
33341
|
interactiveElementsDescription
|
|
33256
33342
|
} = renderGraphElements({
|
|
33257
33343
|
state,
|
|
33258
|
-
dispatch
|
|
33344
|
+
dispatch,
|
|
33345
|
+
i18n
|
|
33259
33346
|
});
|
|
33260
33347
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
33261
33348
|
value: {
|
|
@@ -33633,7 +33720,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
33633
33720
|
const renderGraphElements = props => {
|
|
33634
33721
|
const {
|
|
33635
33722
|
state,
|
|
33636
|
-
dispatch
|
|
33723
|
+
dispatch,
|
|
33724
|
+
i18n
|
|
33637
33725
|
} = props;
|
|
33638
33726
|
const {
|
|
33639
33727
|
type
|
|
@@ -33656,7 +33744,7 @@ const renderGraphElements = props => {
|
|
|
33656
33744
|
case "circle":
|
|
33657
33745
|
return renderCircleGraph(state, dispatch);
|
|
33658
33746
|
case "quadratic":
|
|
33659
|
-
return renderQuadraticGraph(state, dispatch);
|
|
33747
|
+
return renderQuadraticGraph(state, dispatch, i18n);
|
|
33660
33748
|
case "sinusoid":
|
|
33661
33749
|
return renderSinusoidGraph(state, dispatch);
|
|
33662
33750
|
case "none":
|
|
@@ -35188,7 +35276,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
35188
35276
|
};
|
|
35189
35277
|
this.setTrashCanVisibility = opacity => {
|
|
35190
35278
|
const graphie = this.graphie;
|
|
35191
|
-
if (number
|
|
35279
|
+
if (number.equal(opacity, 0)) {
|
|
35192
35280
|
if (this.trashCan) {
|
|
35193
35281
|
this.trashCan.remove();
|
|
35194
35282
|
this.trashCan = null;
|
|
@@ -37238,19 +37326,21 @@ class LabelImage extends React.Component {
|
|
|
37238
37326
|
this._mounted = false;
|
|
37239
37327
|
}
|
|
37240
37328
|
getUserInput() {
|
|
37241
|
-
const {
|
|
37242
|
-
markers
|
|
37243
|
-
} = this.props;
|
|
37244
37329
|
return {
|
|
37245
|
-
markers
|
|
37330
|
+
markers: this.props.markers.map(marker => ({
|
|
37331
|
+
selected: marker.selected,
|
|
37332
|
+
label: marker.label
|
|
37333
|
+
}))
|
|
37246
37334
|
};
|
|
37247
37335
|
}
|
|
37248
37336
|
getPromptJSON() {
|
|
37249
37337
|
return getPromptJSON$a(this.props, this.getUserInput());
|
|
37250
37338
|
}
|
|
37251
37339
|
|
|
37252
|
-
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37253
|
-
|
|
37340
|
+
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37341
|
+
// Also consider how scoreMarker is being called as it seems to require the marker.answers property.
|
|
37342
|
+
// Removed rubric parameter, but it gets a full widget options object from the renderer
|
|
37343
|
+
showRationalesForCurrentlySelectedChoices() {
|
|
37254
37344
|
const {
|
|
37255
37345
|
markers
|
|
37256
37346
|
} = this.props;
|
|
@@ -37258,7 +37348,7 @@ class LabelImage extends React.Component {
|
|
|
37258
37348
|
onChange
|
|
37259
37349
|
} = this.props;
|
|
37260
37350
|
const updatedMarkers = markers.map(marker => {
|
|
37261
|
-
const score = scoreLabelImageMarker(marker);
|
|
37351
|
+
const score = scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37262
37352
|
return _extends({}, marker, {
|
|
37263
37353
|
// Reveal correctness state for markers with answers.
|
|
37264
37354
|
showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
|
|
@@ -37364,6 +37454,7 @@ class LabelImage extends React.Component {
|
|
|
37364
37454
|
selected: selected.length ? selected : undefined
|
|
37365
37455
|
}));
|
|
37366
37456
|
}
|
|
37457
|
+
// TODO(LEMS-2723): Investigate if possible to change this to not require answers
|
|
37367
37458
|
renderMarkers() {
|
|
37368
37459
|
const {
|
|
37369
37460
|
markers,
|
|
@@ -37405,7 +37496,7 @@ class LabelImage extends React.Component {
|
|
|
37405
37496
|
bottom: "top"
|
|
37406
37497
|
}[markerPosition];
|
|
37407
37498
|
}
|
|
37408
|
-
const score = scoreLabelImageMarker(marker);
|
|
37499
|
+
const score = scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37409
37500
|
// Once the question is answered, show markers
|
|
37410
37501
|
// with correct answers, otherwise passthrough
|
|
37411
37502
|
// the correctness state.
|
|
@@ -38950,14 +39041,16 @@ var Matrix$1 = {
|
|
|
38950
39041
|
isLintable: true,
|
|
38951
39042
|
// TODO(LEMS-2656): remove TS suppression
|
|
38952
39043
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
38953
|
-
scorer: scoreMatrix
|
|
39044
|
+
scorer: scoreMatrix,
|
|
39045
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39046
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39047
|
+
validator: validateMatrix
|
|
38954
39048
|
};
|
|
38955
39049
|
|
|
38956
39050
|
const getPromptJSON$7 = () => {
|
|
38957
39051
|
return getUnsupportedPromptJSON("measurer");
|
|
38958
39052
|
};
|
|
38959
39053
|
|
|
38960
|
-
const _excluded$1 = ["imageUrl", "imageTop", "imageLeft"];
|
|
38961
39054
|
const defaultImage = {
|
|
38962
39055
|
url: null,
|
|
38963
39056
|
top: 0,
|
|
@@ -39062,33 +39155,13 @@ Measurer.defaultProps = {
|
|
|
39062
39155
|
rulerPixels: 40,
|
|
39063
39156
|
rulerLength: 10
|
|
39064
39157
|
};
|
|
39065
|
-
const propUpgrades = {
|
|
39066
|
-
"1": v0props => {
|
|
39067
|
-
const {
|
|
39068
|
-
imageUrl,
|
|
39069
|
-
imageTop,
|
|
39070
|
-
imageLeft
|
|
39071
|
-
} = v0props,
|
|
39072
|
-
rest = _objectWithoutPropertiesLoose(v0props, _excluded$1);
|
|
39073
|
-
return _extends({}, rest, {
|
|
39074
|
-
image: {
|
|
39075
|
-
url: imageUrl,
|
|
39076
|
-
top: imageTop,
|
|
39077
|
-
left: imageLeft
|
|
39078
|
-
}
|
|
39079
|
-
});
|
|
39080
|
-
}
|
|
39081
|
-
};
|
|
39082
39158
|
var Measurer$1 = {
|
|
39083
39159
|
name: "measurer",
|
|
39084
39160
|
displayName: "Measurer",
|
|
39085
39161
|
hidden: true,
|
|
39086
39162
|
widget: Measurer,
|
|
39087
|
-
version:
|
|
39088
|
-
|
|
39089
|
-
minor: 0
|
|
39090
|
-
},
|
|
39091
|
-
propUpgrades: propUpgrades,
|
|
39163
|
+
version: measurerLogic.version,
|
|
39164
|
+
propUpgrades: measurerLogic.widgetOptionsUpgrades,
|
|
39092
39165
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39093
39166
|
scorer: () => scoreNoop(1)
|
|
39094
39167
|
};
|
|
@@ -39661,10 +39734,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
39661
39734
|
const firstChar = smiles[0];
|
|
39662
39735
|
const rest = smiles.slice(1);
|
|
39663
39736
|
if (firstChar === "=") {
|
|
39664
|
-
return parse$
|
|
39737
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
39665
39738
|
}
|
|
39666
39739
|
if (firstChar === "#") {
|
|
39667
|
-
return parse$
|
|
39740
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
39668
39741
|
}
|
|
39669
39742
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
39670
39743
|
}
|
|
@@ -39723,8 +39796,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
39723
39796
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
39724
39797
|
parens: newCtx.parens.concat("(")
|
|
39725
39798
|
});
|
|
39726
|
-
const parenExpr = parse$
|
|
39727
|
-
const remainder = parse$
|
|
39799
|
+
const parenExpr = parse$1(rest, parenCtx);
|
|
39800
|
+
const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
39728
39801
|
return [parenExpr].concat(remainder);
|
|
39729
39802
|
}
|
|
39730
39803
|
if (firstChar === ")") {
|
|
@@ -39788,7 +39861,7 @@ function parseAtom(smiles, ctx) {
|
|
|
39788
39861
|
|
|
39789
39862
|
// increment the atom counter and reset the branch counter
|
|
39790
39863
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
39791
|
-
let restOfMolecule = parse$
|
|
39864
|
+
let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
39792
39865
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
39793
39866
|
//TODO(colin): fix this awkwardness.
|
|
39794
39867
|
restOfMolecule = [restOfMolecule];
|
|
@@ -39827,7 +39900,7 @@ function isModifierChar(s) {
|
|
|
39827
39900
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
39828
39901
|
* yet implemented.
|
|
39829
39902
|
*/
|
|
39830
|
-
function parse$
|
|
39903
|
+
function parse$1(smiles, ctx) {
|
|
39831
39904
|
if (!validate(smiles)) {
|
|
39832
39905
|
throw new ParseError$1("Invalid input.");
|
|
39833
39906
|
}
|
|
@@ -39852,7 +39925,7 @@ function parse$2(smiles, ctx) {
|
|
|
39852
39925
|
return parseParenthesizedExpression(smiles, ctx);
|
|
39853
39926
|
}
|
|
39854
39927
|
var SmilesParser = {
|
|
39855
|
-
parse: parse$
|
|
39928
|
+
parse: parse$1,
|
|
39856
39929
|
ParseError: ParseError$1
|
|
39857
39930
|
};
|
|
39858
39931
|
|
|
@@ -39860,7 +39933,7 @@ var SmilesParser = {
|
|
|
39860
39933
|
const {
|
|
39861
39934
|
layout
|
|
39862
39935
|
} = MoleculeLayout;
|
|
39863
|
-
const parse
|
|
39936
|
+
const parse = SmilesParser.parse;
|
|
39864
39937
|
const ParseError = SmilesParser.ParseError;
|
|
39865
39938
|
const borderSize = 30;
|
|
39866
39939
|
class Molecule extends React.Component {
|
|
@@ -39874,7 +39947,7 @@ class Molecule extends React.Component {
|
|
|
39874
39947
|
try {
|
|
39875
39948
|
this.setState({
|
|
39876
39949
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
39877
|
-
parsedSmiles: parse
|
|
39950
|
+
parsedSmiles: parse(smiles),
|
|
39878
39951
|
error: null
|
|
39879
39952
|
});
|
|
39880
39953
|
} catch (e) {
|
|
@@ -40093,7 +40166,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
40093
40166
|
const x = range[0] + i * tickStep;
|
|
40094
40167
|
fractions.push(x);
|
|
40095
40168
|
}
|
|
40096
|
-
const getDenom = x => number
|
|
40169
|
+
const getDenom = x => number.toFraction(x)[1];
|
|
40097
40170
|
const denoms = _.map(fractions, getDenom);
|
|
40098
40171
|
base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
40099
40172
|
} else {
|
|
@@ -40156,7 +40229,7 @@ class NumberLine extends React.Component {
|
|
|
40156
40229
|
let initialX = this.props.numLinePosition;
|
|
40157
40230
|
const divisionRange = this.props.divisionRange;
|
|
40158
40231
|
initialX = initialX == null ? range[0] : initialX;
|
|
40159
|
-
return range[0] < range[1] && number
|
|
40232
|
+
return range[0] < range[1] && number.sign(initialX - range[0]) >= 0 && number.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
40160
40233
|
};
|
|
40161
40234
|
this.onNumDivisionsChange = (numDivisions, cb) => {
|
|
40162
40235
|
const divRange = this.props.divisionRange.slice();
|
|
@@ -40259,7 +40332,7 @@ class NumberLine extends React.Component {
|
|
|
40259
40332
|
const right = props.range[1];
|
|
40260
40333
|
const snapX = props.tickStep / props.snapDivisions;
|
|
40261
40334
|
let x = bound(numLinePosition, left, right);
|
|
40262
|
-
x = left + number
|
|
40335
|
+
x = left + number.roundTo(x - left, snapX);
|
|
40263
40336
|
assert$1(_.isFinite(x));
|
|
40264
40337
|
return x;
|
|
40265
40338
|
};
|
|
@@ -40550,7 +40623,10 @@ var NumberLine$1 = {
|
|
|
40550
40623
|
staticTransform: staticTransform$1,
|
|
40551
40624
|
// TODO(LEMS-2656): remove TS suppression
|
|
40552
40625
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40553
|
-
scorer: scoreNumberLine
|
|
40626
|
+
scorer: scoreNumberLine,
|
|
40627
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40628
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40629
|
+
validator: validateNumberLine
|
|
40554
40630
|
};
|
|
40555
40631
|
|
|
40556
40632
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -41113,7 +41189,11 @@ var Orderer$1 = {
|
|
|
41113
41189
|
isLintable: true,
|
|
41114
41190
|
// TODO(LEMS-2656): remove TS suppression
|
|
41115
41191
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41116
|
-
scorer: scoreOrderer
|
|
41192
|
+
scorer: scoreOrderer,
|
|
41193
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41194
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41195
|
+
validator: validateOrderer,
|
|
41196
|
+
getPublicWidgetOptions: getOrdererPublicWidgetOptions
|
|
41117
41197
|
};
|
|
41118
41198
|
|
|
41119
41199
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -41157,10 +41237,6 @@ var PassageRefTarget$1 = {
|
|
|
41157
41237
|
transform: editorProps => {
|
|
41158
41238
|
return _.pick(editorProps, "content");
|
|
41159
41239
|
},
|
|
41160
|
-
version: {
|
|
41161
|
-
major: 0,
|
|
41162
|
-
minor: 0
|
|
41163
|
-
},
|
|
41164
41240
|
isLintable: true,
|
|
41165
41241
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41166
41242
|
scorer: () => scoreNoop()
|
|
@@ -42183,7 +42259,10 @@ var Plotter$1 = {
|
|
|
42183
42259
|
staticTransform: staticTransform,
|
|
42184
42260
|
// TODO(LEMS-2656): remove TS suppression
|
|
42185
42261
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42186
|
-
scorer: scorePlotter
|
|
42262
|
+
scorer: scorePlotter,
|
|
42263
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42264
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42265
|
+
validator: validatePlotter
|
|
42187
42266
|
};
|
|
42188
42267
|
|
|
42189
42268
|
const getPromptJSON$2 = () => {
|
|
@@ -42348,7 +42427,10 @@ var Sorter$1 = {
|
|
|
42348
42427
|
isLintable: true,
|
|
42349
42428
|
// TODO(LEMS-2656): remove TS suppression
|
|
42350
42429
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42351
|
-
scorer: scoreSorter
|
|
42430
|
+
scorer: scoreSorter,
|
|
42431
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42432
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42433
|
+
validator: validateSorter
|
|
42352
42434
|
};
|
|
42353
42435
|
|
|
42354
42436
|
const {
|
|
@@ -42570,7 +42652,10 @@ var Table$1 = {
|
|
|
42570
42652
|
isLintable: true,
|
|
42571
42653
|
// TODO(LEMS-2656): remove TS suppression
|
|
42572
42654
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42573
|
-
scorer: scoreTable
|
|
42655
|
+
scorer: scoreTable,
|
|
42656
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42657
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42658
|
+
validator: validateTable
|
|
42574
42659
|
};
|
|
42575
42660
|
|
|
42576
42661
|
const getPromptJSON = () => {
|
|
@@ -42753,7 +42838,7 @@ const init = function init() {
|
|
|
42753
42838
|
|
|
42754
42839
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
42755
42840
|
const libName = "@khanacademy/perseus";
|
|
42756
|
-
const libVersion = "
|
|
42841
|
+
const libVersion = "52.0.1";
|
|
42757
42842
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
42758
42843
|
|
|
42759
42844
|
const apiVersion = {
|
|
@@ -44191,6 +44276,71 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44191
44276
|
replaceDeprecatedWidgets();
|
|
44192
44277
|
};
|
|
44193
44278
|
|
|
44279
|
+
/**
|
|
44280
|
+
* Get a widget type by a widget's ID
|
|
44281
|
+
* (please don't derive type from ID)
|
|
44282
|
+
*
|
|
44283
|
+
* @param {string} widgetId the ID of the widget
|
|
44284
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44285
|
+
* @returns {string} the widget type (ie "radio")
|
|
44286
|
+
*/
|
|
44287
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44288
|
+
var _widget$type;
|
|
44289
|
+
const widget = widgetMap[WidgetId];
|
|
44290
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
44291
|
+
}
|
|
44292
|
+
|
|
44293
|
+
/**
|
|
44294
|
+
* Does the content have a specific type of widget?
|
|
44295
|
+
*
|
|
44296
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44297
|
+
* @param {string} content the string to search through
|
|
44298
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44299
|
+
* @returns {boolean} if the content includes the widget type
|
|
44300
|
+
*/
|
|
44301
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44302
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44303
|
+
}
|
|
44304
|
+
|
|
44305
|
+
/**
|
|
44306
|
+
* Pull the widget map out of ItemData
|
|
44307
|
+
*
|
|
44308
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44309
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44310
|
+
*/
|
|
44311
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44312
|
+
return itemData.question.widgets;
|
|
44313
|
+
}
|
|
44314
|
+
|
|
44315
|
+
/**
|
|
44316
|
+
* Get the widget information from a WidgetMap
|
|
44317
|
+
*
|
|
44318
|
+
* @param {string} widgetId the ID of the widget
|
|
44319
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44320
|
+
*/
|
|
44321
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44322
|
+
var _widgetMap$widgetId;
|
|
44323
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
44324
|
+
}
|
|
44325
|
+
|
|
44326
|
+
/**
|
|
44327
|
+
* Get select widgets from a widget map.
|
|
44328
|
+
*
|
|
44329
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44330
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44331
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44332
|
+
*/
|
|
44333
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44334
|
+
const widgets = {};
|
|
44335
|
+
widgetIds.forEach(widgetId => {
|
|
44336
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44337
|
+
if (widget) {
|
|
44338
|
+
widgets[widgetId] = widget;
|
|
44339
|
+
}
|
|
44340
|
+
});
|
|
44341
|
+
return widgets;
|
|
44342
|
+
}
|
|
44343
|
+
|
|
44194
44344
|
/**
|
|
44195
44345
|
* This function extracts the answers from the widgets.
|
|
44196
44346
|
*
|
|
@@ -44714,1670 +44864,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
44714
44864
|
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44715
44865
|
};
|
|
44716
44866
|
|
|
44717
|
-
function isRealJSONParse(jsonParse) {
|
|
44718
|
-
const randomPhrase = buildRandomPhrase();
|
|
44719
|
-
const randomHintPhrase = buildRandomPhrase();
|
|
44720
|
-
const randomString = buildRandomString();
|
|
44721
|
-
const testingObject = JSON.stringify({
|
|
44722
|
-
answerArea: {
|
|
44723
|
-
calculator: false,
|
|
44724
|
-
chi2Table: false,
|
|
44725
|
-
financialCalculatorMonthlyPayment: false,
|
|
44726
|
-
financialCalculatorTimeToPayOff: false,
|
|
44727
|
-
financialCalculatorTotalAmount: false,
|
|
44728
|
-
periodicTable: false,
|
|
44729
|
-
periodicTableWithKey: false,
|
|
44730
|
-
tTable: false,
|
|
44731
|
-
zTable: false
|
|
44732
|
-
},
|
|
44733
|
-
hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
|
|
44734
|
-
itemDataVersion: {
|
|
44735
|
-
major: 0,
|
|
44736
|
-
minor: 1
|
|
44737
|
-
},
|
|
44738
|
-
question: {
|
|
44739
|
-
content: `${randomPhrase}`,
|
|
44740
|
-
images: {},
|
|
44741
|
-
widgets: {
|
|
44742
|
-
expression1: {
|
|
44743
|
-
alignment: "default",
|
|
44744
|
-
graded: false,
|
|
44745
|
-
options: {
|
|
44746
|
-
answerForms: [{
|
|
44747
|
-
considered: "wrong",
|
|
44748
|
-
form: false,
|
|
44749
|
-
key: 0,
|
|
44750
|
-
simplify: false,
|
|
44751
|
-
value: `${randomString}`
|
|
44752
|
-
}],
|
|
44753
|
-
ariaLabel: "Answer",
|
|
44754
|
-
buttonSets: ["basic"],
|
|
44755
|
-
functions: ["f", "g", "h"],
|
|
44756
|
-
static: true,
|
|
44757
|
-
times: false,
|
|
44758
|
-
visibleLabel: "Answer"
|
|
44759
|
-
},
|
|
44760
|
-
static: true,
|
|
44761
|
-
type: "expression",
|
|
44762
|
-
version: {
|
|
44763
|
-
major: 1,
|
|
44764
|
-
minor: 0
|
|
44765
|
-
}
|
|
44766
|
-
}
|
|
44767
|
-
}
|
|
44768
|
-
}
|
|
44769
|
-
});
|
|
44770
|
-
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
44771
|
-
const parsedTestJSON = jsonParse(testJSON);
|
|
44772
|
-
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
44773
|
-
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
44774
|
-
}
|
|
44775
|
-
function buildRandomString(capitalize = false) {
|
|
44776
|
-
let randomString = "";
|
|
44777
|
-
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
44778
|
-
for (let i = 0; i < randomLength; i++) {
|
|
44779
|
-
const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
44780
|
-
randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
|
|
44781
|
-
}
|
|
44782
|
-
return randomString;
|
|
44783
|
-
}
|
|
44784
|
-
function buildRandomPhrase() {
|
|
44785
|
-
const phrases = [];
|
|
44786
|
-
const randomLength = Math.floor(Math.random() * 10) + 5;
|
|
44787
|
-
for (let i = 0; i < randomLength; i++) {
|
|
44788
|
-
phrases.push(buildRandomString(i === 0));
|
|
44789
|
-
}
|
|
44790
|
-
const modifierStart = ["**", "$"];
|
|
44791
|
-
const modifierEnd = ["**", "$"];
|
|
44792
|
-
const modifierIndex = Math.floor(Math.random() * modifierStart.length);
|
|
44793
|
-
return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
|
|
44794
|
-
}
|
|
44795
|
-
function buildTestData(testObject) {
|
|
44796
|
-
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
44797
|
-
}
|
|
44798
|
-
|
|
44799
|
-
function success(value) {
|
|
44800
|
-
return {
|
|
44801
|
-
type: "success",
|
|
44802
|
-
value
|
|
44803
|
-
};
|
|
44804
|
-
}
|
|
44805
|
-
function failure(detail) {
|
|
44806
|
-
return {
|
|
44807
|
-
type: "failure",
|
|
44808
|
-
detail
|
|
44809
|
-
};
|
|
44810
|
-
}
|
|
44811
|
-
function isFailure(result) {
|
|
44812
|
-
return result.type === "failure";
|
|
44813
|
-
}
|
|
44814
|
-
function isSuccess(result) {
|
|
44815
|
-
return result.type === "success";
|
|
44816
|
-
}
|
|
44817
|
-
|
|
44818
|
-
// Result's `all` function is similar to Promise.all: given an array of
|
|
44819
|
-
// results, it returns success if all succeeded, and failure if any failed.
|
|
44820
|
-
function all(results, combineFailureDetails = a => a) {
|
|
44821
|
-
const values = [];
|
|
44822
|
-
const failureDetails = [];
|
|
44823
|
-
for (const result of results) {
|
|
44824
|
-
if (result.type === "success") {
|
|
44825
|
-
values.push(result.value);
|
|
44826
|
-
} else {
|
|
44827
|
-
failureDetails.push(result.detail);
|
|
44828
|
-
}
|
|
44829
|
-
}
|
|
44830
|
-
if (failureDetails.length > 0) {
|
|
44831
|
-
return failure(failureDetails.reduce(combineFailureDetails));
|
|
44832
|
-
}
|
|
44833
|
-
return success(values);
|
|
44834
|
-
}
|
|
44835
|
-
|
|
44836
|
-
class ErrorTrackingParseContext {
|
|
44837
|
-
constructor(path) {
|
|
44838
|
-
this.path = path;
|
|
44839
|
-
}
|
|
44840
|
-
failure(expected, badValue) {
|
|
44841
|
-
return failure([{
|
|
44842
|
-
expected: wrapInArray(expected),
|
|
44843
|
-
badValue,
|
|
44844
|
-
path: this.path
|
|
44845
|
-
}]);
|
|
44846
|
-
}
|
|
44847
|
-
forSubtree(key) {
|
|
44848
|
-
return new ErrorTrackingParseContext([...this.path, key]);
|
|
44849
|
-
}
|
|
44850
|
-
success(value) {
|
|
44851
|
-
return success(value);
|
|
44852
|
-
}
|
|
44853
|
-
}
|
|
44854
|
-
function wrapInArray(a) {
|
|
44855
|
-
return Array.isArray(a) ? a : [a];
|
|
44856
|
-
}
|
|
44857
|
-
|
|
44858
|
-
function formatPath(path) {
|
|
44859
|
-
return "(root)" + path.map(formatPathSegment).join("");
|
|
44860
|
-
}
|
|
44861
|
-
function formatPathSegment(segment) {
|
|
44862
|
-
if (typeof segment === "string") {
|
|
44863
|
-
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
44864
|
-
}
|
|
44865
|
-
return `[${segment.toString()}]`;
|
|
44866
|
-
}
|
|
44867
|
-
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
44868
|
-
|
|
44869
|
-
function message(failure) {
|
|
44870
|
-
const expected = conjoin(failure.expected);
|
|
44871
|
-
const path = formatPath(failure.path);
|
|
44872
|
-
const badValue = JSON.stringify(failure.badValue);
|
|
44873
|
-
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
44874
|
-
}
|
|
44875
|
-
function conjoin(items) {
|
|
44876
|
-
switch (items.length) {
|
|
44877
|
-
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
44878
|
-
case 1:
|
|
44879
|
-
return items[0];
|
|
44880
|
-
case 2:
|
|
44881
|
-
return items.join(" or ");
|
|
44882
|
-
default:
|
|
44883
|
-
{
|
|
44884
|
-
const allButLast = items.slice(0, items.length - 1);
|
|
44885
|
-
const last = items[items.length - 1];
|
|
44886
|
-
return allButLast.join(", ") + ", or " + last;
|
|
44887
|
-
}
|
|
44888
|
-
}
|
|
44889
|
-
}
|
|
44890
|
-
|
|
44891
|
-
function parse(value, parser) {
|
|
44892
|
-
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
44893
|
-
if (isFailure(result)) {
|
|
44894
|
-
return failure(result.detail.map(message).join("; "));
|
|
44895
|
-
}
|
|
44896
|
-
return result;
|
|
44897
|
-
}
|
|
44898
|
-
|
|
44899
|
-
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
44900
|
-
|
|
44901
|
-
function array(elementParser) {
|
|
44902
|
-
return (rawValue, ctx) => {
|
|
44903
|
-
if (!Array.isArray(rawValue)) {
|
|
44904
|
-
return ctx.failure("array", rawValue);
|
|
44905
|
-
}
|
|
44906
|
-
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
44907
|
-
return all(elementResults, concat);
|
|
44908
|
-
};
|
|
44909
|
-
}
|
|
44910
|
-
function concat(a, b) {
|
|
44911
|
-
return [...a, ...b];
|
|
44912
|
-
}
|
|
44913
|
-
|
|
44914
|
-
function boolean(rawValue, ctx) {
|
|
44915
|
-
if (typeof rawValue === "boolean") {
|
|
44916
|
-
return ctx.success(rawValue);
|
|
44917
|
-
}
|
|
44918
|
-
return ctx.failure("boolean", rawValue);
|
|
44919
|
-
}
|
|
44920
|
-
|
|
44921
|
-
function constant(acceptedValue) {
|
|
44922
|
-
return (rawValue, ctx) => {
|
|
44923
|
-
if (rawValue !== acceptedValue) {
|
|
44924
|
-
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
44925
|
-
}
|
|
44926
|
-
return ctx.success(acceptedValue);
|
|
44927
|
-
};
|
|
44928
|
-
}
|
|
44929
|
-
|
|
44930
|
-
function enumeration(...acceptedValues) {
|
|
44931
|
-
return (rawValue, ctx) => {
|
|
44932
|
-
if (typeof rawValue === "string") {
|
|
44933
|
-
const index = acceptedValues.indexOf(rawValue);
|
|
44934
|
-
if (index > -1) {
|
|
44935
|
-
return ctx.success(acceptedValues[index]);
|
|
44936
|
-
}
|
|
44937
|
-
}
|
|
44938
|
-
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
44939
|
-
return ctx.failure(expected, rawValue);
|
|
44940
|
-
};
|
|
44941
|
-
}
|
|
44942
|
-
|
|
44943
|
-
function isObject(x) {
|
|
44944
|
-
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
44945
|
-
}
|
|
44946
|
-
|
|
44947
|
-
function nullable(parseValue) {
|
|
44948
|
-
return (rawValue, ctx) => {
|
|
44949
|
-
if (rawValue === null) {
|
|
44950
|
-
return ctx.success(rawValue);
|
|
44951
|
-
}
|
|
44952
|
-
return parseValue(rawValue, ctx);
|
|
44953
|
-
};
|
|
44954
|
-
}
|
|
44955
|
-
|
|
44956
|
-
const number = (rawValue, ctx) => {
|
|
44957
|
-
if (typeof rawValue === "number") {
|
|
44958
|
-
return ctx.success(rawValue);
|
|
44959
|
-
}
|
|
44960
|
-
return ctx.failure("number", rawValue);
|
|
44961
|
-
};
|
|
44962
|
-
|
|
44963
|
-
function object(schema) {
|
|
44964
|
-
return (rawValue, ctx) => {
|
|
44965
|
-
if (!isObject(rawValue)) {
|
|
44966
|
-
return ctx.failure("object", rawValue);
|
|
44967
|
-
}
|
|
44968
|
-
const ret = _extends({}, rawValue);
|
|
44969
|
-
const mismatches = [];
|
|
44970
|
-
for (const [prop, propParser] of Object.entries(schema)) {
|
|
44971
|
-
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
44972
|
-
if (isSuccess(result)) {
|
|
44973
|
-
if (result.value !== undefined || prop in rawValue) {
|
|
44974
|
-
ret[prop] = result.value;
|
|
44975
|
-
}
|
|
44976
|
-
} else {
|
|
44977
|
-
mismatches.push(...result.detail);
|
|
44978
|
-
}
|
|
44979
|
-
}
|
|
44980
|
-
if (mismatches.length > 0) {
|
|
44981
|
-
return failure(mismatches);
|
|
44982
|
-
}
|
|
44983
|
-
return ctx.success(ret);
|
|
44984
|
-
};
|
|
44985
|
-
}
|
|
44986
|
-
|
|
44987
|
-
function optional(parseValue) {
|
|
44988
|
-
return (rawValue, ctx) => {
|
|
44989
|
-
if (rawValue === undefined) {
|
|
44990
|
-
return ctx.success(rawValue);
|
|
44991
|
-
}
|
|
44992
|
-
return parseValue(rawValue, ctx);
|
|
44993
|
-
};
|
|
44994
|
-
}
|
|
44995
|
-
|
|
44996
|
-
function pair(parseA, parseB) {
|
|
44997
|
-
return (rawValue, ctx) => {
|
|
44998
|
-
if (!Array.isArray(rawValue)) {
|
|
44999
|
-
return ctx.failure("array", rawValue);
|
|
45000
|
-
}
|
|
45001
|
-
if (rawValue.length !== 2) {
|
|
45002
|
-
return ctx.failure("array of length 2", rawValue);
|
|
45003
|
-
}
|
|
45004
|
-
const [rawA, rawB] = rawValue;
|
|
45005
|
-
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
45006
|
-
if (isFailure(resultA)) {
|
|
45007
|
-
return resultA;
|
|
45008
|
-
}
|
|
45009
|
-
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
45010
|
-
if (isFailure(resultB)) {
|
|
45011
|
-
return resultB;
|
|
45012
|
-
}
|
|
45013
|
-
return ctx.success([resultA.value, resultB.value]);
|
|
45014
|
-
};
|
|
45015
|
-
}
|
|
45016
|
-
|
|
45017
|
-
function pipeParsers(p) {
|
|
45018
|
-
return new ParserPipeline(p);
|
|
45019
|
-
}
|
|
45020
|
-
class ParserPipeline {
|
|
45021
|
-
constructor(parser) {
|
|
45022
|
-
this.parser = parser;
|
|
45023
|
-
}
|
|
45024
|
-
then(nextParser) {
|
|
45025
|
-
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
45026
|
-
}
|
|
45027
|
-
}
|
|
45028
|
-
function composeParsers(parserA, parserB) {
|
|
45029
|
-
return (rawValue, ctx) => {
|
|
45030
|
-
const partialResult = parserA(rawValue, ctx);
|
|
45031
|
-
if (isFailure(partialResult)) {
|
|
45032
|
-
return partialResult;
|
|
45033
|
-
}
|
|
45034
|
-
return parserB(partialResult.value, ctx);
|
|
45035
|
-
};
|
|
45036
|
-
}
|
|
45037
|
-
|
|
45038
|
-
function record(parseKey, parseValue) {
|
|
45039
|
-
return (rawValue, ctx) => {
|
|
45040
|
-
if (!isObject(rawValue)) {
|
|
45041
|
-
return ctx.failure("object", rawValue);
|
|
45042
|
-
}
|
|
45043
|
-
const result = {};
|
|
45044
|
-
const mismatches = [];
|
|
45045
|
-
for (const [key, value] of Object.entries(rawValue)) {
|
|
45046
|
-
const entryCtx = ctx.forSubtree(key);
|
|
45047
|
-
const keyResult = parseKey(key, entryCtx);
|
|
45048
|
-
if (isFailure(keyResult)) {
|
|
45049
|
-
mismatches.push(...keyResult.detail);
|
|
45050
|
-
}
|
|
45051
|
-
const valueResult = parseValue(value, entryCtx);
|
|
45052
|
-
if (isFailure(valueResult)) {
|
|
45053
|
-
mismatches.push(...valueResult.detail);
|
|
45054
|
-
}
|
|
45055
|
-
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
45056
|
-
result[keyResult.value] = valueResult.value;
|
|
45057
|
-
}
|
|
45058
|
-
}
|
|
45059
|
-
if (mismatches.length > 0) {
|
|
45060
|
-
return failure(mismatches);
|
|
45061
|
-
}
|
|
45062
|
-
return ctx.success(result);
|
|
45063
|
-
};
|
|
45064
|
-
}
|
|
45065
|
-
|
|
45066
|
-
const string = (rawValue, ctx) => {
|
|
45067
|
-
if (typeof rawValue === "string") {
|
|
45068
|
-
return ctx.success(rawValue);
|
|
45069
|
-
}
|
|
45070
|
-
return ctx.failure("string", rawValue);
|
|
45071
|
-
};
|
|
45072
|
-
|
|
45073
|
-
function trio(parseA, parseB, parseC) {
|
|
45074
|
-
return (rawValue, ctx) => {
|
|
45075
|
-
if (!Array.isArray(rawValue)) {
|
|
45076
|
-
return ctx.failure("array", rawValue);
|
|
45077
|
-
}
|
|
45078
|
-
if (rawValue.length !== 3) {
|
|
45079
|
-
return ctx.failure("array of length 3", rawValue);
|
|
45080
|
-
}
|
|
45081
|
-
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
45082
|
-
if (isFailure(resultA)) {
|
|
45083
|
-
return resultA;
|
|
45084
|
-
}
|
|
45085
|
-
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
45086
|
-
if (isFailure(resultB)) {
|
|
45087
|
-
return resultB;
|
|
45088
|
-
}
|
|
45089
|
-
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
45090
|
-
if (isFailure(resultC)) {
|
|
45091
|
-
return resultC;
|
|
45092
|
-
}
|
|
45093
|
-
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
45094
|
-
};
|
|
45095
|
-
}
|
|
45096
|
-
|
|
45097
|
-
function union(parseBranch) {
|
|
45098
|
-
return new UnionBuilder(parseBranch);
|
|
45099
|
-
}
|
|
45100
|
-
class UnionBuilder {
|
|
45101
|
-
constructor(parser) {
|
|
45102
|
-
this.parser = parser;
|
|
45103
|
-
}
|
|
45104
|
-
or(newBranch) {
|
|
45105
|
-
return new UnionBuilder(either(this.parser, newBranch));
|
|
45106
|
-
}
|
|
45107
|
-
}
|
|
45108
|
-
function either(parseA, parseB) {
|
|
45109
|
-
return (rawValue, ctx) => {
|
|
45110
|
-
const resultA = parseA(rawValue, ctx);
|
|
45111
|
-
if (isSuccess(resultA)) {
|
|
45112
|
-
return resultA;
|
|
45113
|
-
}
|
|
45114
|
-
return parseB(rawValue, ctx);
|
|
45115
|
-
};
|
|
45116
|
-
}
|
|
45117
|
-
|
|
45118
|
-
function defaulted(parser, fallback) {
|
|
45119
|
-
return (rawValue, ctx) => {
|
|
45120
|
-
if (rawValue == null) {
|
|
45121
|
-
return success(fallback(rawValue));
|
|
45122
|
-
}
|
|
45123
|
-
return parser(rawValue, ctx);
|
|
45124
|
-
};
|
|
45125
|
-
}
|
|
45126
|
-
|
|
45127
|
-
const parseImages = defaulted(record(string, object({
|
|
45128
|
-
width: number,
|
|
45129
|
-
height: number
|
|
45130
|
-
})), () => ({}));
|
|
45131
|
-
|
|
45132
|
-
function parseWidget(parseType, parseOptions) {
|
|
45133
|
-
return object({
|
|
45134
|
-
type: parseType,
|
|
45135
|
-
static: optional(boolean),
|
|
45136
|
-
graded: optional(boolean),
|
|
45137
|
-
alignment: optional(string),
|
|
45138
|
-
options: parseOptions,
|
|
45139
|
-
key: optional(number),
|
|
45140
|
-
version: optional(object({
|
|
45141
|
-
major: number,
|
|
45142
|
-
minor: number
|
|
45143
|
-
}))
|
|
45144
|
-
});
|
|
45145
|
-
}
|
|
45146
|
-
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
45147
|
-
return object({
|
|
45148
|
-
type: parseType,
|
|
45149
|
-
static: optional(boolean),
|
|
45150
|
-
graded: optional(boolean),
|
|
45151
|
-
alignment: optional(string),
|
|
45152
|
-
options: parseOptions,
|
|
45153
|
-
key: optional(number),
|
|
45154
|
-
version: parseVersion
|
|
45155
|
-
});
|
|
45156
|
-
}
|
|
45157
|
-
|
|
45158
|
-
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
45159
|
-
items: array(string),
|
|
45160
|
-
categories: array(string),
|
|
45161
|
-
randomizeItems: defaulted(boolean, () => false),
|
|
45162
|
-
static: defaulted(boolean, () => false),
|
|
45163
|
-
values: array(defaulted(number, () => 0)),
|
|
45164
|
-
highlightLint: optional(boolean),
|
|
45165
|
-
linterContext: optional(object({
|
|
45166
|
-
contentType: string,
|
|
45167
|
-
paths: array(string),
|
|
45168
|
-
stack: array(string)
|
|
45169
|
-
}))
|
|
45170
|
-
}));
|
|
45171
|
-
|
|
45172
|
-
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
45173
|
-
programID: string,
|
|
45174
|
-
programType: any,
|
|
45175
|
-
settings: array(object({
|
|
45176
|
-
name: string,
|
|
45177
|
-
value: string
|
|
45178
|
-
})),
|
|
45179
|
-
showEditor: boolean,
|
|
45180
|
-
showButtons: boolean,
|
|
45181
|
-
height: number,
|
|
45182
|
-
static: defaulted(boolean, () => false)
|
|
45183
|
-
}));
|
|
45184
|
-
|
|
45185
|
-
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
45186
|
-
togglePrompt: string,
|
|
45187
|
-
definition: string,
|
|
45188
|
-
static: defaulted(boolean, () => false)
|
|
45189
|
-
}));
|
|
45190
|
-
|
|
45191
|
-
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
45192
|
-
placeholder: defaulted(string, () => ""),
|
|
45193
|
-
ariaLabel: optional(string),
|
|
45194
|
-
visibleLabel: optional(string),
|
|
45195
|
-
static: defaulted(boolean, () => false),
|
|
45196
|
-
choices: array(object({
|
|
45197
|
-
content: string,
|
|
45198
|
-
correct: boolean
|
|
45199
|
-
}))
|
|
45200
|
-
}));
|
|
45201
|
-
|
|
45202
|
-
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
45203
|
-
showPrompt: string,
|
|
45204
|
-
hidePrompt: string,
|
|
45205
|
-
explanation: string,
|
|
45206
|
-
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
45207
|
-
// referenced before it is defined. There is an import cycle between
|
|
45208
|
-
// this file and widgets-map.ts that could cause it to be undefined.
|
|
45209
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
45210
|
-
static: defaulted(boolean, () => false)
|
|
45211
|
-
}));
|
|
45212
|
-
|
|
45213
|
-
// Given a function, creates a PartialParser that converts one type to another
|
|
45214
|
-
// using that function. The returned parser never fails.
|
|
45215
|
-
function convert(f) {
|
|
45216
|
-
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
45217
|
-
}
|
|
45218
|
-
|
|
45219
|
-
/**
|
|
45220
|
-
* Creates a parser for a widget options type with multiple major versions. Old
|
|
45221
|
-
* versions are migrated to the latest version. The parse fails if the input
|
|
45222
|
-
* data does not match any of the versions.
|
|
45223
|
-
*
|
|
45224
|
-
* @example
|
|
45225
|
-
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
45226
|
-
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
45227
|
-
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
45228
|
-
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
45229
|
-
* .parser;
|
|
45230
|
-
*
|
|
45231
|
-
* @param latestMajorVersion the latest major version of the widget options.
|
|
45232
|
-
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
45233
|
-
* options.
|
|
45234
|
-
* @returns a builder object, to which migrations from earlier versions can be
|
|
45235
|
-
* added. Migrations must be added in "reverse chronological" order as in the
|
|
45236
|
-
* example above.
|
|
45237
|
-
*/
|
|
45238
|
-
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
45239
|
-
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
45240
|
-
}
|
|
45241
|
-
class VersionedWidgetOptionsParserBuilder {
|
|
45242
|
-
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
45243
|
-
this.parser = void 0;
|
|
45244
|
-
this.migrateToLatest = migrateToLatest;
|
|
45245
|
-
this.parseOtherVersions = parseOtherVersions;
|
|
45246
|
-
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
45247
|
-
this.parser = (raw, ctx) => {
|
|
45248
|
-
if (!isObject(raw)) {
|
|
45249
|
-
return ctx.failure("object", raw);
|
|
45250
|
-
}
|
|
45251
|
-
const versionParseResult = parseVersionedObject(raw, ctx);
|
|
45252
|
-
if (isFailure(versionParseResult)) {
|
|
45253
|
-
return versionParseResult;
|
|
45254
|
-
}
|
|
45255
|
-
if (versionParseResult.value.version.major !== majorVersion) {
|
|
45256
|
-
return this.parseOtherVersions(raw, ctx);
|
|
45257
|
-
}
|
|
45258
|
-
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
45259
|
-
};
|
|
45260
|
-
}
|
|
45261
|
-
|
|
45262
|
-
/**
|
|
45263
|
-
* Add a migration from an old version of the widget options.
|
|
45264
|
-
*/
|
|
45265
|
-
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
45266
|
-
const parseOtherVersions = this.parser;
|
|
45267
|
-
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
45268
|
-
return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
|
|
45269
|
-
}
|
|
45270
|
-
}
|
|
45271
|
-
const parseVersionedObject = object({
|
|
45272
|
-
version: defaulted(object({
|
|
45273
|
-
major: number,
|
|
45274
|
-
minor: number
|
|
45275
|
-
}), () => ({
|
|
45276
|
-
major: 0,
|
|
45277
|
-
minor: 0
|
|
45278
|
-
}))
|
|
45279
|
-
});
|
|
45280
|
-
|
|
45281
|
-
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
45282
|
-
const parsePossiblyInvalidAnswerForm = object({
|
|
45283
|
-
// `value` is the possibly invalid part of this. It should always be a
|
|
45284
|
-
// string, but some answer forms don't have it. The Expression widget
|
|
45285
|
-
// ignores invalid values, so we can safely filter them out during parsing.
|
|
45286
|
-
value: optional(string),
|
|
45287
|
-
form: defaulted(boolean, () => false),
|
|
45288
|
-
simplify: defaulted(boolean, () => false),
|
|
45289
|
-
considered: enumeration("correct", "wrong", "ungraded"),
|
|
45290
|
-
key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
|
|
45291
|
-
});
|
|
45292
|
-
function removeInvalidAnswerForms(possiblyInvalid) {
|
|
45293
|
-
const valid = [];
|
|
45294
|
-
for (const answerForm of possiblyInvalid) {
|
|
45295
|
-
const {
|
|
45296
|
-
value
|
|
45297
|
-
} = answerForm;
|
|
45298
|
-
if (value != null) {
|
|
45299
|
-
// Copying the object seems to be needed to make TypeScript happy
|
|
45300
|
-
valid.push(_extends({}, answerForm, {
|
|
45301
|
-
value
|
|
45302
|
-
}));
|
|
45303
|
-
}
|
|
45304
|
-
}
|
|
45305
|
-
return valid;
|
|
45306
|
-
}
|
|
45307
|
-
|
|
45308
|
-
// NOTE(benchristel): I copied the default buttonSets from
|
|
45309
|
-
// expression.tsx. See the parse-perseus-json/README.md for
|
|
45310
|
-
// an explanation of why we want to duplicate the default here.
|
|
45311
|
-
const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
45312
|
-
const version1 = object({
|
|
45313
|
-
major: constant(1),
|
|
45314
|
-
minor: number
|
|
45315
|
-
});
|
|
45316
|
-
const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
|
|
45317
|
-
answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
|
|
45318
|
-
functions: array(string),
|
|
45319
|
-
times: boolean,
|
|
45320
|
-
visibleLabel: optional(string),
|
|
45321
|
-
ariaLabel: optional(string),
|
|
45322
|
-
buttonSets: parseButtonSets,
|
|
45323
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45324
|
-
}));
|
|
45325
|
-
const version0 = optional(object({
|
|
45326
|
-
major: constant(0),
|
|
45327
|
-
minor: number
|
|
45328
|
-
}));
|
|
45329
|
-
const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
|
|
45330
|
-
functions: array(string),
|
|
45331
|
-
times: boolean,
|
|
45332
|
-
visibleLabel: optional(string),
|
|
45333
|
-
ariaLabel: optional(string),
|
|
45334
|
-
form: boolean,
|
|
45335
|
-
simplify: boolean,
|
|
45336
|
-
value: string,
|
|
45337
|
-
buttonSets: parseButtonSets,
|
|
45338
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45339
|
-
}));
|
|
45340
|
-
function migrateV0ToV1(widget) {
|
|
45341
|
-
const {
|
|
45342
|
-
options
|
|
45343
|
-
} = widget;
|
|
45344
|
-
return _extends({}, widget, {
|
|
45345
|
-
version: {
|
|
45346
|
-
major: 1,
|
|
45347
|
-
minor: 0
|
|
45348
|
-
},
|
|
45349
|
-
options: {
|
|
45350
|
-
times: options.times,
|
|
45351
|
-
buttonSets: options.buttonSets,
|
|
45352
|
-
functions: options.functions,
|
|
45353
|
-
buttonsVisible: options.buttonsVisible,
|
|
45354
|
-
visibleLabel: options.visibleLabel,
|
|
45355
|
-
ariaLabel: options.ariaLabel,
|
|
45356
|
-
answerForms: [{
|
|
45357
|
-
considered: "correct",
|
|
45358
|
-
form: options.form,
|
|
45359
|
-
simplify: options.simplify,
|
|
45360
|
-
value: options.value
|
|
45361
|
-
}]
|
|
45362
|
-
}
|
|
45363
|
-
});
|
|
45364
|
-
}
|
|
45365
|
-
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
45366
|
-
|
|
45367
|
-
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
45368
|
-
const parseGradedGroupWidgetOptions = object({
|
|
45369
|
-
title: defaulted(string, () => ""),
|
|
45370
|
-
hasHint: optional(nullable(boolean)),
|
|
45371
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45372
|
-
// The anonymous function below ensures that we don't try to access
|
|
45373
|
-
// parsePerseusRenderer before it's defined.
|
|
45374
|
-
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
45375
|
-
content: string,
|
|
45376
|
-
// This module has an import cycle with parseWidgetsMap.
|
|
45377
|
-
// The anonymous function below ensures that we don't try to access
|
|
45378
|
-
// parseWidgetsMap before it's defined.
|
|
45379
|
-
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
45380
|
-
widgetEnabled: optional(nullable(boolean)),
|
|
45381
|
-
immutableWidgets: optional(nullable(boolean)),
|
|
45382
|
-
images: record(string, object({
|
|
45383
|
-
width: number,
|
|
45384
|
-
height: number
|
|
45385
|
-
}))
|
|
45386
|
-
});
|
|
45387
|
-
const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
|
|
45388
|
-
|
|
45389
|
-
const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
|
|
45390
|
-
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
45391
|
-
}));
|
|
45392
|
-
|
|
45393
|
-
/**
|
|
45394
|
-
* discriminatedUnion() should be preferred over union() when parsing a
|
|
45395
|
-
* discriminated union type, because discriminatedUnion() produces more
|
|
45396
|
-
* understandable failure messages. It takes the discriminant as the source of
|
|
45397
|
-
* truth for which variant is to be parsed, and expects the other data to match
|
|
45398
|
-
* that variant.
|
|
45399
|
-
*/
|
|
45400
|
-
function discriminatedUnionOn(discriminantKey) {
|
|
45401
|
-
const noMoreBranches = (raw, ctx) => {
|
|
45402
|
-
if (!isObject(raw)) {
|
|
45403
|
-
return ctx.failure("object", raw);
|
|
45404
|
-
}
|
|
45405
|
-
return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
|
|
45406
|
-
};
|
|
45407
|
-
return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
|
|
45408
|
-
}
|
|
45409
|
-
class DiscriminatedUnionBuilder {
|
|
45410
|
-
constructor(discriminantKey, parser) {
|
|
45411
|
-
this.discriminantKey = discriminantKey;
|
|
45412
|
-
this.parser = parser;
|
|
45413
|
-
}
|
|
45414
|
-
withBranch(discriminantValue, parseNewVariant) {
|
|
45415
|
-
const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
|
|
45416
|
-
return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
|
|
45417
|
-
}
|
|
45418
|
-
}
|
|
45419
|
-
function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
|
|
45420
|
-
return (raw, ctx) => {
|
|
45421
|
-
if (!isObject(raw)) {
|
|
45422
|
-
return ctx.failure("object", raw);
|
|
45423
|
-
}
|
|
45424
|
-
if (raw[discriminantKey] === discriminantValue) {
|
|
45425
|
-
return parseVariant(raw, ctx);
|
|
45426
|
-
}
|
|
45427
|
-
return parseOtherBranches(raw, ctx);
|
|
45428
|
-
};
|
|
45429
|
-
}
|
|
45430
|
-
|
|
45431
|
-
const pairOfNumbers$3 = pair(number, number);
|
|
45432
|
-
const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
|
|
45433
|
-
const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
45434
|
-
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
45435
|
-
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
45436
|
-
type: constant("absolute_value"),
|
|
45437
|
-
coords: nullable(pairOfPoints)
|
|
45438
|
-
})).withBranch("exponential", object({
|
|
45439
|
-
type: constant("exponential"),
|
|
45440
|
-
asymptote: pairOfPoints,
|
|
45441
|
-
coords: nullable(pairOfPoints)
|
|
45442
|
-
})).withBranch("linear", object({
|
|
45443
|
-
type: constant("linear"),
|
|
45444
|
-
coords: nullable(pairOfPoints)
|
|
45445
|
-
})).withBranch("logarithm", object({
|
|
45446
|
-
type: constant("logarithm"),
|
|
45447
|
-
asymptote: pairOfPoints,
|
|
45448
|
-
coords: nullable(pairOfPoints)
|
|
45449
|
-
})).withBranch("quadratic", object({
|
|
45450
|
-
type: constant("quadratic"),
|
|
45451
|
-
coords: nullable(pairOfPoints)
|
|
45452
|
-
})).withBranch("sinusoid", object({
|
|
45453
|
-
type: constant("sinusoid"),
|
|
45454
|
-
coords: nullable(pairOfPoints)
|
|
45455
|
-
})).withBranch("tangent", object({
|
|
45456
|
-
type: constant("tangent"),
|
|
45457
|
-
coords: nullable(pairOfPoints)
|
|
45458
|
-
})).parser,
|
|
45459
|
-
graph: object({
|
|
45460
|
-
backgroundImage: object({
|
|
45461
|
-
bottom: optional(number),
|
|
45462
|
-
height: optional(number),
|
|
45463
|
-
left: optional(number),
|
|
45464
|
-
scale: optional(number),
|
|
45465
|
-
url: optional(nullable(string)),
|
|
45466
|
-
width: optional(number)
|
|
45467
|
-
}),
|
|
45468
|
-
box: optional(pairOfNumbers$3),
|
|
45469
|
-
editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
|
|
45470
|
-
gridStep: optional(pairOfNumbers$3),
|
|
45471
|
-
labels: pair(string, string),
|
|
45472
|
-
markings: enumeration("graph", "none", "grid"),
|
|
45473
|
-
range: pair(pairOfNumbers$3, pairOfNumbers$3),
|
|
45474
|
-
rulerLabel: constant(""),
|
|
45475
|
-
rulerTicks: number,
|
|
45476
|
-
showProtractor: optional(boolean),
|
|
45477
|
-
showRuler: optional(boolean),
|
|
45478
|
-
showTooltips: optional(boolean),
|
|
45479
|
-
snapStep: optional(pairOfNumbers$3),
|
|
45480
|
-
step: pairOfNumbers$3,
|
|
45481
|
-
valid: optional(union(boolean).or(string).parser)
|
|
45482
|
-
})
|
|
45483
|
-
}));
|
|
45484
|
-
|
|
45485
|
-
const parseGroupWidget = parseWidget(constant("group"),
|
|
45486
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45487
|
-
// The anonymous function below ensures that we don't try to access
|
|
45488
|
-
// parsePerseusRenderer before it's defined.
|
|
45489
|
-
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
45490
|
-
|
|
45491
|
-
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
45492
|
-
url: string,
|
|
45493
|
-
settings: optional(array(object({
|
|
45494
|
-
name: string,
|
|
45495
|
-
value: string
|
|
45496
|
-
}))),
|
|
45497
|
-
width: union(number).or(string).parser,
|
|
45498
|
-
height: union(number).or(string).parser,
|
|
45499
|
-
allowFullScreen: defaulted(boolean, () => false),
|
|
45500
|
-
allowTopNavigation: optional(boolean),
|
|
45501
|
-
static: defaulted(boolean, () => false)
|
|
45502
|
-
}));
|
|
45503
|
-
|
|
45504
|
-
const stringToNumber = (rawValue, ctx) => {
|
|
45505
|
-
if (typeof rawValue === "number") {
|
|
45506
|
-
return ctx.success(rawValue);
|
|
45507
|
-
}
|
|
45508
|
-
const parsedNumber = +rawValue;
|
|
45509
|
-
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
45510
|
-
return ctx.failure("a number or numeric string", rawValue);
|
|
45511
|
-
}
|
|
45512
|
-
return ctx.success(parsedNumber);
|
|
45513
|
-
};
|
|
45514
|
-
|
|
45515
|
-
function emptyToZero(x) {
|
|
45516
|
-
return x === "" ? 0 : x;
|
|
45517
|
-
}
|
|
45518
|
-
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
45519
|
-
// In this specific case, empty string is equivalent to zero. An empty
|
|
45520
|
-
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
45521
|
-
// CSS will treat NaN as invalid and default to 0 instead.
|
|
45522
|
-
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
45523
|
-
const parsePerseusImageBackground = object({
|
|
45524
|
-
url: optional(nullable(string)),
|
|
45525
|
-
width: optional(imageDimensionToNumber),
|
|
45526
|
-
height: optional(imageDimensionToNumber),
|
|
45527
|
-
top: optional(imageDimensionToNumber),
|
|
45528
|
-
left: optional(imageDimensionToNumber),
|
|
45529
|
-
bottom: optional(imageDimensionToNumber),
|
|
45530
|
-
scale: optional(imageDimensionToNumber)
|
|
45531
|
-
});
|
|
45532
|
-
|
|
45533
|
-
const pairOfNumbers$2 = pair(number, number);
|
|
45534
|
-
const parseImageWidget = parseWidget(constant("image"), object({
|
|
45535
|
-
title: optional(string),
|
|
45536
|
-
caption: optional(string),
|
|
45537
|
-
alt: optional(string),
|
|
45538
|
-
backgroundImage: parsePerseusImageBackground,
|
|
45539
|
-
static: optional(boolean),
|
|
45540
|
-
labels: optional(array(object({
|
|
45541
|
-
content: string,
|
|
45542
|
-
alignment: string,
|
|
45543
|
-
coordinates: array(number)
|
|
45544
|
-
}))),
|
|
45545
|
-
range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
|
|
45546
|
-
box: optional(pairOfNumbers$2)
|
|
45547
|
-
}));
|
|
45548
|
-
|
|
45549
|
-
const booleanToString = (rawValue, ctx) => {
|
|
45550
|
-
if (typeof rawValue === "boolean") {
|
|
45551
|
-
return ctx.success(String(rawValue));
|
|
45552
|
-
}
|
|
45553
|
-
return ctx.failure("boolean", rawValue);
|
|
45554
|
-
};
|
|
45555
|
-
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
45556
|
-
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
45557
|
-
inexact: optional(boolean),
|
|
45558
|
-
maxError: optional(union(number).or(string).parser),
|
|
45559
|
-
rightAlign: optional(boolean),
|
|
45560
|
-
simplify: enumeration("required", "optional", "enforced"),
|
|
45561
|
-
size: enumeration("normal", "small"),
|
|
45562
|
-
// TODO(benchristel): there are some content items where value is a
|
|
45563
|
-
// boolean, even though that makes no sense. We should figure out if
|
|
45564
|
-
// those content items are actually published anywhere, and consider
|
|
45565
|
-
// updating them.
|
|
45566
|
-
value: union(number).or(string).or(booleanToString).parser,
|
|
45567
|
-
customKeypad: optional(boolean)
|
|
45568
|
-
}));
|
|
45569
|
-
|
|
45570
|
-
const pairOfNumbers$1 = pair(number, number);
|
|
45571
|
-
const stringOrEmpty = defaulted(string, () => "");
|
|
45572
|
-
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
45573
|
-
const parseFunctionElement = object({
|
|
45574
|
-
type: constant("function"),
|
|
45575
|
-
key: parseKey,
|
|
45576
|
-
options: object({
|
|
45577
|
-
value: string,
|
|
45578
|
-
funcName: string,
|
|
45579
|
-
rangeMin: string,
|
|
45580
|
-
rangeMax: string,
|
|
45581
|
-
color: string,
|
|
45582
|
-
strokeDasharray: string,
|
|
45583
|
-
strokeWidth: number
|
|
45584
|
-
})
|
|
45585
|
-
});
|
|
45586
|
-
const parseLabelElement = object({
|
|
45587
|
-
type: constant("label"),
|
|
45588
|
-
key: parseKey,
|
|
45589
|
-
options: object({
|
|
45590
|
-
label: string,
|
|
45591
|
-
color: string,
|
|
45592
|
-
coordX: string,
|
|
45593
|
-
coordY: string
|
|
45594
|
-
})
|
|
45595
|
-
});
|
|
45596
|
-
const parseLineElement = object({
|
|
45597
|
-
type: constant("line"),
|
|
45598
|
-
key: parseKey,
|
|
45599
|
-
options: object({
|
|
45600
|
-
color: string,
|
|
45601
|
-
startX: string,
|
|
45602
|
-
startY: string,
|
|
45603
|
-
endX: string,
|
|
45604
|
-
endY: string,
|
|
45605
|
-
strokeDasharray: string,
|
|
45606
|
-
strokeWidth: number,
|
|
45607
|
-
arrows: string
|
|
45608
|
-
})
|
|
45609
|
-
});
|
|
45610
|
-
const parseMovableLineElement = object({
|
|
45611
|
-
type: constant("movable-line"),
|
|
45612
|
-
key: parseKey,
|
|
45613
|
-
options: object({
|
|
45614
|
-
startX: string,
|
|
45615
|
-
startY: string,
|
|
45616
|
-
startSubscript: number,
|
|
45617
|
-
endX: string,
|
|
45618
|
-
endY: string,
|
|
45619
|
-
endSubscript: number,
|
|
45620
|
-
constraint: string,
|
|
45621
|
-
snap: number,
|
|
45622
|
-
constraintFn: string,
|
|
45623
|
-
constraintXMin: string,
|
|
45624
|
-
constraintXMax: string,
|
|
45625
|
-
constraintYMin: string,
|
|
45626
|
-
constraintYMax: string
|
|
45627
|
-
})
|
|
45628
|
-
});
|
|
45629
|
-
const parseMovablePointElement = object({
|
|
45630
|
-
type: constant("movable-point"),
|
|
45631
|
-
key: parseKey,
|
|
45632
|
-
options: object({
|
|
45633
|
-
startX: string,
|
|
45634
|
-
startY: string,
|
|
45635
|
-
varSubscript: number,
|
|
45636
|
-
constraint: string,
|
|
45637
|
-
snap: number,
|
|
45638
|
-
constraintFn: string,
|
|
45639
|
-
constraintXMin: stringOrEmpty,
|
|
45640
|
-
constraintXMax: stringOrEmpty,
|
|
45641
|
-
constraintYMin: stringOrEmpty,
|
|
45642
|
-
constraintYMax: stringOrEmpty
|
|
45643
|
-
})
|
|
45644
|
-
});
|
|
45645
|
-
const parseParametricElement = object({
|
|
45646
|
-
type: constant("parametric"),
|
|
45647
|
-
key: parseKey,
|
|
45648
|
-
options: object({
|
|
45649
|
-
x: string,
|
|
45650
|
-
y: string,
|
|
45651
|
-
rangeMin: string,
|
|
45652
|
-
rangeMax: string,
|
|
45653
|
-
color: string,
|
|
45654
|
-
strokeDasharray: string,
|
|
45655
|
-
strokeWidth: number
|
|
45656
|
-
})
|
|
45657
|
-
});
|
|
45658
|
-
const parsePointElement = object({
|
|
45659
|
-
type: constant("point"),
|
|
45660
|
-
key: parseKey,
|
|
45661
|
-
options: object({
|
|
45662
|
-
color: string,
|
|
45663
|
-
coordX: string,
|
|
45664
|
-
coordY: string
|
|
45665
|
-
})
|
|
45666
|
-
});
|
|
45667
|
-
const parseRectangleElement = object({
|
|
45668
|
-
type: constant("rectangle"),
|
|
45669
|
-
key: parseKey,
|
|
45670
|
-
options: object({
|
|
45671
|
-
color: string,
|
|
45672
|
-
coordX: string,
|
|
45673
|
-
coordY: string,
|
|
45674
|
-
width: string,
|
|
45675
|
-
height: string
|
|
45676
|
-
})
|
|
45677
|
-
});
|
|
45678
|
-
const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
45679
|
-
static: defaulted(boolean, () => false),
|
|
45680
|
-
graph: object({
|
|
45681
|
-
editableSettings: optional(array(enumeration("canvas", "graph"))),
|
|
45682
|
-
box: pairOfNumbers$1,
|
|
45683
|
-
labels: array(string),
|
|
45684
|
-
range: pair(pairOfNumbers$1, pairOfNumbers$1),
|
|
45685
|
-
gridStep: pairOfNumbers$1,
|
|
45686
|
-
markings: enumeration("graph", "grid", "none"),
|
|
45687
|
-
snapStep: optional(pairOfNumbers$1),
|
|
45688
|
-
valid: optional(union(boolean).or(string).parser),
|
|
45689
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
45690
|
-
showProtractor: optional(boolean),
|
|
45691
|
-
showRuler: optional(boolean),
|
|
45692
|
-
rulerLabel: optional(string),
|
|
45693
|
-
rulerTicks: optional(number),
|
|
45694
|
-
tickStep: pairOfNumbers$1
|
|
45695
|
-
}),
|
|
45696
|
-
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)
|
|
45697
|
-
}));
|
|
45698
|
-
|
|
45699
|
-
// Used to represent 2-D points and ranges
|
|
45700
|
-
const pairOfNumbers = pair(number, number);
|
|
45701
|
-
const parsePerseusGraphTypeAngle = object({
|
|
45702
|
-
type: constant("angle"),
|
|
45703
|
-
showAngles: optional(boolean),
|
|
45704
|
-
allowReflexAngles: optional(boolean),
|
|
45705
|
-
angleOffsetDeg: optional(number),
|
|
45706
|
-
snapDegrees: optional(number),
|
|
45707
|
-
match: optional(constant("congruent")),
|
|
45708
|
-
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
45709
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
45710
|
-
});
|
|
45711
|
-
const parsePerseusGraphTypeCircle = object({
|
|
45712
|
-
type: constant("circle"),
|
|
45713
|
-
center: optional(pairOfNumbers),
|
|
45714
|
-
radius: optional(number),
|
|
45715
|
-
startCoords: optional(object({
|
|
45716
|
-
center: pairOfNumbers,
|
|
45717
|
-
radius: number
|
|
45718
|
-
})),
|
|
45719
|
-
// TODO: remove coord? it's legacy.
|
|
45720
|
-
coord: optional(pairOfNumbers)
|
|
45721
|
-
});
|
|
45722
|
-
const parsePerseusGraphTypeLinear = object({
|
|
45723
|
-
type: constant("linear"),
|
|
45724
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
45725
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
45726
|
-
// TODO: remove coord? it's legacy.
|
|
45727
|
-
coord: optional(pairOfNumbers)
|
|
45728
|
-
});
|
|
45729
|
-
const parsePerseusGraphTypeLinearSystem = object({
|
|
45730
|
-
type: constant("linear-system"),
|
|
45731
|
-
// TODO(benchristel): default coords to empty array?
|
|
45732
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
45733
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
45734
|
-
// TODO: remove coord? it's legacy.
|
|
45735
|
-
coord: optional(pairOfNumbers)
|
|
45736
|
-
});
|
|
45737
|
-
const parsePerseusGraphTypeNone = object({
|
|
45738
|
-
type: constant("none")
|
|
45739
|
-
});
|
|
45740
|
-
const parsePerseusGraphTypePoint = object({
|
|
45741
|
-
type: constant("point"),
|
|
45742
|
-
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
45743
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
45744
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45745
|
-
// TODO: remove coord? it's legacy.
|
|
45746
|
-
coord: optional(pairOfNumbers)
|
|
45747
|
-
});
|
|
45748
|
-
const parsePerseusGraphTypePolygon = object({
|
|
45749
|
-
type: constant("polygon"),
|
|
45750
|
-
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
45751
|
-
showAngles: optional(boolean),
|
|
45752
|
-
showSides: optional(boolean),
|
|
45753
|
-
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
45754
|
-
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
45755
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45756
|
-
// TODO: remove coord? it's legacy.
|
|
45757
|
-
coord: optional(pairOfNumbers)
|
|
45758
|
-
});
|
|
45759
|
-
const parsePerseusGraphTypeQuadratic = object({
|
|
45760
|
-
type: constant("quadratic"),
|
|
45761
|
-
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
45762
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
45763
|
-
// TODO: remove coord? it's legacy.
|
|
45764
|
-
coord: optional(pairOfNumbers)
|
|
45765
|
-
});
|
|
45766
|
-
const parsePerseusGraphTypeRay = object({
|
|
45767
|
-
type: constant("ray"),
|
|
45768
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
45769
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
45770
|
-
// TODO: remove coord? it's legacy.
|
|
45771
|
-
coord: optional(pairOfNumbers)
|
|
45772
|
-
});
|
|
45773
|
-
const parsePerseusGraphTypeSegment = object({
|
|
45774
|
-
type: constant("segment"),
|
|
45775
|
-
// TODO(benchristel): default numSegments?
|
|
45776
|
-
numSegments: optional(number),
|
|
45777
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
45778
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
45779
|
-
// TODO: remove coord? it's legacy.
|
|
45780
|
-
coord: optional(pairOfNumbers)
|
|
45781
|
-
});
|
|
45782
|
-
const parsePerseusGraphTypeSinusoid = object({
|
|
45783
|
-
type: constant("sinusoid"),
|
|
45784
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
45785
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45786
|
-
// TODO: remove coord? it's legacy.
|
|
45787
|
-
coord: optional(pairOfNumbers)
|
|
45788
|
-
});
|
|
45789
|
-
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;
|
|
45790
|
-
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
45791
|
-
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
45792
|
-
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
45793
|
-
const parseLockedLabelType = object({
|
|
45794
|
-
type: constant("label"),
|
|
45795
|
-
coord: pairOfNumbers,
|
|
45796
|
-
text: string,
|
|
45797
|
-
color: parseLockedFigureColor,
|
|
45798
|
-
size: enumeration("small", "medium", "large")
|
|
45799
|
-
});
|
|
45800
|
-
const parseLockedPointType = object({
|
|
45801
|
-
type: constant("point"),
|
|
45802
|
-
coord: pairOfNumbers,
|
|
45803
|
-
color: parseLockedFigureColor,
|
|
45804
|
-
filled: boolean,
|
|
45805
|
-
// TODO(benchristel): default labels to empty array?
|
|
45806
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45807
|
-
ariaLabel: optional(string)
|
|
45808
|
-
});
|
|
45809
|
-
const parseLockedLineType = object({
|
|
45810
|
-
type: constant("line"),
|
|
45811
|
-
kind: enumeration("line", "ray", "segment"),
|
|
45812
|
-
points: pair(parseLockedPointType, parseLockedPointType),
|
|
45813
|
-
color: parseLockedFigureColor,
|
|
45814
|
-
lineStyle: parseLockedLineStyle,
|
|
45815
|
-
showPoint1: defaulted(boolean, () => false),
|
|
45816
|
-
showPoint2: defaulted(boolean, () => false),
|
|
45817
|
-
// TODO(benchristel): default labels to empty array?
|
|
45818
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45819
|
-
ariaLabel: optional(string)
|
|
45820
|
-
});
|
|
45821
|
-
const parseLockedVectorType = object({
|
|
45822
|
-
type: constant("vector"),
|
|
45823
|
-
points: pair(pairOfNumbers, pairOfNumbers),
|
|
45824
|
-
color: parseLockedFigureColor,
|
|
45825
|
-
// TODO(benchristel): default labels to empty array?
|
|
45826
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45827
|
-
ariaLabel: optional(string)
|
|
45828
|
-
});
|
|
45829
|
-
const parseLockedEllipseType = object({
|
|
45830
|
-
type: constant("ellipse"),
|
|
45831
|
-
center: pairOfNumbers,
|
|
45832
|
-
radius: pairOfNumbers,
|
|
45833
|
-
angle: number,
|
|
45834
|
-
color: parseLockedFigureColor,
|
|
45835
|
-
fillStyle: parseLockedFigureFillType,
|
|
45836
|
-
strokeStyle: parseLockedLineStyle,
|
|
45837
|
-
// TODO(benchristel): default labels to empty array?
|
|
45838
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45839
|
-
ariaLabel: optional(string)
|
|
45840
|
-
});
|
|
45841
|
-
const parseLockedPolygonType = object({
|
|
45842
|
-
type: constant("polygon"),
|
|
45843
|
-
points: array(pairOfNumbers),
|
|
45844
|
-
color: parseLockedFigureColor,
|
|
45845
|
-
showVertices: boolean,
|
|
45846
|
-
fillStyle: parseLockedFigureFillType,
|
|
45847
|
-
strokeStyle: parseLockedLineStyle,
|
|
45848
|
-
// TODO(benchristel): default labels to empty array?
|
|
45849
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45850
|
-
ariaLabel: optional(string)
|
|
45851
|
-
});
|
|
45852
|
-
const parseLockedFunctionType = object({
|
|
45853
|
-
type: constant("function"),
|
|
45854
|
-
color: parseLockedFigureColor,
|
|
45855
|
-
strokeStyle: parseLockedLineStyle,
|
|
45856
|
-
equation: string,
|
|
45857
|
-
directionalAxis: enumeration("x", "y"),
|
|
45858
|
-
domain: optional(pairOfNumbers),
|
|
45859
|
-
// TODO(benchristel): default labels to empty array?
|
|
45860
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45861
|
-
ariaLabel: optional(string)
|
|
45862
|
-
});
|
|
45863
|
-
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;
|
|
45864
|
-
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
45865
|
-
step: pairOfNumbers,
|
|
45866
|
-
// TODO(benchristel): rather than making gridStep and snapStep
|
|
45867
|
-
// optional, we should duplicate the defaulting logic from the
|
|
45868
|
-
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
45869
|
-
// why.
|
|
45870
|
-
gridStep: optional(pairOfNumbers),
|
|
45871
|
-
snapStep: optional(pairOfNumbers),
|
|
45872
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
45873
|
-
markings: enumeration("graph", "grid", "none"),
|
|
45874
|
-
labels: optional(array(string)),
|
|
45875
|
-
showProtractor: boolean,
|
|
45876
|
-
showRuler: optional(boolean),
|
|
45877
|
-
showTooltips: optional(boolean),
|
|
45878
|
-
rulerLabel: optional(string),
|
|
45879
|
-
rulerTicks: optional(number),
|
|
45880
|
-
range: pair(pairOfNumbers, pairOfNumbers),
|
|
45881
|
-
// NOTE(benchristel): I copied the default graph from
|
|
45882
|
-
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
45883
|
-
// an explanation of why we want to duplicate the default here.
|
|
45884
|
-
graph: defaulted(parsePerseusGraphType, () => ({
|
|
45885
|
-
type: "linear"
|
|
45886
|
-
})),
|
|
45887
|
-
correct: parsePerseusGraphType,
|
|
45888
|
-
// TODO(benchristel): default lockedFigures to empty array
|
|
45889
|
-
lockedFigures: optional(array(parseLockedFigure)),
|
|
45890
|
-
fullGraphLabel: optional(string),
|
|
45891
|
-
fullGraphAriaDescription: optional(string)
|
|
45892
|
-
}));
|
|
45893
|
-
|
|
45894
|
-
const parseLabelImageWidget = parseWidget(constant("label-image"), object({
|
|
45895
|
-
choices: array(string),
|
|
45896
|
-
imageUrl: string,
|
|
45897
|
-
imageAlt: string,
|
|
45898
|
-
imageHeight: number,
|
|
45899
|
-
imageWidth: number,
|
|
45900
|
-
markers: array(object({
|
|
45901
|
-
answers: array(string),
|
|
45902
|
-
label: string,
|
|
45903
|
-
x: number,
|
|
45904
|
-
y: number
|
|
45905
|
-
})),
|
|
45906
|
-
hideChoicesFromInstructions: boolean,
|
|
45907
|
-
multipleAnswers: boolean,
|
|
45908
|
-
static: defaulted(boolean, () => false)
|
|
45909
|
-
}));
|
|
45910
|
-
|
|
45911
|
-
const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
45912
|
-
labels: array(string),
|
|
45913
|
-
left: array(string),
|
|
45914
|
-
right: array(string),
|
|
45915
|
-
orderMatters: boolean,
|
|
45916
|
-
padding: boolean
|
|
45917
|
-
}));
|
|
45918
|
-
|
|
45919
|
-
const numberOrString = union(number).or(string).parser;
|
|
45920
|
-
const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
|
|
45921
|
-
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
45922
|
-
prefix: optional(string),
|
|
45923
|
-
suffix: optional(string),
|
|
45924
|
-
answers: array(array(numeric)),
|
|
45925
|
-
cursorPosition: optional(array(number)),
|
|
45926
|
-
matrixBoardSize: array(number),
|
|
45927
|
-
static: optional(boolean)
|
|
45928
|
-
}));
|
|
45929
|
-
|
|
45930
|
-
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
45931
|
-
// The default value for image comes from measurer.tsx.
|
|
45932
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
45933
|
-
// defaults here.
|
|
45934
|
-
image: defaulted(parsePerseusImageBackground, () => ({
|
|
45935
|
-
url: null,
|
|
45936
|
-
top: 0,
|
|
45937
|
-
left: 0
|
|
45938
|
-
})),
|
|
45939
|
-
showProtractor: boolean,
|
|
45940
|
-
showRuler: boolean,
|
|
45941
|
-
rulerLabel: string,
|
|
45942
|
-
rulerTicks: number,
|
|
45943
|
-
rulerPixels: number,
|
|
45944
|
-
rulerLength: number,
|
|
45945
|
-
box: pair(number, number),
|
|
45946
|
-
// TODO(benchristel): static is not used. Remove it?
|
|
45947
|
-
static: defaulted(boolean, () => false)
|
|
45948
|
-
}));
|
|
45949
|
-
|
|
45950
|
-
const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
|
|
45951
|
-
widgetId: string,
|
|
45952
|
-
rotationAngle: optional(number),
|
|
45953
|
-
smiles: optional(string)
|
|
45954
|
-
}));
|
|
45955
|
-
|
|
45956
|
-
const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
|
|
45957
|
-
const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
45958
|
-
range: array(number),
|
|
45959
|
-
labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
|
|
45960
|
-
labelStyle: string,
|
|
45961
|
-
labelTicks: boolean,
|
|
45962
|
-
isTickCtrl: optional(nullable(boolean)),
|
|
45963
|
-
divisionRange: array(number),
|
|
45964
|
-
numDivisions: optional(nullable(number)),
|
|
45965
|
-
// NOTE(benchristel): I copied the default snapDivisions from
|
|
45966
|
-
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
45967
|
-
// an explanation of why we want to duplicate the default here.
|
|
45968
|
-
snapDivisions: defaulted(number, () => 2),
|
|
45969
|
-
tickStep: optional(nullable(number)),
|
|
45970
|
-
correctRel: optional(nullable(string)),
|
|
45971
|
-
correctX: nullable(number),
|
|
45972
|
-
initialX: optional(nullable(number)),
|
|
45973
|
-
showTooltip: optional(boolean),
|
|
45974
|
-
static: defaulted(boolean, () => false)
|
|
45975
|
-
}));
|
|
45976
|
-
|
|
45977
|
-
const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
|
|
45978
|
-
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
45979
|
-
answers: array(object({
|
|
45980
|
-
message: string,
|
|
45981
|
-
// TODO(benchristel): value should never be null or undefined,
|
|
45982
|
-
// but we have some content where it is anyway. If we backfill
|
|
45983
|
-
// the data, simplify this.
|
|
45984
|
-
value: optional(nullable(number)),
|
|
45985
|
-
status: string,
|
|
45986
|
-
answerForms: optional(array(parseMathFormat)),
|
|
45987
|
-
strict: boolean,
|
|
45988
|
-
maxError: optional(nullable(number)),
|
|
45989
|
-
// TODO(benchristel): simplify should never be a boolean, but we
|
|
45990
|
-
// have some content where it is anyway. If we ever backfill
|
|
45991
|
-
// the data, we should simplify `simplify`.
|
|
45992
|
-
simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
|
|
45993
|
-
})),
|
|
45994
|
-
labelText: optional(string),
|
|
45995
|
-
size: string,
|
|
45996
|
-
coefficient: defaulted(boolean, () => false),
|
|
45997
|
-
rightAlign: optional(boolean),
|
|
45998
|
-
static: defaulted(boolean, () => false),
|
|
45999
|
-
answerForms: optional(array(object({
|
|
46000
|
-
name: parseMathFormat,
|
|
46001
|
-
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
46002
|
-
})))
|
|
46003
|
-
}));
|
|
46004
|
-
|
|
46005
|
-
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
46006
|
-
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
46007
|
-
// it's defined.
|
|
46008
|
-
function parseRenderer(rawValue, ctx) {
|
|
46009
|
-
return parsePerseusRenderer(rawValue, ctx);
|
|
46010
|
-
}
|
|
46011
|
-
const largeToAuto = (height, ctx) => {
|
|
46012
|
-
if (height === "large") {
|
|
46013
|
-
return ctx.success("auto");
|
|
46014
|
-
}
|
|
46015
|
-
return ctx.success(height);
|
|
46016
|
-
};
|
|
46017
|
-
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
46018
|
-
options: defaulted(array(parseRenderer), () => []),
|
|
46019
|
-
correctOptions: array(parseRenderer),
|
|
46020
|
-
otherOptions: array(parseRenderer),
|
|
46021
|
-
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
46022
|
-
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
46023
|
-
}));
|
|
46024
|
-
|
|
46025
|
-
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
46026
|
-
passageNumber: number,
|
|
46027
|
-
referenceNumber: number,
|
|
46028
|
-
summaryText: optional(string)
|
|
46029
|
-
}));
|
|
46030
|
-
|
|
46031
|
-
const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
46032
|
-
footnotes: defaulted(string, () => ""),
|
|
46033
|
-
passageText: string,
|
|
46034
|
-
passageTitle: defaulted(string, () => ""),
|
|
46035
|
-
showLineNumbers: boolean,
|
|
46036
|
-
static: defaulted(boolean, () => false)
|
|
46037
|
-
}));
|
|
46038
|
-
|
|
46039
|
-
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
46040
|
-
url: string,
|
|
46041
|
-
description: string
|
|
46042
|
-
}));
|
|
46043
|
-
|
|
46044
|
-
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
46045
|
-
labels: array(string),
|
|
46046
|
-
categories: array(string),
|
|
46047
|
-
type: enumeration(...plotterPlotTypes),
|
|
46048
|
-
maxY: number,
|
|
46049
|
-
// The default value for scaleY comes from plotter.tsx.
|
|
46050
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46051
|
-
// defaults here.
|
|
46052
|
-
scaleY: defaulted(number, () => 1),
|
|
46053
|
-
labelInterval: optional(nullable(number)),
|
|
46054
|
-
// The default value for snapsPerLine comes from plotter.tsx.
|
|
46055
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46056
|
-
// defaults here.
|
|
46057
|
-
snapsPerLine: defaulted(number, () => 2),
|
|
46058
|
-
starting: array(number),
|
|
46059
|
-
correct: array(number),
|
|
46060
|
-
picUrl: optional(nullable(string)),
|
|
46061
|
-
picSize: optional(nullable(number)),
|
|
46062
|
-
picBoxHeight: optional(nullable(number)),
|
|
46063
|
-
// NOTE(benchristel): I copied the default plotDimensions from
|
|
46064
|
-
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
46065
|
-
// of why we want to duplicate the defaults here.
|
|
46066
|
-
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
46067
|
-
}));
|
|
46068
|
-
|
|
46069
|
-
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
46070
|
-
programID: string,
|
|
46071
|
-
height: number
|
|
46072
|
-
}));
|
|
46073
|
-
|
|
46074
|
-
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
46075
|
-
choices: array(object({
|
|
46076
|
-
content: defaulted(string, () => ""),
|
|
46077
|
-
clue: optional(string),
|
|
46078
|
-
correct: optional(boolean),
|
|
46079
|
-
isNoneOfTheAbove: optional(boolean),
|
|
46080
|
-
// deprecated
|
|
46081
|
-
// There is an import cycle between radio-widget.ts and
|
|
46082
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
46083
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
46084
|
-
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
46085
|
-
})),
|
|
46086
|
-
hasNoneOfTheAbove: optional(boolean),
|
|
46087
|
-
countChoices: optional(boolean),
|
|
46088
|
-
randomize: optional(boolean),
|
|
46089
|
-
multipleSelect: optional(boolean),
|
|
46090
|
-
deselectEnabled: optional(boolean),
|
|
46091
|
-
// deprecated
|
|
46092
|
-
onePerLine: optional(boolean),
|
|
46093
|
-
// deprecated
|
|
46094
|
-
displayCount: optional(any),
|
|
46095
|
-
// v0 props
|
|
46096
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
46097
|
-
noneOfTheAbove: optional(constant(false))
|
|
46098
|
-
}));
|
|
46099
|
-
|
|
46100
|
-
const parseSorterWidget = parseWidget(constant("sorter"), object({
|
|
46101
|
-
correct: array(string),
|
|
46102
|
-
padding: boolean,
|
|
46103
|
-
layout: enumeration("horizontal", "vertical")
|
|
46104
|
-
}));
|
|
46105
|
-
|
|
46106
|
-
const parseTableWidget = parseWidget(constant("table"), object({
|
|
46107
|
-
headers: array(string),
|
|
46108
|
-
rows: number,
|
|
46109
|
-
columns: number,
|
|
46110
|
-
answers: array(array(string))
|
|
46111
|
-
}));
|
|
46112
|
-
|
|
46113
|
-
const parseVideoWidget = parseWidget(constant("video"), object({
|
|
46114
|
-
location: string,
|
|
46115
|
-
static: optional(boolean)
|
|
46116
|
-
}));
|
|
46117
|
-
|
|
46118
|
-
const parseWidgetsMap = (rawValue, ctx) => {
|
|
46119
|
-
if (!isObject(rawValue)) {
|
|
46120
|
-
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
46121
|
-
}
|
|
46122
|
-
const widgetsMap = {};
|
|
46123
|
-
for (const key of Object.keys(rawValue)) {
|
|
46124
|
-
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
46125
|
-
// but it's the only way I could find to make TypeScript check the key
|
|
46126
|
-
// against the widget type.
|
|
46127
|
-
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
46128
|
-
if (isFailure(entryResult)) {
|
|
46129
|
-
return entryResult;
|
|
46130
|
-
}
|
|
46131
|
-
}
|
|
46132
|
-
return ctx.success(widgetsMap);
|
|
46133
|
-
};
|
|
46134
|
-
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
46135
|
-
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
46136
|
-
if (isFailure(idComponentsResult)) {
|
|
46137
|
-
return idComponentsResult;
|
|
46138
|
-
}
|
|
46139
|
-
const [type, n] = idComponentsResult.value;
|
|
46140
|
-
function parseAndAssign(key, parse) {
|
|
46141
|
-
const widgetResult = parse(widget, ctx);
|
|
46142
|
-
if (isFailure(widgetResult)) {
|
|
46143
|
-
return widgetResult;
|
|
46144
|
-
}
|
|
46145
|
-
widgetMap[key] = widgetResult.value;
|
|
46146
|
-
return ctx.success(undefined);
|
|
46147
|
-
}
|
|
46148
|
-
switch (type) {
|
|
46149
|
-
case "categorizer":
|
|
46150
|
-
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
46151
|
-
case "cs-program":
|
|
46152
|
-
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
46153
|
-
case "definition":
|
|
46154
|
-
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
46155
|
-
case "dropdown":
|
|
46156
|
-
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
46157
|
-
case "explanation":
|
|
46158
|
-
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
46159
|
-
case "expression":
|
|
46160
|
-
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
46161
|
-
case "grapher":
|
|
46162
|
-
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
46163
|
-
case "group":
|
|
46164
|
-
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
46165
|
-
case "graded-group":
|
|
46166
|
-
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
46167
|
-
case "graded-group-set":
|
|
46168
|
-
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
46169
|
-
case "iframe":
|
|
46170
|
-
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
46171
|
-
case "image":
|
|
46172
|
-
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
46173
|
-
case "input-number":
|
|
46174
|
-
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
46175
|
-
case "interaction":
|
|
46176
|
-
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
46177
|
-
case "interactive-graph":
|
|
46178
|
-
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
46179
|
-
case "label-image":
|
|
46180
|
-
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
46181
|
-
case "matcher":
|
|
46182
|
-
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
46183
|
-
case "matrix":
|
|
46184
|
-
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
46185
|
-
case "measurer":
|
|
46186
|
-
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
46187
|
-
case "molecule-renderer":
|
|
46188
|
-
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
46189
|
-
case "number-line":
|
|
46190
|
-
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
46191
|
-
case "numeric-input":
|
|
46192
|
-
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
46193
|
-
case "orderer":
|
|
46194
|
-
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
46195
|
-
case "passage":
|
|
46196
|
-
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
46197
|
-
case "passage-ref":
|
|
46198
|
-
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
46199
|
-
case "passage-ref-target":
|
|
46200
|
-
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
46201
|
-
// used in test content. See:
|
|
46202
|
-
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
46203
|
-
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
46204
|
-
case "phet-simulation":
|
|
46205
|
-
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
46206
|
-
case "plotter":
|
|
46207
|
-
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
46208
|
-
case "python-program":
|
|
46209
|
-
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
46210
|
-
case "radio":
|
|
46211
|
-
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
46212
|
-
case "sorter":
|
|
46213
|
-
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
46214
|
-
case "table":
|
|
46215
|
-
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
46216
|
-
case "video":
|
|
46217
|
-
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
46218
|
-
case "sequence":
|
|
46219
|
-
// sequence is a deprecated widget type, and the corresponding
|
|
46220
|
-
// widget component no longer exists.
|
|
46221
|
-
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
46222
|
-
case "lights-puzzle":
|
|
46223
|
-
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
46224
|
-
case "simulator":
|
|
46225
|
-
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
46226
|
-
case "transformer":
|
|
46227
|
-
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
46228
|
-
default:
|
|
46229
|
-
if (getWidget(type)) {
|
|
46230
|
-
// @ts-expect-error - 'type' is not a valid widget type
|
|
46231
|
-
return parseAndAssign(`${type} ${n}`, any);
|
|
46232
|
-
}
|
|
46233
|
-
return ctx.failure("a valid widget type", type);
|
|
46234
|
-
}
|
|
46235
|
-
};
|
|
46236
|
-
const parseDeprecatedWidget = parseWidget(
|
|
46237
|
-
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
46238
|
-
(_, ctx) => ctx.success("deprecated-standin"),
|
|
46239
|
-
// Allow any widget options
|
|
46240
|
-
object({}));
|
|
46241
|
-
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
46242
|
-
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
46243
|
-
return ctx.failure("a string representing a positive integer", rawValue);
|
|
46244
|
-
}
|
|
46245
|
-
return ctx.success(+rawValue);
|
|
46246
|
-
};
|
|
46247
|
-
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
46248
|
-
|
|
46249
|
-
const parsePerseusRenderer = defaulted(object({
|
|
46250
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
46251
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
46252
|
-
content: defaulted(string, () => ""),
|
|
46253
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
46254
|
-
// `group` widget can contain another renderer.
|
|
46255
|
-
// The anonymous function below ensures that we don't try to access
|
|
46256
|
-
// parseWidgetsMap before it's defined.
|
|
46257
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
46258
|
-
metadata: optional(array(string)),
|
|
46259
|
-
images: parseImages
|
|
46260
|
-
}),
|
|
46261
|
-
// Default value
|
|
46262
|
-
() => ({
|
|
46263
|
-
content: "",
|
|
46264
|
-
widgets: {},
|
|
46265
|
-
images: {}
|
|
46266
|
-
}));
|
|
46267
|
-
|
|
46268
|
-
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
46269
|
-
|
|
46270
|
-
const parseHint = object({
|
|
46271
|
-
replace: optional(boolean),
|
|
46272
|
-
content: string,
|
|
46273
|
-
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
46274
|
-
metadata: optional(array(string)),
|
|
46275
|
-
images: parseImages
|
|
46276
|
-
});
|
|
46277
|
-
|
|
46278
|
-
const _excluded = ["type", "options"];
|
|
46279
|
-
const parsePerseusItem$1 = object({
|
|
46280
|
-
question: parsePerseusRenderer,
|
|
46281
|
-
hints: defaulted(array(parseHint), () => []),
|
|
46282
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
46283
|
-
itemDataVersion: optional(object({
|
|
46284
|
-
major: number,
|
|
46285
|
-
minor: number
|
|
46286
|
-
})),
|
|
46287
|
-
// Deprecated field
|
|
46288
|
-
answer: any
|
|
46289
|
-
});
|
|
46290
|
-
|
|
46291
|
-
// Some answerAreas have extra fields, like:
|
|
46292
|
-
//
|
|
46293
|
-
// "answerArea": {
|
|
46294
|
-
// "type": "multiple",
|
|
46295
|
-
// "options": {
|
|
46296
|
-
// "content": "",
|
|
46297
|
-
// "images": {},
|
|
46298
|
-
// "widgets": {}
|
|
46299
|
-
// }
|
|
46300
|
-
// }
|
|
46301
|
-
//
|
|
46302
|
-
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
46303
|
-
// migration function removes them.
|
|
46304
|
-
function migrateAnswerArea(rawValue, ctx) {
|
|
46305
|
-
const rest = _objectWithoutPropertiesLoose(rawValue, _excluded);
|
|
46306
|
-
return ctx.success(rest);
|
|
46307
|
-
}
|
|
46308
|
-
|
|
46309
|
-
/**
|
|
46310
|
-
* Helper to parse PerseusItem JSON
|
|
46311
|
-
* Why not just use JSON.parse? We want:
|
|
46312
|
-
* - To make sure types are correct
|
|
46313
|
-
* - To give us a central place to validate/transform output if needed
|
|
46314
|
-
* @deprecated - use parseAndMigratePerseusItem instead
|
|
46315
|
-
* @param {string} json - the stringified PerseusItem JSON
|
|
46316
|
-
* @returns {PerseusItem} the parsed PerseusItem object
|
|
46317
|
-
*/
|
|
46318
|
-
function parsePerseusItem(json) {
|
|
46319
|
-
// Try to block a cheating vector which relies on monkey-patching
|
|
46320
|
-
// JSON.parse
|
|
46321
|
-
if (isRealJSONParse(JSON.parse)) {
|
|
46322
|
-
return JSON.parse(json);
|
|
46323
|
-
}
|
|
46324
|
-
throw new Error("Something went wrong.");
|
|
46325
|
-
}
|
|
46326
|
-
/**
|
|
46327
|
-
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
46328
|
-
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
46329
|
-
* data.
|
|
46330
|
-
*
|
|
46331
|
-
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
46332
|
-
* failure, it will contain an error message describing where in the tree
|
|
46333
|
-
* parsing failed.
|
|
46334
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46335
|
-
*/
|
|
46336
|
-
function parseAndMigratePerseusItem(json) {
|
|
46337
|
-
throwErrorIfCheatingDetected();
|
|
46338
|
-
const object = JSON.parse(json);
|
|
46339
|
-
const result = parse(object, parsePerseusItem$1);
|
|
46340
|
-
if (isFailure(result)) {
|
|
46341
|
-
return failure({
|
|
46342
|
-
message: result.detail,
|
|
46343
|
-
invalidObject: object
|
|
46344
|
-
});
|
|
46345
|
-
}
|
|
46346
|
-
return result;
|
|
46347
|
-
}
|
|
46348
|
-
|
|
46349
|
-
/**
|
|
46350
|
-
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
46351
|
-
* latest schema, and runtime-typechecks the result.
|
|
46352
|
-
*
|
|
46353
|
-
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
46354
|
-
* failure, it will contain an error message describing where in the tree
|
|
46355
|
-
* parsing failed.
|
|
46356
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46357
|
-
*/
|
|
46358
|
-
function parseAndMigratePerseusArticle(json) {
|
|
46359
|
-
throwErrorIfCheatingDetected();
|
|
46360
|
-
const object = JSON.parse(json);
|
|
46361
|
-
const result = parse(object, parsePerseusArticle);
|
|
46362
|
-
if (isFailure(result)) {
|
|
46363
|
-
return failure({
|
|
46364
|
-
message: result.detail,
|
|
46365
|
-
invalidObject: object
|
|
46366
|
-
});
|
|
46367
|
-
}
|
|
46368
|
-
return result;
|
|
46369
|
-
}
|
|
46370
|
-
|
|
46371
|
-
/**
|
|
46372
|
-
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
46373
|
-
*/
|
|
46374
|
-
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
46375
|
-
function throwErrorIfCheatingDetected() {
|
|
46376
|
-
if (!isRealJSONParse(JSON.parse)) {
|
|
46377
|
-
throw new Error("Something went wrong.");
|
|
46378
|
-
}
|
|
46379
|
-
}
|
|
46380
|
-
|
|
46381
44867
|
const genericPerseusItemData = {
|
|
46382
44868
|
question: {
|
|
46383
44869
|
content: "",
|
|
@@ -46595,5 +45081,5 @@ const ScoringUtil = {
|
|
|
46595
45081
|
* Types
|
|
46596
45082
|
*/
|
|
46597
45083
|
|
|
46598
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget,
|
|
45084
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
46599
45085
|
//# sourceMappingURL=index.js.map
|