@khanacademy/perseus 41.1.1 → 41.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-widgets.d.ts +1 -1
- package/dist/article-renderer.d.ts +2 -2
- package/dist/basic-widgets.d.ts +1 -1
- package/dist/es/index.js +1663 -397
- package/dist/es/index.js.map +1 -1
- package/dist/extra-widgets.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1583 -316
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -8
- package/dist/perseus-api.d.ts +3 -3
- package/dist/perseus-types.d.ts +7 -2
- package/dist/prompt-types.d.ts +33 -0
- package/dist/renderer.d.ts +8 -2
- package/dist/server-item-renderer.d.ts +6 -4
- package/dist/types.d.ts +4 -2
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +15 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/util/{parse-perseus-json.d.ts → parse-perseus-json/index.d.ts} +7 -1
- package/dist/util/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse.d.ts +3 -0
- package/dist/util/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/util/parse-perseus-json/result.d.ts +16 -0
- package/dist/validation.types.d.ts +0 -3
- package/dist/widget-ai-utils/categorizer/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/cs-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/definition/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/dropdown/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/explanation/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/expression/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/graded-group/prompt-utils.d.ts +7 -0
- package/dist/widget-ai-utils/graded-group-set/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/grapher/prompt-utils.d.ts +18 -0
- package/dist/widget-ai-utils/group/prompt-utils.d.ts +5 -0
- package/dist/widget-ai-utils/iframe/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/image/prompt-utils.d.ts +12 -0
- package/dist/widget-ai-utils/input-number/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/interaction/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/interactive-graph/prompt-utils.d.ts +94 -0
- package/dist/widget-ai-utils/label-image/prompt-utils.d.ts +24 -0
- package/dist/widget-ai-utils/matcher/prompt-utils.d.ts +17 -0
- package/dist/widget-ai-utils/matrix/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/measurer/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/number-line/prompt-utils.d.ts +16 -0
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/orderer/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/passage/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/passage-ref/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/phet-simulation/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/plotter/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/python-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/radio/prompt-utils.d.ts +15 -0
- package/dist/widget-ai-utils/sorter/prompt-utils.d.ts +9 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +11 -0
- package/dist/widget-ai-utils/video/prompt-utils.d.ts +2 -0
- package/dist/widgets/__shared__/{noop-validator.d.ts → score-noop.d.ts} +4 -4
- package/dist/widgets/categorizer/categorizer.d.ts +10 -19
- package/dist/widgets/categorizer/{categorizer-validator.d.ts → score-categorizer.d.ts} +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +8 -19
- package/dist/widgets/cs-program/{cs-program-validator.d.ts → score-cs-program.d.ts} +2 -1
- package/dist/widgets/definition/definition.d.ts +8 -19
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +4 -19
- package/dist/widgets/dropdown/dropdown.d.ts +10 -19
- package/dist/widgets/dropdown/score-dropdown.d.ts +4 -0
- package/dist/widgets/explanation/explanation.d.ts +9 -19
- package/dist/widgets/expression/expression.d.ts +26 -290
- package/dist/widgets/expression/{expression-validator.d.ts → score-expression.d.ts} +2 -2
- package/dist/widgets/graded-group/graded-group.d.ts +13 -21
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +9 -20
- package/dist/widgets/grapher/grapher.d.ts +9 -19
- package/dist/widgets/grapher/score-grapher.d.ts +4 -0
- package/dist/widgets/group/group.d.ts +7 -19
- package/dist/widgets/iframe/iframe.d.ts +7 -19
- package/dist/widgets/iframe/{iframe-validator.d.ts → score-iframe.d.ts} +1 -1
- package/dist/widgets/image/image.d.ts +9 -19
- package/dist/widgets/input-number/input-number.d.ts +11 -19
- package/dist/widgets/input-number/{input-number-validator.d.ts → score-input-number.d.ts} +2 -2
- package/dist/widgets/interaction/interaction.d.ts +7 -19
- package/dist/widgets/interactive-graph.d.ts +8 -19
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +4 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +6 -0
- package/dist/widgets/label-image/label-image.d.ts +10 -86
- package/dist/widgets/label-image/{label-image-validator.d.ts → score-label-image.d.ts} +2 -2
- package/dist/widgets/matcher/matcher.d.ts +7 -19
- package/dist/widgets/matcher/score-matcher.d.ts +4 -0
- package/dist/widgets/matrix/matrix.d.ts +10 -19
- package/dist/widgets/matrix/{matrix-validator.d.ts → score-matrix.d.ts} +2 -2
- package/dist/widgets/measurer/measurer.d.ts +13 -19
- package/dist/widgets/molecule/molecule.d.ts +4 -19
- package/dist/widgets/number-line/number-line.d.ts +8 -19
- package/dist/widgets/number-line/score-number-line.d.ts +4 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +22 -19
- package/dist/widgets/numeric-input/{numeric-input-validator.d.ts → score-numeric-input.d.ts} +2 -2
- package/dist/widgets/orderer/orderer.d.ts +8 -19
- package/dist/widgets/orderer/{orderer-validator.d.ts → score-orderer.d.ts} +1 -1
- package/dist/widgets/passage/passage.d.ts +8 -19
- package/dist/widgets/passage-ref/passage-ref.d.ts +12 -19
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +11 -19
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +5 -19
- package/dist/widgets/plotter/plotter.d.ts +8 -19
- package/dist/widgets/plotter/score-plotter.d.ts +4 -0
- package/dist/widgets/python-program/python-program.d.ts +4 -19
- package/dist/widgets/radio/radio-component.d.ts +3 -1
- package/dist/widgets/radio/radio.d.ts +18 -19
- package/dist/widgets/radio/{radio-validator.d.ts → score-radio.d.ts} +2 -2
- package/dist/widgets/sorter/score-sorter.d.ts +4 -0
- package/dist/widgets/sorter/sorter.d.ts +7 -19
- package/dist/widgets/table/{table-validator.d.ts → score-table.d.ts} +2 -2
- package/dist/widgets/table/table.d.ts +8 -19
- package/dist/widgets/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +4 -4
- package/dist/widgets/dropdown/dropdown-validator.d.ts +0 -4
- package/dist/widgets/grapher/grapher-validator.d.ts +0 -4
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +0 -4
- package/dist/widgets/number-line/number-line-validator.d.ts +0 -4
- package/dist/widgets/plotter/plotter-validator.d.ts +0 -4
- package/dist/widgets/sorter/sorter-validator.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var kmath = require('@khanacademy/kmath');
|
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
26
26
|
var SimpleMarkdown = require('@khanacademy/simple-markdown');
|
|
27
|
-
var
|
|
27
|
+
var SimpleMarkdown$1 = require('@khanacademy/pure-markdown');
|
|
28
28
|
var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
|
|
29
29
|
var Button = require('@khanacademy/wonder-blocks-button');
|
|
30
30
|
var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
|
|
@@ -77,6 +77,7 @@ var Clickable__default = /*#__PURE__*/_interopDefaultLegacy(Clickable);
|
|
|
77
77
|
var $__default = /*#__PURE__*/_interopDefaultLegacy($);
|
|
78
78
|
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
79
79
|
var SimpleMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(SimpleMarkdown);
|
|
80
|
+
var SimpleMarkdown__namespace = /*#__PURE__*/_interopNamespace(SimpleMarkdown$1);
|
|
80
81
|
var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
|
|
81
82
|
var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
|
|
82
83
|
var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
|
|
@@ -1293,6 +1294,41 @@ const ClassNames = {
|
|
|
1293
1294
|
MOBILE: "perseus-mobile"
|
|
1294
1295
|
};
|
|
1295
1296
|
|
|
1297
|
+
const getPromptJSON$t = (renderProps, userInput) => {
|
|
1298
|
+
return {
|
|
1299
|
+
type: "expression",
|
|
1300
|
+
label: renderProps.visibleLabel,
|
|
1301
|
+
userInput: {
|
|
1302
|
+
value: userInput
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Get the character used for separating decimals.
|
|
1309
|
+
*/
|
|
1310
|
+
const getDecimalSeparator = locale => {
|
|
1311
|
+
switch (locale) {
|
|
1312
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1313
|
+
// locale properly.
|
|
1314
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1315
|
+
//
|
|
1316
|
+
// Supported locales in Chrome:
|
|
1317
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1318
|
+
case "ka":
|
|
1319
|
+
return ",";
|
|
1320
|
+
default:
|
|
1321
|
+
const numberWithDecimalSeparator = 1.1;
|
|
1322
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1323
|
+
// support Safari 12.
|
|
1324
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1325
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1326
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1327
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
1328
|
+
return match?.[0] ?? ".";
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1296
1332
|
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1297
1333
|
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1298
1334
|
// which get's the injected logger.
|
|
@@ -2225,31 +2261,6 @@ const KhanAnswerTypes = {
|
|
|
2225
2261
|
}
|
|
2226
2262
|
};
|
|
2227
2263
|
|
|
2228
|
-
/**
|
|
2229
|
-
* Get the character used for separating decimals.
|
|
2230
|
-
*/
|
|
2231
|
-
const getDecimalSeparator = locale => {
|
|
2232
|
-
switch (locale) {
|
|
2233
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2234
|
-
// locale properly.
|
|
2235
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2236
|
-
//
|
|
2237
|
-
// Supported locales in Chrome:
|
|
2238
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2239
|
-
case "ka":
|
|
2240
|
-
return ",";
|
|
2241
|
-
default:
|
|
2242
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2243
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2244
|
-
// support Safari 12.
|
|
2245
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2246
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2247
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2248
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2249
|
-
return match?.[0] ?? ".";
|
|
2250
|
-
}
|
|
2251
|
-
};
|
|
2252
|
-
|
|
2253
2264
|
/* Content creators input a list of answers which are matched from top to
|
|
2254
2265
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2255
2266
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2268,7 +2279,7 @@ const getDecimalSeparator = locale => {
|
|
|
2268
2279
|
* show the user an error. TODO(joel) - what error?
|
|
2269
2280
|
* - Otherwise, pass through the resulting points and message.
|
|
2270
2281
|
*/
|
|
2271
|
-
function
|
|
2282
|
+
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2272
2283
|
const options = ___default["default"].clone(rubric);
|
|
2273
2284
|
___default["default"].extend(options, {
|
|
2274
2285
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2480,6 +2491,9 @@ class Expression extends React__namespace.Component {
|
|
|
2480
2491
|
getUserInput() {
|
|
2481
2492
|
return Expression.getUserInputFromProps(this.props);
|
|
2482
2493
|
}
|
|
2494
|
+
getPromptJSON() {
|
|
2495
|
+
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2496
|
+
}
|
|
2483
2497
|
change = (() => {
|
|
2484
2498
|
var _this = this;
|
|
2485
2499
|
return function () {
|
|
@@ -2764,7 +2778,7 @@ var Expression$1 = {
|
|
|
2764
2778
|
propUpgrades: propUpgrades$2,
|
|
2765
2779
|
// For use by the editor
|
|
2766
2780
|
isLintable: true,
|
|
2767
|
-
|
|
2781
|
+
scorer: scoreExpression,
|
|
2768
2782
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2769
2783
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2770
2784
|
if (correctAnswers.length === 0) {
|
|
@@ -18502,10 +18516,10 @@ const styles$C = aphrodite.StyleSheet.create({
|
|
|
18502
18516
|
|
|
18503
18517
|
/* eslint-disable no-useless-escape, no-prototype-builtins */
|
|
18504
18518
|
const rules$1 = {
|
|
18505
|
-
...
|
|
18519
|
+
...SimpleMarkdown$1.pureMarkdownRules,
|
|
18506
18520
|
// NOTE: basically ignored by JIPT. wraps everything at the outer layer
|
|
18507
18521
|
columns: {
|
|
18508
|
-
...
|
|
18522
|
+
...SimpleMarkdown$1.pureMarkdownRules.columns,
|
|
18509
18523
|
react: (node, output, state) => {
|
|
18510
18524
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
18511
18525
|
className: "perseus-two-columns",
|
|
@@ -18533,7 +18547,7 @@ const rules$1 = {
|
|
|
18533
18547
|
// for exercises the renderer just renders the crowdin id to the
|
|
18534
18548
|
// renderer div.
|
|
18535
18549
|
crowdinId: {
|
|
18536
|
-
...
|
|
18550
|
+
...SimpleMarkdown$1.pureMarkdownRules.crowdinId,
|
|
18537
18551
|
react: (node, output, state) => node.id
|
|
18538
18552
|
},
|
|
18539
18553
|
// Ensure that the table is focusable in sequential keyboard navigation
|
|
@@ -18542,7 +18556,7 @@ const rules$1 = {
|
|
|
18542
18556
|
// left/right gesture. Note, setting `tabindex` doesn't appear required for
|
|
18543
18557
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18544
18558
|
table: {
|
|
18545
|
-
...
|
|
18559
|
+
...SimpleMarkdown$1.pureMarkdownRules.table,
|
|
18546
18560
|
react: function (node, output, state) {
|
|
18547
18561
|
const table = SimpleMarkdown__default["default"].defaultRules.table.react(node, output, state);
|
|
18548
18562
|
if (!state.isMobile) {
|
|
@@ -18560,7 +18574,7 @@ const rules$1 = {
|
|
|
18560
18574
|
// our table regex into tableCapture[1..], and we pass tableCapture to
|
|
18561
18575
|
// our nptable regex
|
|
18562
18576
|
titledTable: {
|
|
18563
|
-
...
|
|
18577
|
+
...SimpleMarkdown$1.pureMarkdownRules.titledTable,
|
|
18564
18578
|
react: (node, output, state) => {
|
|
18565
18579
|
let contents;
|
|
18566
18580
|
if (!node.table) {
|
|
@@ -18620,7 +18634,7 @@ const rules$1 = {
|
|
|
18620
18634
|
}
|
|
18621
18635
|
},
|
|
18622
18636
|
widget: {
|
|
18623
|
-
...
|
|
18637
|
+
...SimpleMarkdown$1.pureMarkdownRules.widget,
|
|
18624
18638
|
react: (node, output, state) => {
|
|
18625
18639
|
// The actual output is handled in the renderer, where
|
|
18626
18640
|
// we know the current widget props/state. This is
|
|
@@ -18631,7 +18645,7 @@ const rules$1 = {
|
|
|
18631
18645
|
}
|
|
18632
18646
|
},
|
|
18633
18647
|
blockMath: {
|
|
18634
|
-
...
|
|
18648
|
+
...SimpleMarkdown$1.pureMarkdownRules.blockMath,
|
|
18635
18649
|
react: (node, output, state) => {
|
|
18636
18650
|
const {
|
|
18637
18651
|
TeX
|
|
@@ -18645,7 +18659,7 @@ const rules$1 = {
|
|
|
18645
18659
|
}
|
|
18646
18660
|
},
|
|
18647
18661
|
math: {
|
|
18648
|
-
...
|
|
18662
|
+
...SimpleMarkdown$1.pureMarkdownRules.math,
|
|
18649
18663
|
react: (node, output, state) => {
|
|
18650
18664
|
const {
|
|
18651
18665
|
TeX
|
|
@@ -18659,7 +18673,7 @@ const rules$1 = {
|
|
|
18659
18673
|
}
|
|
18660
18674
|
},
|
|
18661
18675
|
unescapedDollar: {
|
|
18662
|
-
...
|
|
18676
|
+
...SimpleMarkdown$1.pureMarkdownRules.unescapedDollar,
|
|
18663
18677
|
react: (node, output, state) => {
|
|
18664
18678
|
// Unescaped dollar signs render correctly, but result in
|
|
18665
18679
|
// untranslatable text after the i18n python linter flags it
|
|
@@ -18669,7 +18683,7 @@ const rules$1 = {
|
|
|
18669
18683
|
// Extend the SimpleMarkdown link parser to make the link open in a new
|
|
18670
18684
|
// window and handle reverse tabnapping phishing attacks
|
|
18671
18685
|
link: {
|
|
18672
|
-
...
|
|
18686
|
+
...SimpleMarkdown$1.pureMarkdownRules.link,
|
|
18673
18687
|
react: function (node, output, state) {
|
|
18674
18688
|
const link = SimpleMarkdown__default["default"].defaultRules.link.react(node, output, state);
|
|
18675
18689
|
const href = link.props.href;
|
|
@@ -18696,7 +18710,7 @@ const rules$1 = {
|
|
|
18696
18710
|
}
|
|
18697
18711
|
},
|
|
18698
18712
|
codeBlock: {
|
|
18699
|
-
...
|
|
18713
|
+
...SimpleMarkdown$1.pureMarkdownRules.codeBlock,
|
|
18700
18714
|
react: (node, output, state) => {
|
|
18701
18715
|
// ideally this should be a different rule, with only an
|
|
18702
18716
|
// output function, but right now that breaks the parser.
|
|
@@ -18714,7 +18728,7 @@ const rules$1 = {
|
|
|
18714
18728
|
// transform the tree to add lint nodes. This rule is here
|
|
18715
18729
|
// just for the react() function
|
|
18716
18730
|
lint: {
|
|
18717
|
-
...
|
|
18731
|
+
...SimpleMarkdown$1.pureMarkdownRules.lint,
|
|
18718
18732
|
react: (node, output, state) => {
|
|
18719
18733
|
return /*#__PURE__*/React__namespace.createElement(Lint, {
|
|
18720
18734
|
message: node.message,
|
|
@@ -18964,8 +18978,8 @@ const getWidget = name => {
|
|
|
18964
18978
|
const getWidgetExport = name => {
|
|
18965
18979
|
return widgets[name] ?? null;
|
|
18966
18980
|
};
|
|
18967
|
-
const
|
|
18968
|
-
return widgets[name]?.
|
|
18981
|
+
const getWidgetScorer = name => {
|
|
18982
|
+
return widgets[name]?.scorer ?? null;
|
|
18969
18983
|
};
|
|
18970
18984
|
const getEditor = name => {
|
|
18971
18985
|
return ___default["default"].has(editors, name) ? editors[name] : null;
|
|
@@ -19273,7 +19287,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19273
19287
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
19274
19288
|
getWidget: getWidget,
|
|
19275
19289
|
getWidgetExport: getWidgetExport,
|
|
19276
|
-
|
|
19290
|
+
getWidgetScorer: getWidgetScorer,
|
|
19277
19291
|
getEditor: getEditor,
|
|
19278
19292
|
getTransform: getTransform,
|
|
19279
19293
|
getVersion: getVersion,
|
|
@@ -19305,12 +19319,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19305
19319
|
}
|
|
19306
19320
|
let score = null;
|
|
19307
19321
|
const userInput = userInputMap[id];
|
|
19308
|
-
const
|
|
19322
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19309
19323
|
if (widget.type === "group") {
|
|
19310
19324
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19311
19325
|
score = Util.flattenScores(scores);
|
|
19312
|
-
} else if (
|
|
19313
|
-
score =
|
|
19326
|
+
} else if (scorer) {
|
|
19327
|
+
score = scorer(userInput, widget.options, strings, locale);
|
|
19314
19328
|
}
|
|
19315
19329
|
if (score) {
|
|
19316
19330
|
return Util.scoreIsEmpty(score);
|
|
@@ -19335,12 +19349,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19335
19349
|
return;
|
|
19336
19350
|
}
|
|
19337
19351
|
const userInput = userInputMap[id];
|
|
19338
|
-
const
|
|
19352
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19339
19353
|
if (widget.type === "group") {
|
|
19340
19354
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19341
19355
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19342
|
-
} else if (
|
|
19343
|
-
widgetScores[id] =
|
|
19356
|
+
} else if (scorer) {
|
|
19357
|
+
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19344
19358
|
}
|
|
19345
19359
|
});
|
|
19346
19360
|
return widgetScores;
|
|
@@ -19824,9 +19838,9 @@ class Renderer extends React__namespace.Component {
|
|
|
19824
19838
|
// WidgetContainers don't update their widgets' props when
|
|
19825
19839
|
// they are re-rendered, so even if they've been
|
|
19826
19840
|
// re-rendered we need to call these methods on them.
|
|
19827
|
-
|
|
19841
|
+
this.widgetIds.forEach(id => {
|
|
19828
19842
|
const container = this._widgetContainers.get(makeContainerId(id));
|
|
19829
|
-
container
|
|
19843
|
+
container?.replaceWidgetProps(this.getWidgetProps(id));
|
|
19830
19844
|
});
|
|
19831
19845
|
if (this.props.serializedState && !___default["default"].isEqual(this.props.serializedState, this.getSerializedState())) {
|
|
19832
19846
|
this.restoreSerializedState(this.props.serializedState);
|
|
@@ -19965,25 +19979,25 @@ class Renderer extends React__namespace.Component {
|
|
|
19965
19979
|
}
|
|
19966
19980
|
return null;
|
|
19967
19981
|
};
|
|
19968
|
-
getWidgetProps
|
|
19982
|
+
getWidgetProps(widgetId) {
|
|
19969
19983
|
var _this = this;
|
|
19970
19984
|
const apiOptions = this.getApiOptions();
|
|
19971
|
-
const widgetProps = this.state.widgetProps[
|
|
19985
|
+
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19972
19986
|
|
|
19973
19987
|
// The widget needs access to its "rubric" at all times when in review
|
|
19974
19988
|
// mode (which is really just part of its widget info).
|
|
19975
|
-
const widgetInfo = this.state.widgetInfo[
|
|
19989
|
+
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19976
19990
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
19977
19991
|
if (!this._interactionTrackers) {
|
|
19978
19992
|
this._interactionTrackers = {};
|
|
19979
19993
|
}
|
|
19980
|
-
let interactionTracker = this._interactionTrackers[
|
|
19994
|
+
let interactionTracker = this._interactionTrackers[widgetId];
|
|
19981
19995
|
if (!interactionTracker) {
|
|
19982
|
-
interactionTracker = this._interactionTrackers[
|
|
19996
|
+
interactionTracker = this._interactionTrackers[widgetId] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, widgetId, getTracking(widgetInfo && widgetInfo.type));
|
|
19983
19997
|
}
|
|
19984
19998
|
return {
|
|
19985
19999
|
...widgetProps,
|
|
19986
|
-
widgetId:
|
|
20000
|
+
widgetId: widgetId,
|
|
19987
20001
|
alignment: widgetInfo && widgetInfo.alignment,
|
|
19988
20002
|
static: widgetInfo?.static,
|
|
19989
20003
|
problemNum: this.props.problemNum,
|
|
@@ -19991,19 +20005,19 @@ class Renderer extends React__namespace.Component {
|
|
|
19991
20005
|
keypadElement: this.props.keypadElement,
|
|
19992
20006
|
questionCompleted: this.props.questionCompleted,
|
|
19993
20007
|
showSolutions: this.props.showSolutions,
|
|
19994
|
-
onFocus: ___default["default"].partial(this._onWidgetFocus,
|
|
19995
|
-
onBlur: ___default["default"].partial(this._onWidgetBlur,
|
|
20008
|
+
onFocus: ___default["default"].partial(this._onWidgetFocus, widgetId),
|
|
20009
|
+
onBlur: ___default["default"].partial(this._onWidgetBlur, widgetId),
|
|
19996
20010
|
findWidgets: this.findWidgets,
|
|
19997
20011
|
reviewModeRubric: reviewModeRubric,
|
|
19998
20012
|
reviewMode: this.props.reviewMode,
|
|
19999
20013
|
onChange: function (newProps, cb) {
|
|
20000
20014
|
let silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
20001
|
-
_this._setWidgetProps(
|
|
20015
|
+
_this._setWidgetProps(widgetId, newProps, cb, silent);
|
|
20002
20016
|
},
|
|
20003
20017
|
trackInteraction: interactionTracker.track,
|
|
20004
|
-
isLastUsedWidget:
|
|
20018
|
+
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20005
20019
|
};
|
|
20006
|
-
}
|
|
20020
|
+
}
|
|
20007
20021
|
|
|
20008
20022
|
/**
|
|
20009
20023
|
* Serializes the questions state so it can be recovered.
|
|
@@ -20892,6 +20906,25 @@ class Renderer extends React__namespace.Component {
|
|
|
20892
20906
|
return this.widgetIds;
|
|
20893
20907
|
};
|
|
20894
20908
|
|
|
20909
|
+
/**
|
|
20910
|
+
* Returns a JSON representation of the content and widgets
|
|
20911
|
+
* that can be passed to an LLM for prompt context.
|
|
20912
|
+
*/
|
|
20913
|
+
getPromptJSON() {
|
|
20914
|
+
const {
|
|
20915
|
+
content
|
|
20916
|
+
} = this.props;
|
|
20917
|
+
const widgetJSON = {};
|
|
20918
|
+
this.widgetIds.forEach(id => {
|
|
20919
|
+
const widget = this.getWidgetInstance(id);
|
|
20920
|
+
widgetJSON[id] = widget?.getPromptJSON?.() || {};
|
|
20921
|
+
});
|
|
20922
|
+
return {
|
|
20923
|
+
content,
|
|
20924
|
+
widgets: widgetJSON
|
|
20925
|
+
};
|
|
20926
|
+
}
|
|
20927
|
+
|
|
20895
20928
|
/**
|
|
20896
20929
|
* Returns an object mapping from widget ID to perseus-style score.
|
|
20897
20930
|
* The keys of this object are the values of the array returned
|
|
@@ -21618,6 +21651,19 @@ SimpleKeypadInput.propTypes = {
|
|
|
21618
21651
|
value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
|
|
21619
21652
|
};
|
|
21620
21653
|
|
|
21654
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
21655
|
+
return {
|
|
21656
|
+
type: "input-number",
|
|
21657
|
+
options: {
|
|
21658
|
+
simplify: renderProps.simplify,
|
|
21659
|
+
answerType: renderProps.answerType
|
|
21660
|
+
},
|
|
21661
|
+
userInput: {
|
|
21662
|
+
value: userInput.currentValue
|
|
21663
|
+
}
|
|
21664
|
+
};
|
|
21665
|
+
};
|
|
21666
|
+
|
|
21621
21667
|
/*
|
|
21622
21668
|
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21623
21669
|
* curly brackets.
|
|
@@ -21808,7 +21854,7 @@ const answerTypes = {
|
|
|
21808
21854
|
forms: "pi"
|
|
21809
21855
|
}
|
|
21810
21856
|
};
|
|
21811
|
-
function
|
|
21857
|
+
function scoreInputNumber(userInput, rubric, strings) {
|
|
21812
21858
|
if (rubric.answerType == null) {
|
|
21813
21859
|
rubric.answerType = "number";
|
|
21814
21860
|
}
|
|
@@ -21939,6 +21985,9 @@ class InputNumber extends React__namespace.Component {
|
|
|
21939
21985
|
getUserInput() {
|
|
21940
21986
|
return InputNumber.getUserInputFromProps(this.props);
|
|
21941
21987
|
}
|
|
21988
|
+
getPromptJSON() {
|
|
21989
|
+
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21990
|
+
}
|
|
21942
21991
|
examples = () => {
|
|
21943
21992
|
const {
|
|
21944
21993
|
strings
|
|
@@ -22038,7 +22087,7 @@ var InputNumber$1 = {
|
|
|
22038
22087
|
widget: InputNumber,
|
|
22039
22088
|
transform: propTransform$3,
|
|
22040
22089
|
isLintable: true,
|
|
22041
|
-
|
|
22090
|
+
scorer: scoreInputNumber,
|
|
22042
22091
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22043
22092
|
if (rubric.value == null) {
|
|
22044
22093
|
return;
|
|
@@ -22051,6 +22100,16 @@ var InputNumber$1 = {
|
|
|
22051
22100
|
}
|
|
22052
22101
|
};
|
|
22053
22102
|
|
|
22103
|
+
const getPromptJSON$r = (renderProps, userInput) => {
|
|
22104
|
+
return {
|
|
22105
|
+
type: "numeric-input",
|
|
22106
|
+
label: renderProps.labelText,
|
|
22107
|
+
userInput: {
|
|
22108
|
+
value: userInput.currentValue
|
|
22109
|
+
}
|
|
22110
|
+
};
|
|
22111
|
+
};
|
|
22112
|
+
|
|
22054
22113
|
const ParseTex = TexWrangler.parseTex;
|
|
22055
22114
|
const answerFormButtons = [{
|
|
22056
22115
|
title: "Integers",
|
|
@@ -22109,7 +22168,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
22109
22168
|
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22110
22169
|
return value;
|
|
22111
22170
|
}
|
|
22112
|
-
function
|
|
22171
|
+
function scoreNumericInput(userInput, rubric, strings) {
|
|
22113
22172
|
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22114
22173
|
// Don't default to validating the answer as a pi answer
|
|
22115
22174
|
// if answerForm isn't set on the answer
|
|
@@ -22271,6 +22330,9 @@ class NumericInput extends React__namespace.Component {
|
|
|
22271
22330
|
getUserInput() {
|
|
22272
22331
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22273
22332
|
}
|
|
22333
|
+
getPromptJSON() {
|
|
22334
|
+
return getPromptJSON$r(this.props, this.getUserInput());
|
|
22335
|
+
}
|
|
22274
22336
|
handleChange = (newValue, cb) => {
|
|
22275
22337
|
this.props.onChange({
|
|
22276
22338
|
currentValue: newValue
|
|
@@ -22396,7 +22458,7 @@ var NumericInput$1 = {
|
|
|
22396
22458
|
widget: NumericInput,
|
|
22397
22459
|
transform: propsTransform,
|
|
22398
22460
|
isLintable: true,
|
|
22399
|
-
|
|
22461
|
+
scorer: scoreNumericInput,
|
|
22400
22462
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22401
22463
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22402
22464
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -22427,15 +22489,43 @@ var NumericInput$1 = {
|
|
|
22427
22489
|
}
|
|
22428
22490
|
};
|
|
22429
22491
|
|
|
22492
|
+
const getPromptJSON$q = (renderProps, userInput) => {
|
|
22493
|
+
const choices = renderProps.choices || [];
|
|
22494
|
+
const options = choices.map(choice => {
|
|
22495
|
+
return {
|
|
22496
|
+
value: choice.content
|
|
22497
|
+
};
|
|
22498
|
+
});
|
|
22499
|
+
return {
|
|
22500
|
+
type: "radio",
|
|
22501
|
+
hasNoneOfTheAbove: !!renderProps.hasNoneOfTheAbove,
|
|
22502
|
+
options,
|
|
22503
|
+
userInput: {
|
|
22504
|
+
selectedOptions: userInput.choicesSelected.slice()
|
|
22505
|
+
}
|
|
22506
|
+
};
|
|
22507
|
+
};
|
|
22508
|
+
|
|
22509
|
+
const getPromptJSON$p = renderProps => {
|
|
22510
|
+
return {
|
|
22511
|
+
type: "passage-ref",
|
|
22512
|
+
options: {
|
|
22513
|
+
passageNumber: renderProps.passageNumber,
|
|
22514
|
+
referenceNumber: renderProps.referenceNumber,
|
|
22515
|
+
summaryText: renderProps.summaryText
|
|
22516
|
+
}
|
|
22517
|
+
};
|
|
22518
|
+
};
|
|
22519
|
+
|
|
22430
22520
|
/**
|
|
22431
|
-
* Several widgets don't have "right"/"wrong"
|
|
22521
|
+
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
22432
22522
|
* so this just says to move on past those widgets
|
|
22433
22523
|
*
|
|
22434
22524
|
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
22435
|
-
*
|
|
22525
|
+
* scoring logic and the thing scoring an exercise
|
|
22436
22526
|
* should just know to skip these
|
|
22437
22527
|
*/
|
|
22438
|
-
function
|
|
22528
|
+
function scoreNoop() {
|
|
22439
22529
|
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
22440
22530
|
return {
|
|
22441
22531
|
type: "points",
|
|
@@ -23829,6 +23919,17 @@ const styles$x = aphrodite.StyleSheet.create({
|
|
|
23829
23919
|
}
|
|
23830
23920
|
});
|
|
23831
23921
|
|
|
23922
|
+
const getPromptJSON$o = renderProps => {
|
|
23923
|
+
return {
|
|
23924
|
+
type: "passage",
|
|
23925
|
+
options: {
|
|
23926
|
+
passageTitle: renderProps.passageTitle,
|
|
23927
|
+
passageText: renderProps.passageText,
|
|
23928
|
+
footnotes: renderProps.footnotes
|
|
23929
|
+
}
|
|
23930
|
+
};
|
|
23931
|
+
};
|
|
23932
|
+
|
|
23832
23933
|
function getInitialParseState() {
|
|
23833
23934
|
return {
|
|
23834
23935
|
currentRef: [],
|
|
@@ -24461,6 +24562,9 @@ class Passage extends React__namespace.Component {
|
|
|
24461
24562
|
content: refContent
|
|
24462
24563
|
};
|
|
24463
24564
|
}
|
|
24565
|
+
getPromptJSON() {
|
|
24566
|
+
return getPromptJSON$o(this.props);
|
|
24567
|
+
}
|
|
24464
24568
|
|
|
24465
24569
|
/**
|
|
24466
24570
|
* Rendering
|
|
@@ -24606,7 +24710,7 @@ var Passage$1 = {
|
|
|
24606
24710
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
24607
24711
|
},
|
|
24608
24712
|
isLintable: true,
|
|
24609
|
-
|
|
24713
|
+
scorer: () => scoreNoop()
|
|
24610
24714
|
};
|
|
24611
24715
|
|
|
24612
24716
|
/**
|
|
@@ -24671,6 +24775,9 @@ class PassageRef extends React__namespace.Component {
|
|
|
24671
24775
|
return change.apply(_this, args);
|
|
24672
24776
|
};
|
|
24673
24777
|
})();
|
|
24778
|
+
getPromptJSON() {
|
|
24779
|
+
return getPromptJSON$p(this.props);
|
|
24780
|
+
}
|
|
24674
24781
|
_deferredUpdateRange = () => {
|
|
24675
24782
|
___default["default"].defer(this._updateRange);
|
|
24676
24783
|
};
|
|
@@ -24740,8 +24847,8 @@ var PassageRef$1 = {
|
|
|
24740
24847
|
major: 0,
|
|
24741
24848
|
minor: 1
|
|
24742
24849
|
},
|
|
24743
|
-
// TODO: things that aren't interactive shouldn't need
|
|
24744
|
-
|
|
24850
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
24851
|
+
scorer: () => scoreNoop()
|
|
24745
24852
|
};
|
|
24746
24853
|
|
|
24747
24854
|
/**
|
|
@@ -26109,7 +26216,7 @@ const styles$p = aphrodite.StyleSheet.create({
|
|
|
26109
26216
|
}
|
|
26110
26217
|
});
|
|
26111
26218
|
|
|
26112
|
-
function
|
|
26219
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
26113
26220
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26114
26221
|
return sum + (selected ? 1 : 0);
|
|
26115
26222
|
}, 0);
|
|
@@ -26119,10 +26226,10 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26119
26226
|
message: null
|
|
26120
26227
|
};
|
|
26121
26228
|
}
|
|
26122
|
-
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
if (
|
|
26229
|
+
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
26230
|
+
return currentChoice.correct ? sum + 1 : sum;
|
|
26231
|
+
}, 0);
|
|
26232
|
+
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
26126
26233
|
return {
|
|
26127
26234
|
type: "invalid",
|
|
26128
26235
|
message: strings.chooseCorrectNum
|
|
@@ -26130,9 +26237,8 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26130
26237
|
// If NOTA and some other answer are checked, ...
|
|
26131
26238
|
}
|
|
26132
26239
|
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
26240
|
+
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
26241
|
+
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
26136
26242
|
return {
|
|
26137
26243
|
type: "invalid",
|
|
26138
26244
|
message: strings.notNoneOfTheAbove
|
|
@@ -26140,9 +26246,7 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26140
26246
|
}
|
|
26141
26247
|
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
26142
26248
|
let isCorrect;
|
|
26143
|
-
|
|
26144
|
-
// instead of the userInput?
|
|
26145
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
26249
|
+
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
26146
26250
|
isCorrect = rubric.choices.every((choice, j) => {
|
|
26147
26251
|
return i === j || !choice.correct;
|
|
26148
26252
|
});
|
|
@@ -26174,31 +26278,19 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26174
26278
|
showSolutions: "none"
|
|
26175
26279
|
};
|
|
26176
26280
|
static getUserInputFromProps(props) {
|
|
26281
|
+
let unshuffle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
26177
26282
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
26178
26283
|
// future timeline implementers: this used to be {value: i} before
|
|
26179
26284
|
// multiple select was added)
|
|
26180
26285
|
if (props.choiceStates) {
|
|
26181
|
-
let noneOfTheAboveIndex = null;
|
|
26182
|
-
let noneOfTheAboveSelected = false;
|
|
26183
26286
|
const choiceStates = props.choiceStates;
|
|
26184
26287
|
const choicesSelected = choiceStates.map(() => false);
|
|
26185
|
-
const numCorrect = props.numCorrect;
|
|
26186
26288
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26187
|
-
const index = props.choices[i].originalIndex;
|
|
26289
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26188
26290
|
choicesSelected[index] = choiceStates[i].selected;
|
|
26189
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26190
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26191
|
-
noneOfTheAboveIndex = index;
|
|
26192
|
-
if (choicesSelected[i]) {
|
|
26193
|
-
noneOfTheAboveSelected = true;
|
|
26194
|
-
}
|
|
26195
|
-
}
|
|
26196
26291
|
}
|
|
26197
26292
|
return {
|
|
26198
|
-
choicesSelected
|
|
26199
|
-
numCorrect,
|
|
26200
|
-
noneOfTheAboveIndex,
|
|
26201
|
-
noneOfTheAboveSelected
|
|
26293
|
+
choicesSelected
|
|
26202
26294
|
};
|
|
26203
26295
|
// Support legacy choiceState implementation
|
|
26204
26296
|
}
|
|
@@ -26207,27 +26299,14 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26207
26299
|
values
|
|
26208
26300
|
} = props;
|
|
26209
26301
|
if (values) {
|
|
26210
|
-
let noneOfTheAboveIndex = null;
|
|
26211
|
-
let noneOfTheAboveSelected = false;
|
|
26212
26302
|
const choicesSelected = [...values];
|
|
26213
|
-
const numCorrect = props.numCorrect;
|
|
26214
26303
|
const valuesLength = values.length;
|
|
26215
26304
|
for (let i = 0; i < valuesLength; i++) {
|
|
26216
|
-
const index = props.choices[i].originalIndex;
|
|
26305
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26217
26306
|
choicesSelected[index] = values[i];
|
|
26218
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26219
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26220
|
-
noneOfTheAboveIndex = index;
|
|
26221
|
-
if (choicesSelected[i]) {
|
|
26222
|
-
noneOfTheAboveSelected = true;
|
|
26223
|
-
}
|
|
26224
|
-
}
|
|
26225
26307
|
}
|
|
26226
26308
|
return {
|
|
26227
|
-
choicesSelected
|
|
26228
|
-
noneOfTheAboveIndex,
|
|
26229
|
-
noneOfTheAboveSelected,
|
|
26230
|
-
numCorrect
|
|
26309
|
+
choicesSelected
|
|
26231
26310
|
};
|
|
26232
26311
|
}
|
|
26233
26312
|
// Nothing checked
|
|
@@ -26355,6 +26434,10 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26355
26434
|
getUserInput() {
|
|
26356
26435
|
return Radio$1.getUserInputFromProps(this.props);
|
|
26357
26436
|
}
|
|
26437
|
+
getPromptJSON() {
|
|
26438
|
+
const userInput = Radio$1.getUserInputFromProps(this.props, false);
|
|
26439
|
+
return getPromptJSON$q(this.props, userInput);
|
|
26440
|
+
}
|
|
26358
26441
|
|
|
26359
26442
|
/**
|
|
26360
26443
|
* Turn on rationale display for the currently selected choices. Note that
|
|
@@ -26367,7 +26450,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26367
26450
|
choiceStates
|
|
26368
26451
|
} = this.props;
|
|
26369
26452
|
if (choiceStates) {
|
|
26370
|
-
const score =
|
|
26453
|
+
const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
|
|
26371
26454
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26372
26455
|
const newStates = choiceStates.map(state => ({
|
|
26373
26456
|
...state,
|
|
@@ -26649,13 +26732,26 @@ var Radio = {
|
|
|
26649
26732
|
},
|
|
26650
26733
|
propUpgrades: propUpgrades$1,
|
|
26651
26734
|
isLintable: true,
|
|
26652
|
-
|
|
26735
|
+
scorer: scoreRadio
|
|
26653
26736
|
};
|
|
26654
26737
|
|
|
26655
26738
|
/* eslint-disable import/no-named-as-default */
|
|
26656
26739
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
26657
26740
|
|
|
26658
|
-
|
|
26741
|
+
const getPromptJSON$n = (renderProps, userInput) => {
|
|
26742
|
+
return {
|
|
26743
|
+
type: "categorizer",
|
|
26744
|
+
options: {
|
|
26745
|
+
items: renderProps.items,
|
|
26746
|
+
categories: renderProps.categories
|
|
26747
|
+
},
|
|
26748
|
+
userInput: {
|
|
26749
|
+
itemToCategoryMapping: userInput.values
|
|
26750
|
+
}
|
|
26751
|
+
};
|
|
26752
|
+
};
|
|
26753
|
+
|
|
26754
|
+
function scoreCategorizer(userInput, rubric, strings) {
|
|
26659
26755
|
let completed = true;
|
|
26660
26756
|
let allCorrect = true;
|
|
26661
26757
|
rubric.values.forEach((value, i) => {
|
|
@@ -26710,6 +26806,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
26710
26806
|
getUserInput() {
|
|
26711
26807
|
return Categorizer.getUserInputFromProps(this.props);
|
|
26712
26808
|
}
|
|
26809
|
+
getPromptJSON() {
|
|
26810
|
+
return getPromptJSON$n(this.props, this.getUserInput());
|
|
26811
|
+
}
|
|
26713
26812
|
onChange(itemNum, catNum) {
|
|
26714
26813
|
const values = [...this.props.values];
|
|
26715
26814
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -26856,7 +26955,7 @@ var Categorizer$1 = {
|
|
|
26856
26955
|
return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
26857
26956
|
},
|
|
26858
26957
|
isLintable: true,
|
|
26859
|
-
|
|
26958
|
+
scorer: scoreCategorizer
|
|
26860
26959
|
};
|
|
26861
26960
|
|
|
26862
26961
|
/**
|
|
@@ -26906,7 +27005,20 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
26906
27005
|
*/
|
|
26907
27006
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
26908
27007
|
|
|
26909
|
-
function
|
|
27008
|
+
const getUnsupportedPromptJSON = function (widgetType) {
|
|
27009
|
+
let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
|
|
27010
|
+
return {
|
|
27011
|
+
type: widgetType,
|
|
27012
|
+
isSupported: false,
|
|
27013
|
+
message
|
|
27014
|
+
};
|
|
27015
|
+
};
|
|
27016
|
+
|
|
27017
|
+
const getPromptJSON$m = () => {
|
|
27018
|
+
return getUnsupportedPromptJSON("cs-program");
|
|
27019
|
+
};
|
|
27020
|
+
|
|
27021
|
+
function scoreCSProgram(state) {
|
|
26910
27022
|
// The iframe can tell us whether it's correct or incorrect,
|
|
26911
27023
|
// and pass an optional message
|
|
26912
27024
|
if (state.status === "correct") {
|
|
@@ -27002,6 +27114,9 @@ class CSProgram extends React__namespace.Component {
|
|
|
27002
27114
|
message: this.props.message
|
|
27003
27115
|
};
|
|
27004
27116
|
}
|
|
27117
|
+
getPromptJSON() {
|
|
27118
|
+
return getPromptJSON$m();
|
|
27119
|
+
}
|
|
27005
27120
|
render() {
|
|
27006
27121
|
if (!this.props.programID) {
|
|
27007
27122
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -27077,7 +27192,15 @@ var CSProgram$1 = {
|
|
|
27077
27192
|
supportedAlignments: ["block", "full-width"],
|
|
27078
27193
|
widget: CSProgram,
|
|
27079
27194
|
hidden: true,
|
|
27080
|
-
|
|
27195
|
+
scorer: scoreCSProgram
|
|
27196
|
+
};
|
|
27197
|
+
|
|
27198
|
+
const getPromptJSON$l = renderProps => {
|
|
27199
|
+
return {
|
|
27200
|
+
type: "definition",
|
|
27201
|
+
definition: renderProps.definition,
|
|
27202
|
+
togglePrompt: renderProps.togglePrompt
|
|
27203
|
+
};
|
|
27081
27204
|
};
|
|
27082
27205
|
|
|
27083
27206
|
class Definition extends React__namespace.Component {
|
|
@@ -27090,6 +27213,9 @@ class Definition extends React__namespace.Component {
|
|
|
27090
27213
|
// this just helps with TS weak typing when a Widget
|
|
27091
27214
|
// doesn't implement any Widget methods
|
|
27092
27215
|
isWidget = true;
|
|
27216
|
+
getPromptJSON() {
|
|
27217
|
+
return getPromptJSON$l(this.props);
|
|
27218
|
+
}
|
|
27093
27219
|
render() {
|
|
27094
27220
|
return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
|
|
27095
27221
|
let {
|
|
@@ -27146,8 +27272,8 @@ var Definition$1 = {
|
|
|
27146
27272
|
defaultAlignment: "inline",
|
|
27147
27273
|
widget: Definition,
|
|
27148
27274
|
transform: x => x,
|
|
27149
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27150
|
-
|
|
27275
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27276
|
+
scorer: () => scoreNoop()
|
|
27151
27277
|
};
|
|
27152
27278
|
|
|
27153
27279
|
// The props are type `any` on purpose so that this can receive props
|
|
@@ -27175,11 +27301,23 @@ var DeprecatedStandin$1 = {
|
|
|
27175
27301
|
displayName: "Deprecated Standin",
|
|
27176
27302
|
widget: DeprecatedStandin,
|
|
27177
27303
|
hidden: true,
|
|
27178
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27179
|
-
|
|
27304
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27305
|
+
scorer: () => scoreNoop(1)
|
|
27180
27306
|
};
|
|
27181
27307
|
|
|
27182
|
-
|
|
27308
|
+
const getPromptJSON$k = (renderProps, userInput) => {
|
|
27309
|
+
return {
|
|
27310
|
+
type: "dropdown",
|
|
27311
|
+
options: {
|
|
27312
|
+
items: renderProps.choices
|
|
27313
|
+
},
|
|
27314
|
+
userInput: {
|
|
27315
|
+
selectedIndex: userInput.value - 1 // Offset to account for placeholder
|
|
27316
|
+
}
|
|
27317
|
+
};
|
|
27318
|
+
};
|
|
27319
|
+
|
|
27320
|
+
function scoreDropdown(userInput, rubric) {
|
|
27183
27321
|
const selected = userInput.value;
|
|
27184
27322
|
if (selected === 0) {
|
|
27185
27323
|
return {
|
|
@@ -27225,6 +27363,9 @@ class Dropdown extends React__namespace.Component {
|
|
|
27225
27363
|
value: this.props.selected
|
|
27226
27364
|
};
|
|
27227
27365
|
}
|
|
27366
|
+
getPromptJSON() {
|
|
27367
|
+
return getPromptJSON$k(this.props, this.getUserInput());
|
|
27368
|
+
}
|
|
27228
27369
|
render() {
|
|
27229
27370
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
27230
27371
|
key: "placeholder",
|
|
@@ -27266,7 +27407,15 @@ var Dropdown$1 = {
|
|
|
27266
27407
|
accessible: true,
|
|
27267
27408
|
widget: Dropdown,
|
|
27268
27409
|
transform: optionsTransform,
|
|
27269
|
-
|
|
27410
|
+
scorer: scoreDropdown
|
|
27411
|
+
};
|
|
27412
|
+
|
|
27413
|
+
const getPromptJSON$j = renderProps => {
|
|
27414
|
+
return {
|
|
27415
|
+
type: "explanation",
|
|
27416
|
+
showPrompt: renderProps.showPrompt,
|
|
27417
|
+
explanation: renderProps.explanation
|
|
27418
|
+
};
|
|
27270
27419
|
};
|
|
27271
27420
|
|
|
27272
27421
|
// transform = _.identity
|
|
@@ -27315,6 +27464,9 @@ class Explanation extends React__namespace.Component {
|
|
|
27315
27464
|
});
|
|
27316
27465
|
this.props.trackInteraction();
|
|
27317
27466
|
};
|
|
27467
|
+
getPromptJSON() {
|
|
27468
|
+
return getPromptJSON$j(this.props);
|
|
27469
|
+
}
|
|
27318
27470
|
render() {
|
|
27319
27471
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
27320
27472
|
const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
|
|
@@ -27421,8 +27573,26 @@ var Explanation$1 = {
|
|
|
27421
27573
|
widget: Explanation,
|
|
27422
27574
|
transform: ___default["default"].identity,
|
|
27423
27575
|
isLintable: true,
|
|
27424
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27425
|
-
|
|
27576
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27577
|
+
scorer: () => scoreNoop()
|
|
27578
|
+
};
|
|
27579
|
+
|
|
27580
|
+
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
27581
|
+
if (!rendererJSON) {
|
|
27582
|
+
return {
|
|
27583
|
+
type: "graded-group",
|
|
27584
|
+
title,
|
|
27585
|
+
content: "",
|
|
27586
|
+
widgets: {},
|
|
27587
|
+
hint: hintRendererJSON
|
|
27588
|
+
};
|
|
27589
|
+
}
|
|
27590
|
+
return {
|
|
27591
|
+
...rendererJSON,
|
|
27592
|
+
title,
|
|
27593
|
+
type: "graded-group",
|
|
27594
|
+
hint: hintRendererJSON
|
|
27595
|
+
};
|
|
27426
27596
|
};
|
|
27427
27597
|
|
|
27428
27598
|
class GradedGroupAnswerBar extends React__namespace.Component {
|
|
@@ -27566,6 +27736,8 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27566
27736
|
message: "",
|
|
27567
27737
|
answerBarState: "HIDDEN"
|
|
27568
27738
|
};
|
|
27739
|
+
rendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27740
|
+
hintRendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27569
27741
|
shouldComponentUpdate(nextProps, nextState) {
|
|
27570
27742
|
return nextProps !== this.props || nextState !== this.state;
|
|
27571
27743
|
}
|
|
@@ -27591,13 +27763,9 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27591
27763
|
status: GRADING_STATUSES.ungraded,
|
|
27592
27764
|
message: ""
|
|
27593
27765
|
});
|
|
27594
|
-
|
|
27595
|
-
// eslint-disable-next-line react/no-string-refs
|
|
27596
|
-
if (this.refs.renderer) {
|
|
27766
|
+
if (this.rendererRef.current) {
|
|
27597
27767
|
this.change("widgets", this.props.widgets);
|
|
27598
|
-
|
|
27599
|
-
// @ts-expect-error - TS2339 - Property 'emptyWidgets' does not exist on type 'ReactInstance'.
|
|
27600
|
-
const emptyWidgets = this.refs.renderer.emptyWidgets();
|
|
27768
|
+
const emptyWidgets = this.rendererRef.current.emptyWidgets();
|
|
27601
27769
|
const answerable = emptyWidgets.length === 0;
|
|
27602
27770
|
const answerBarState = this.state.answerBarState;
|
|
27603
27771
|
this.setState({
|
|
@@ -27606,12 +27774,10 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27606
27774
|
}
|
|
27607
27775
|
};
|
|
27608
27776
|
_checkAnswer = () => {
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
|
|
27612
|
-
|
|
27613
|
-
// @ts-expect-error - TS2339 - Property 'score' does not exist on type 'ReactInstance'.
|
|
27614
|
-
const score = this.refs.renderer.score();
|
|
27777
|
+
this.rendererRef.current?.showRationalesForCurrentlySelectedChoices();
|
|
27778
|
+
const score = this.rendererRef.current?.score() || {
|
|
27779
|
+
type: "invalid"
|
|
27780
|
+
};
|
|
27615
27781
|
const {
|
|
27616
27782
|
INVALID_MESSAGE_PREFIX,
|
|
27617
27783
|
DEFAULT_INVALID_MESSAGE_1,
|
|
@@ -27632,29 +27798,28 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27632
27798
|
|
|
27633
27799
|
// Mobile API
|
|
27634
27800
|
getInputPaths = () => {
|
|
27635
|
-
|
|
27636
|
-
// @ts-expect-error - TS2339 - Property 'getInputPaths' does not exist on type 'ReactInstance'.
|
|
27637
|
-
return this.refs.renderer.getInputPaths();
|
|
27801
|
+
return this.rendererRef.current?.getInputPaths() || [];
|
|
27638
27802
|
};
|
|
27803
|
+
getPromptJSON() {
|
|
27804
|
+
// If the hint isn't expanded, we can't get the prompt JSON from the rendered widgets.
|
|
27805
|
+
// We'll just pass in the hint content as a string instead.
|
|
27806
|
+
const hint = this.hintRendererRef.current?.getPromptJSON() || {
|
|
27807
|
+
content: this.props.hint?.content || "",
|
|
27808
|
+
widgets: {}
|
|
27809
|
+
};
|
|
27810
|
+
return getPromptJSON$i(this.props.title, this.rendererRef.current?.getPromptJSON(), hint);
|
|
27811
|
+
}
|
|
27639
27812
|
setInputValue = (path, newValue, cb) => {
|
|
27640
|
-
|
|
27641
|
-
// @ts-expect-error - TS2339 - Property 'setInputValue' does not exist on type 'ReactInstance'.
|
|
27642
|
-
return this.refs.renderer.setInputValue(path, newValue, cb);
|
|
27813
|
+
return this.rendererRef.current?.setInputValue(path, newValue, cb);
|
|
27643
27814
|
};
|
|
27644
27815
|
focus = () => {
|
|
27645
|
-
|
|
27646
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
27647
|
-
return this.refs.renderer.focus();
|
|
27816
|
+
return !!this.rendererRef.current?.focus();
|
|
27648
27817
|
};
|
|
27649
27818
|
focusInputPath = path => {
|
|
27650
|
-
|
|
27651
|
-
// @ts-expect-error - TS2339 - Property 'focusPath' does not exist on type 'ReactInstance'.
|
|
27652
|
-
this.refs.renderer.focusPath(path);
|
|
27819
|
+
this.rendererRef.current?.focusPath(path);
|
|
27653
27820
|
};
|
|
27654
27821
|
blurInputPath = path => {
|
|
27655
|
-
|
|
27656
|
-
// @ts-expect-error - TS2339 - Property 'blurPath' does not exist on type 'ReactInstance'.
|
|
27657
|
-
this.refs.renderer.blurPath(path);
|
|
27822
|
+
this.rendererRef.current?.blurPath(path);
|
|
27658
27823
|
};
|
|
27659
27824
|
render() {
|
|
27660
27825
|
const apiOptions = ___default["default"].extend({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
@@ -27712,8 +27877,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27712
27877
|
}, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
|
|
27713
27878
|
className: aphrodite.css(styles$j.title)
|
|
27714
27879
|
}, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
|
|
27715
|
-
|
|
27716
|
-
ref: "renderer",
|
|
27880
|
+
ref: this.rendererRef,
|
|
27717
27881
|
apiOptions: {
|
|
27718
27882
|
...apiOptions,
|
|
27719
27883
|
readOnly
|
|
@@ -27756,8 +27920,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27756
27920
|
});
|
|
27757
27921
|
}
|
|
27758
27922
|
}, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
|
|
27759
|
-
|
|
27760
|
-
ref: "hints-renderer",
|
|
27923
|
+
ref: this.hintRendererRef,
|
|
27761
27924
|
apiOptions: apiOptions,
|
|
27762
27925
|
linterContext: this.props.linterContext,
|
|
27763
27926
|
strings: this.context.strings
|
|
@@ -27849,6 +28012,16 @@ var GradedGroup$1 = {
|
|
|
27849
28012
|
isLintable: true
|
|
27850
28013
|
};
|
|
27851
28014
|
|
|
28015
|
+
const getPromptJSON$h = (renderProps, activeGroupJSON) => {
|
|
28016
|
+
return {
|
|
28017
|
+
type: "graded-group-set",
|
|
28018
|
+
options: {
|
|
28019
|
+
groupCount: renderProps.gradedGroups.length,
|
|
28020
|
+
currentGroup: activeGroupJSON
|
|
28021
|
+
}
|
|
28022
|
+
};
|
|
28023
|
+
};
|
|
28024
|
+
|
|
27852
28025
|
class Indicators extends React__namespace.Component {
|
|
27853
28026
|
static contextType = PerseusI18nContext;
|
|
27854
28027
|
handleKeyDown = (e, i) => {
|
|
@@ -27927,6 +28100,10 @@ class GradedGroupSet extends React__namespace.Component {
|
|
|
27927
28100
|
getInputPaths = () => {
|
|
27928
28101
|
return this._childGroup.getInputPaths();
|
|
27929
28102
|
};
|
|
28103
|
+
getPromptJSON() {
|
|
28104
|
+
const activeGroupPromptJSON = this._childGroup.getPromptJSON();
|
|
28105
|
+
return getPromptJSON$h(this.props, activeGroupPromptJSON);
|
|
28106
|
+
}
|
|
27930
28107
|
setInputValue = (path, newValue, cb) => {
|
|
27931
28108
|
return this._childGroup.setInputValue(path, newValue, cb);
|
|
27932
28109
|
};
|
|
@@ -28180,6 +28357,33 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
28180
28357
|
}
|
|
28181
28358
|
});
|
|
28182
28359
|
|
|
28360
|
+
const getPromptJSON$g = (renderProps, userInput) => {
|
|
28361
|
+
const {
|
|
28362
|
+
type,
|
|
28363
|
+
coords
|
|
28364
|
+
} = userInput;
|
|
28365
|
+
const input = {
|
|
28366
|
+
type,
|
|
28367
|
+
coords
|
|
28368
|
+
};
|
|
28369
|
+
if (userInput.type === "logarithm" || userInput.type === "exponential") {
|
|
28370
|
+
input["asymptote"] = userInput.asymptote;
|
|
28371
|
+
}
|
|
28372
|
+
return {
|
|
28373
|
+
type: "grapher",
|
|
28374
|
+
options: {
|
|
28375
|
+
availableTypes: renderProps.availableTypes,
|
|
28376
|
+
range: renderProps.graph.range,
|
|
28377
|
+
labels: renderProps.graph.labels,
|
|
28378
|
+
tickStep: renderProps.graph.step,
|
|
28379
|
+
gridStep: renderProps.graph.gridStep,
|
|
28380
|
+
snapStep: renderProps.graph.snapStep,
|
|
28381
|
+
backgroundImageUrl: renderProps.graph.backgroundImage.url
|
|
28382
|
+
},
|
|
28383
|
+
userInput: input
|
|
28384
|
+
};
|
|
28385
|
+
};
|
|
28386
|
+
|
|
28183
28387
|
/* eslint-disable one-var */
|
|
28184
28388
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
28185
28389
|
const Plot$1 = Graphie.Plot;
|
|
@@ -28703,7 +28907,7 @@ function getCoefficientsByType(data) {
|
|
|
28703
28907
|
throw new perseusCore.PerseusError("Invalid grapher type", perseusCore.Errors.InvalidInput);
|
|
28704
28908
|
}
|
|
28705
28909
|
}
|
|
28706
|
-
function
|
|
28910
|
+
function scoreGrapher(userInput, rubric) {
|
|
28707
28911
|
if (userInput.type !== rubric.correct.type) {
|
|
28708
28912
|
return {
|
|
28709
28913
|
type: "points",
|
|
@@ -29054,6 +29258,9 @@ class Grapher extends React__namespace.Component {
|
|
|
29054
29258
|
getUserInput() {
|
|
29055
29259
|
return Grapher.getUserInputFromProps(this.props);
|
|
29056
29260
|
}
|
|
29261
|
+
getPromptJSON() {
|
|
29262
|
+
return getPromptJSON$g(this.props, this.getUserInput());
|
|
29263
|
+
}
|
|
29057
29264
|
render() {
|
|
29058
29265
|
const type = this.props.plot.type;
|
|
29059
29266
|
const coords = this.props.plot.coords;
|
|
@@ -29143,7 +29350,21 @@ var Grapher$1 = {
|
|
|
29143
29350
|
widget: Grapher,
|
|
29144
29351
|
transform: propTransform$2,
|
|
29145
29352
|
staticTransform: staticTransform$4,
|
|
29146
|
-
|
|
29353
|
+
scorer: scoreGrapher
|
|
29354
|
+
};
|
|
29355
|
+
|
|
29356
|
+
const getPromptJSON$f = rendererJSON => {
|
|
29357
|
+
if (!rendererJSON) {
|
|
29358
|
+
return {
|
|
29359
|
+
type: "group",
|
|
29360
|
+
content: "",
|
|
29361
|
+
widgets: {}
|
|
29362
|
+
};
|
|
29363
|
+
}
|
|
29364
|
+
return {
|
|
29365
|
+
...rendererJSON,
|
|
29366
|
+
type: "group"
|
|
29367
|
+
};
|
|
29147
29368
|
};
|
|
29148
29369
|
|
|
29149
29370
|
// exports has no 'transform'
|
|
@@ -29182,6 +29403,9 @@ class Group extends React__namespace.Component {
|
|
|
29182
29403
|
getUserInput() {
|
|
29183
29404
|
return this.rendererRef?.getUserInput();
|
|
29184
29405
|
}
|
|
29406
|
+
getPromptJSON() {
|
|
29407
|
+
return getPromptJSON$f(this.rendererRef?.getPromptJSON());
|
|
29408
|
+
}
|
|
29185
29409
|
getSerializedState = () => {
|
|
29186
29410
|
return this.rendererRef?.getSerializedState();
|
|
29187
29411
|
};
|
|
@@ -29295,7 +29519,11 @@ var Group$1 = {
|
|
|
29295
29519
|
isLintable: true
|
|
29296
29520
|
};
|
|
29297
29521
|
|
|
29298
|
-
|
|
29522
|
+
const getPromptJSON$e = () => {
|
|
29523
|
+
return getUnsupportedPromptJSON("iframe");
|
|
29524
|
+
};
|
|
29525
|
+
|
|
29526
|
+
function scoreIframe(userInput) {
|
|
29299
29527
|
// The iframe can tell us whether it's correct or incorrect,
|
|
29300
29528
|
// and pass an optional message
|
|
29301
29529
|
if (userInput.status === "correct") {
|
|
@@ -29352,6 +29580,9 @@ class Iframe extends React__namespace.Component {
|
|
|
29352
29580
|
message: this.props.message
|
|
29353
29581
|
};
|
|
29354
29582
|
}
|
|
29583
|
+
getPromptJSON() {
|
|
29584
|
+
return getPromptJSON$e();
|
|
29585
|
+
}
|
|
29355
29586
|
handleMessageEvent = e => {
|
|
29356
29587
|
// We receive data from the iframe that contains {passed: true/false}
|
|
29357
29588
|
// and use that to set the status
|
|
@@ -29449,7 +29680,19 @@ var Iframe$1 = {
|
|
|
29449
29680
|
widget: Iframe,
|
|
29450
29681
|
// Let's not expose it to all content creators yet
|
|
29451
29682
|
hidden: true,
|
|
29452
|
-
|
|
29683
|
+
scorer: scoreIframe
|
|
29684
|
+
};
|
|
29685
|
+
|
|
29686
|
+
const getPromptJSON$d = renderProps => {
|
|
29687
|
+
return {
|
|
29688
|
+
type: "image",
|
|
29689
|
+
options: {
|
|
29690
|
+
altText: renderProps.alt,
|
|
29691
|
+
title: renderProps.title,
|
|
29692
|
+
caption: renderProps.caption,
|
|
29693
|
+
imageUrl: renderProps.backgroundImage.url
|
|
29694
|
+
}
|
|
29695
|
+
};
|
|
29453
29696
|
};
|
|
29454
29697
|
|
|
29455
29698
|
const defaultBoxSize = 400;
|
|
@@ -29490,6 +29733,9 @@ class ImageWidget extends React__namespace.Component {
|
|
|
29490
29733
|
return change.apply(_this, args);
|
|
29491
29734
|
};
|
|
29492
29735
|
})();
|
|
29736
|
+
getPromptJSON() {
|
|
29737
|
+
return getPromptJSON$d(this.props);
|
|
29738
|
+
}
|
|
29493
29739
|
render() {
|
|
29494
29740
|
let image;
|
|
29495
29741
|
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
@@ -29636,8 +29882,12 @@ var Image$1 = {
|
|
|
29636
29882
|
displayName: "Image",
|
|
29637
29883
|
widget: ImageWidget,
|
|
29638
29884
|
isLintable: true,
|
|
29639
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29640
|
-
|
|
29885
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
29886
|
+
scorer: () => scoreNoop()
|
|
29887
|
+
};
|
|
29888
|
+
|
|
29889
|
+
const getPromptJSON$c = () => {
|
|
29890
|
+
return getUnsupportedPromptJSON("interaction");
|
|
29641
29891
|
};
|
|
29642
29892
|
|
|
29643
29893
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -29826,6 +30076,9 @@ class Interaction extends React__namespace.Component {
|
|
|
29826
30076
|
return change.apply(_this, args);
|
|
29827
30077
|
};
|
|
29828
30078
|
})();
|
|
30079
|
+
getPromptJSON() {
|
|
30080
|
+
return getPromptJSON$c();
|
|
30081
|
+
}
|
|
29829
30082
|
render() {
|
|
29830
30083
|
const range = this.props.graph.range;
|
|
29831
30084
|
let labels = this.props.graph.labels;
|
|
@@ -30234,8 +30487,8 @@ var Interactive = {
|
|
|
30234
30487
|
widget: Interaction,
|
|
30235
30488
|
transform: ___default["default"].identity,
|
|
30236
30489
|
hidden: true,
|
|
30237
|
-
// TODO: things that aren't interactive shouldn't need
|
|
30238
|
-
|
|
30490
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
30491
|
+
scorer: () => scoreNoop()
|
|
30239
30492
|
};
|
|
30240
30493
|
|
|
30241
30494
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -30539,6 +30792,137 @@ class Graph extends React__namespace.Component {
|
|
|
30539
30792
|
}
|
|
30540
30793
|
}
|
|
30541
30794
|
|
|
30795
|
+
const getPromptJSON$b = (props, userInput) => {
|
|
30796
|
+
return {
|
|
30797
|
+
type: "interactive-graph",
|
|
30798
|
+
options: {
|
|
30799
|
+
graph: getGraphOptionsForProps(props),
|
|
30800
|
+
backgroundImageUrl: props.backgroundImage?.url,
|
|
30801
|
+
range: props.range,
|
|
30802
|
+
labels: props.labels
|
|
30803
|
+
},
|
|
30804
|
+
userInput: getUserInput(userInput)
|
|
30805
|
+
};
|
|
30806
|
+
};
|
|
30807
|
+
const getGraphOptionsForProps = props => {
|
|
30808
|
+
const type = props.graph.type;
|
|
30809
|
+
switch (type) {
|
|
30810
|
+
case "angle":
|
|
30811
|
+
return {
|
|
30812
|
+
type: props.graph.type,
|
|
30813
|
+
angleOffsetDegrees: props.graph.angleOffsetDeg,
|
|
30814
|
+
startCoords: props.graph.startCoords
|
|
30815
|
+
};
|
|
30816
|
+
case "circle":
|
|
30817
|
+
return {
|
|
30818
|
+
type: props.graph.type,
|
|
30819
|
+
startParams: {
|
|
30820
|
+
center: props.graph.startCoords?.center,
|
|
30821
|
+
radius: props.graph.startCoords?.radius
|
|
30822
|
+
}
|
|
30823
|
+
};
|
|
30824
|
+
case "linear":
|
|
30825
|
+
return {
|
|
30826
|
+
type: props.graph.type,
|
|
30827
|
+
startCoords: props.graph.startCoords
|
|
30828
|
+
};
|
|
30829
|
+
case "linear-system":
|
|
30830
|
+
return {
|
|
30831
|
+
type: props.graph.type,
|
|
30832
|
+
startCoords: props.graph.startCoords
|
|
30833
|
+
};
|
|
30834
|
+
case "point":
|
|
30835
|
+
return {
|
|
30836
|
+
type: props.graph.type,
|
|
30837
|
+
numPoints: props.graph.numPoints,
|
|
30838
|
+
startCoords: props.graph.startCoords
|
|
30839
|
+
};
|
|
30840
|
+
case "polygon":
|
|
30841
|
+
return {
|
|
30842
|
+
type: props.graph.type,
|
|
30843
|
+
match: props.graph.match,
|
|
30844
|
+
numSides: props.graph.numSides,
|
|
30845
|
+
startCoords: props.graph.startCoords
|
|
30846
|
+
};
|
|
30847
|
+
case "quadratic":
|
|
30848
|
+
return {
|
|
30849
|
+
type: props.graph.type,
|
|
30850
|
+
startCoords: props.graph.startCoords
|
|
30851
|
+
};
|
|
30852
|
+
case "ray":
|
|
30853
|
+
return {
|
|
30854
|
+
type: props.graph.type,
|
|
30855
|
+
startCoords: props.graph.startCoords
|
|
30856
|
+
};
|
|
30857
|
+
case "segment":
|
|
30858
|
+
return {
|
|
30859
|
+
type: props.graph.type,
|
|
30860
|
+
numSegments: props.graph.numSegments,
|
|
30861
|
+
startCoords: props.graph.startCoords
|
|
30862
|
+
};
|
|
30863
|
+
case "sinusoid":
|
|
30864
|
+
return {
|
|
30865
|
+
type: props.graph.type,
|
|
30866
|
+
startCoords: props.graph.startCoords
|
|
30867
|
+
};
|
|
30868
|
+
case "none":
|
|
30869
|
+
return {};
|
|
30870
|
+
default:
|
|
30871
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30872
|
+
}
|
|
30873
|
+
};
|
|
30874
|
+
const getUserInput = userInput => {
|
|
30875
|
+
const type = userInput.type;
|
|
30876
|
+
switch (type) {
|
|
30877
|
+
case "angle":
|
|
30878
|
+
return {
|
|
30879
|
+
coords: userInput.coords,
|
|
30880
|
+
angleOffsetDegrees: userInput.angleOffsetDeg
|
|
30881
|
+
};
|
|
30882
|
+
case "circle":
|
|
30883
|
+
return {
|
|
30884
|
+
center: userInput.center,
|
|
30885
|
+
radius: userInput.radius
|
|
30886
|
+
};
|
|
30887
|
+
case "linear":
|
|
30888
|
+
return {
|
|
30889
|
+
coords: userInput.coords
|
|
30890
|
+
};
|
|
30891
|
+
case "linear-system":
|
|
30892
|
+
return {
|
|
30893
|
+
coords: userInput.coords
|
|
30894
|
+
};
|
|
30895
|
+
case "point":
|
|
30896
|
+
return {
|
|
30897
|
+
coords: userInput.coords
|
|
30898
|
+
};
|
|
30899
|
+
case "polygon":
|
|
30900
|
+
return {
|
|
30901
|
+
coords: userInput.coords
|
|
30902
|
+
};
|
|
30903
|
+
case "quadratic":
|
|
30904
|
+
return {
|
|
30905
|
+
coords: userInput.coords
|
|
30906
|
+
};
|
|
30907
|
+
case "ray":
|
|
30908
|
+
return {
|
|
30909
|
+
coords: userInput.coords
|
|
30910
|
+
};
|
|
30911
|
+
case "segment":
|
|
30912
|
+
return {
|
|
30913
|
+
coords: userInput.coords
|
|
30914
|
+
};
|
|
30915
|
+
case "sinusoid":
|
|
30916
|
+
return {
|
|
30917
|
+
coords: userInput.coords
|
|
30918
|
+
};
|
|
30919
|
+
case "none":
|
|
30920
|
+
return {};
|
|
30921
|
+
default:
|
|
30922
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30923
|
+
}
|
|
30924
|
+
};
|
|
30925
|
+
|
|
30542
30926
|
function pathBuilder() {
|
|
30543
30927
|
return new PathBuilder();
|
|
30544
30928
|
}
|
|
@@ -31790,6 +32174,10 @@ const LegacyGrid = _ref => {
|
|
|
31790
32174
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
31791
32175
|
* area.
|
|
31792
32176
|
*/
|
|
32177
|
+
/**
|
|
32178
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32179
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32180
|
+
*/
|
|
31793
32181
|
/**
|
|
31794
32182
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
31795
32183
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
@@ -31886,6 +32274,10 @@ const ItemExtras = [
|
|
|
31886
32274
|
// A background image applied to various widgets.
|
|
31887
32275
|
|
|
31888
32276
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32277
|
+
|
|
32278
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
32279
|
+
|
|
32280
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
31889
32281
|
const lockedFigureColors = {
|
|
31890
32282
|
blue: "#3D7586",
|
|
31891
32283
|
green: "#447A53",
|
|
@@ -31906,6 +32298,105 @@ const lockedFigureFillStyles = {
|
|
|
31906
32298
|
|
|
31907
32299
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
31908
32300
|
|
|
32301
|
+
/**
|
|
32302
|
+
* 44 is touch best practice and AAA compliant for WCAG
|
|
32303
|
+
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
32304
|
+
*/
|
|
32305
|
+
const TARGET_SIZE = 44;
|
|
32306
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32307
|
+
|
|
32308
|
+
// same as pointsFromNormalized in interactive-graph.tsx
|
|
32309
|
+
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
32310
|
+
// maps over [x, y]
|
|
32311
|
+
coords.map((coord, i) => {
|
|
32312
|
+
const axisRange = range[i];
|
|
32313
|
+
if (noSnap) {
|
|
32314
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
32315
|
+
}
|
|
32316
|
+
const axisStep = step[i];
|
|
32317
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
32318
|
+
const tick = Math.round(coord * nSteps);
|
|
32319
|
+
return axisRange[MIN] + axisStep * tick;
|
|
32320
|
+
}));
|
|
32321
|
+
|
|
32322
|
+
// same as normalizeCoords in interactive-graph.tsx
|
|
32323
|
+
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
32324
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
32325
|
+
}));
|
|
32326
|
+
|
|
32327
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
32328
|
+
// bounds given in `state`.
|
|
32329
|
+
function bound$1(_ref) {
|
|
32330
|
+
let {
|
|
32331
|
+
snapStep,
|
|
32332
|
+
range,
|
|
32333
|
+
point
|
|
32334
|
+
} = _ref;
|
|
32335
|
+
const boundingBox = inset(snapStep, range);
|
|
32336
|
+
return clampToBox(boundingBox, point);
|
|
32337
|
+
}
|
|
32338
|
+
function isUnlimitedGraphState(state) {
|
|
32339
|
+
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
32340
|
+
}
|
|
32341
|
+
|
|
32342
|
+
/**
|
|
32343
|
+
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
32344
|
+
* This way, the entire resulting string can be rendered within <TeX>
|
|
32345
|
+
* and the text outside of the $ blocks will be non-TeX text.
|
|
32346
|
+
*/
|
|
32347
|
+
function replaceOutsideTeX(mathString) {
|
|
32348
|
+
// All the information we need is in the first section,
|
|
32349
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
32350
|
+
const firstSection = SimpleMarkdown__namespace.parse(mathString)[0];
|
|
32351
|
+
|
|
32352
|
+
// If it's blockMath, the outer level has the full math content.
|
|
32353
|
+
if (firstSection.type === "blockMath") {
|
|
32354
|
+
return firstSection.content;
|
|
32355
|
+
}
|
|
32356
|
+
|
|
32357
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
32358
|
+
// to look for individual math blocks.
|
|
32359
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
32360
|
+
let result = "";
|
|
32361
|
+
for (const piece of condensedNodes) {
|
|
32362
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
32363
|
+
}
|
|
32364
|
+
return result;
|
|
32365
|
+
}
|
|
32366
|
+
// Helper function for replaceOutsideTeX()
|
|
32367
|
+
// Condense adjacent text nodes into a single text node
|
|
32368
|
+
function condenseTextNodes(nodes) {
|
|
32369
|
+
const result = [];
|
|
32370
|
+
let currentText = "";
|
|
32371
|
+
for (const node of nodes) {
|
|
32372
|
+
if (node.type === "math") {
|
|
32373
|
+
if (currentText) {
|
|
32374
|
+
result.push({
|
|
32375
|
+
type: "text",
|
|
32376
|
+
content: currentText
|
|
32377
|
+
});
|
|
32378
|
+
currentText = "";
|
|
32379
|
+
}
|
|
32380
|
+
result.push(node);
|
|
32381
|
+
} else {
|
|
32382
|
+
currentText += node.content;
|
|
32383
|
+
}
|
|
32384
|
+
}
|
|
32385
|
+
if (currentText) {
|
|
32386
|
+
result.push({
|
|
32387
|
+
type: "text",
|
|
32388
|
+
content: currentText
|
|
32389
|
+
});
|
|
32390
|
+
}
|
|
32391
|
+
return result;
|
|
32392
|
+
}
|
|
32393
|
+
|
|
32394
|
+
// Helper function for replaceOutsideTeX()
|
|
32395
|
+
function escapeSpecialChars(str) {
|
|
32396
|
+
// Escape $, \, {, and } characters
|
|
32397
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32398
|
+
}
|
|
32399
|
+
|
|
31909
32400
|
function LockedLabel(props) {
|
|
31910
32401
|
const {
|
|
31911
32402
|
coord,
|
|
@@ -31918,7 +32409,7 @@ function LockedLabel(props) {
|
|
|
31918
32409
|
TeX
|
|
31919
32410
|
} = getDependencies();
|
|
31920
32411
|
|
|
31921
|
-
//
|
|
32412
|
+
// Note: The TeX component cannot be rendered within an SVG
|
|
31922
32413
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
31923
32414
|
className: "locked-label",
|
|
31924
32415
|
style: {
|
|
@@ -31930,7 +32421,7 @@ function LockedLabel(props) {
|
|
|
31930
32421
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
31931
32422
|
},
|
|
31932
32423
|
"aria-hidden": true
|
|
31933
|
-
}, /*#__PURE__*/React__namespace.createElement(TeX, null, text));
|
|
32424
|
+
}, /*#__PURE__*/React__namespace.createElement(TeX, null, replaceOutsideTeX(text)));
|
|
31934
32425
|
}
|
|
31935
32426
|
|
|
31936
32427
|
function GraphLockedLabelsLayer(props) {
|
|
@@ -32850,47 +33341,6 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
|
|
|
32850
33341
|
return scaledBisector;
|
|
32851
33342
|
}
|
|
32852
33343
|
|
|
32853
|
-
/**
|
|
32854
|
-
* 44 is touch best practice and AAA compliant for WCAG
|
|
32855
|
-
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
32856
|
-
*/
|
|
32857
|
-
const TARGET_SIZE = 44;
|
|
32858
|
-
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32859
|
-
|
|
32860
|
-
// same as pointsFromNormalized in interactive-graph.tsx
|
|
32861
|
-
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
32862
|
-
// maps over [x, y]
|
|
32863
|
-
coords.map((coord, i) => {
|
|
32864
|
-
const axisRange = range[i];
|
|
32865
|
-
if (noSnap) {
|
|
32866
|
-
return axisRange[MIN] + size(axisRange) * coord;
|
|
32867
|
-
}
|
|
32868
|
-
const axisStep = step[i];
|
|
32869
|
-
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
32870
|
-
const tick = Math.round(coord * nSteps);
|
|
32871
|
-
return axisRange[MIN] + axisStep * tick;
|
|
32872
|
-
}));
|
|
32873
|
-
|
|
32874
|
-
// same as normalizeCoords in interactive-graph.tsx
|
|
32875
|
-
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
32876
|
-
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
32877
|
-
}));
|
|
32878
|
-
|
|
32879
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
32880
|
-
// bounds given in `state`.
|
|
32881
|
-
function bound$1(_ref) {
|
|
32882
|
-
let {
|
|
32883
|
-
snapStep,
|
|
32884
|
-
range,
|
|
32885
|
-
point
|
|
32886
|
-
} = _ref;
|
|
32887
|
-
const boundingBox = inset(snapStep, range);
|
|
32888
|
-
return clampToBox(boundingBox, point);
|
|
32889
|
-
}
|
|
32890
|
-
function isUnlimitedGraphState(state) {
|
|
32891
|
-
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
32892
|
-
}
|
|
32893
|
-
|
|
32894
33344
|
var isProduction = process.env.NODE_ENV === 'production';
|
|
32895
33345
|
var prefix = 'Invariant failed';
|
|
32896
33346
|
function invariant(condition, message) {
|
|
@@ -36281,7 +36731,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
36281
36731
|
|
|
36282
36732
|
const eq$1 = Util.eq;
|
|
36283
36733
|
const deepEq$2 = Util.deepEq;
|
|
36284
|
-
function
|
|
36734
|
+
function scoreInteractiveGraph(userInput, rubric) {
|
|
36285
36735
|
// None-type graphs are not graded
|
|
36286
36736
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
36287
36737
|
return {
|
|
@@ -37779,6 +38229,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
37779
38229
|
}
|
|
37780
38230
|
throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
|
|
37781
38231
|
}
|
|
38232
|
+
getPromptJSON() {
|
|
38233
|
+
return getPromptJSON$b(this.props, this.getUserInput());
|
|
38234
|
+
}
|
|
37782
38235
|
render() {
|
|
37783
38236
|
// Mafs shim
|
|
37784
38237
|
const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
|
|
@@ -38143,7 +38596,7 @@ var InteractiveGraph$1 = {
|
|
|
38143
38596
|
displayName: "Interactive graph (Assessments only)",
|
|
38144
38597
|
widget: InteractiveGraph,
|
|
38145
38598
|
staticTransform: staticTransform$3,
|
|
38146
|
-
|
|
38599
|
+
scorer: scoreInteractiveGraph
|
|
38147
38600
|
};
|
|
38148
38601
|
|
|
38149
38602
|
// TODO: can we use WB typography?
|
|
@@ -38155,6 +38608,32 @@ const bodyXsmallBold = {
|
|
|
38155
38608
|
lineHeight: "22px"
|
|
38156
38609
|
};
|
|
38157
38610
|
|
|
38611
|
+
const getPromptJSON$a = (renderProps, userInput) => {
|
|
38612
|
+
const propMarkers = renderProps.markers.map(marker => {
|
|
38613
|
+
return {
|
|
38614
|
+
label: marker.label
|
|
38615
|
+
};
|
|
38616
|
+
});
|
|
38617
|
+
const inputMarkers = userInput.markers.map(marker => {
|
|
38618
|
+
return {
|
|
38619
|
+
label: marker.label,
|
|
38620
|
+
selected: marker.selected
|
|
38621
|
+
};
|
|
38622
|
+
});
|
|
38623
|
+
return {
|
|
38624
|
+
type: "label-image",
|
|
38625
|
+
options: {
|
|
38626
|
+
choices: renderProps.choices,
|
|
38627
|
+
imageUrl: renderProps.imageUrl,
|
|
38628
|
+
imageAlt: renderProps.imageAlt,
|
|
38629
|
+
markers: propMarkers
|
|
38630
|
+
},
|
|
38631
|
+
userInput: {
|
|
38632
|
+
markers: inputMarkers
|
|
38633
|
+
}
|
|
38634
|
+
};
|
|
38635
|
+
};
|
|
38636
|
+
|
|
38158
38637
|
const AnswerChoices = props => {
|
|
38159
38638
|
const {
|
|
38160
38639
|
strings
|
|
@@ -38240,59 +38719,6 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38240
38719
|
}
|
|
38241
38720
|
});
|
|
38242
38721
|
|
|
38243
|
-
// Question state for marker as result of user selected answers.
|
|
38244
|
-
|
|
38245
|
-
function scoreMarker(marker) {
|
|
38246
|
-
const score = {
|
|
38247
|
-
hasAnswers: false,
|
|
38248
|
-
isCorrect: false
|
|
38249
|
-
};
|
|
38250
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
38251
|
-
score.hasAnswers = true;
|
|
38252
|
-
}
|
|
38253
|
-
if (marker.answers.length > 0) {
|
|
38254
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38255
|
-
// All correct answers are selected by the user.
|
|
38256
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38257
|
-
}
|
|
38258
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38259
|
-
// Correct as no answers should be selected by the user.
|
|
38260
|
-
score.isCorrect = true;
|
|
38261
|
-
}
|
|
38262
|
-
return score;
|
|
38263
|
-
}
|
|
38264
|
-
|
|
38265
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38266
|
-
function labelImageValidator(userInput, rubric) {
|
|
38267
|
-
let numAnswered = 0;
|
|
38268
|
-
let numCorrect = 0;
|
|
38269
|
-
for (const marker of userInput.markers) {
|
|
38270
|
-
const score = scoreMarker(marker);
|
|
38271
|
-
if (score.hasAnswers) {
|
|
38272
|
-
numAnswered++;
|
|
38273
|
-
}
|
|
38274
|
-
if (score.isCorrect) {
|
|
38275
|
-
numCorrect++;
|
|
38276
|
-
}
|
|
38277
|
-
}
|
|
38278
|
-
|
|
38279
|
-
// We expect all question markers to be answered before grading.
|
|
38280
|
-
if (numAnswered !== userInput.markers.length) {
|
|
38281
|
-
return {
|
|
38282
|
-
type: "invalid",
|
|
38283
|
-
message: null
|
|
38284
|
-
};
|
|
38285
|
-
}
|
|
38286
|
-
return {
|
|
38287
|
-
type: "points",
|
|
38288
|
-
// Markers with no expected answers are graded as correct if user
|
|
38289
|
-
// makes no answer selection.
|
|
38290
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38291
|
-
total: 1,
|
|
38292
|
-
message: null
|
|
38293
|
-
};
|
|
38294
|
-
}
|
|
38295
|
-
|
|
38296
38722
|
const BringToFront = {
|
|
38297
38723
|
boxShadow: `0 8px 8px ${wonderBlocksTokens.color.offBlack64}`,
|
|
38298
38724
|
zIndex: 1000
|
|
@@ -38548,6 +38974,59 @@ const styles$b = aphrodite.StyleSheet.create({
|
|
|
38548
38974
|
}
|
|
38549
38975
|
});
|
|
38550
38976
|
|
|
38977
|
+
// Question state for marker as result of user selected answers.
|
|
38978
|
+
|
|
38979
|
+
function scoreMarker(marker) {
|
|
38980
|
+
const score = {
|
|
38981
|
+
hasAnswers: false,
|
|
38982
|
+
isCorrect: false
|
|
38983
|
+
};
|
|
38984
|
+
if (marker.selected && marker.selected.length > 0) {
|
|
38985
|
+
score.hasAnswers = true;
|
|
38986
|
+
}
|
|
38987
|
+
if (marker.answers.length > 0) {
|
|
38988
|
+
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38989
|
+
// All correct answers are selected by the user.
|
|
38990
|
+
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38991
|
+
}
|
|
38992
|
+
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38993
|
+
// Correct as no answers should be selected by the user.
|
|
38994
|
+
score.isCorrect = true;
|
|
38995
|
+
}
|
|
38996
|
+
return score;
|
|
38997
|
+
}
|
|
38998
|
+
|
|
38999
|
+
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
39000
|
+
function scoreLabelImage(userInput, rubric) {
|
|
39001
|
+
let numAnswered = 0;
|
|
39002
|
+
let numCorrect = 0;
|
|
39003
|
+
for (const marker of userInput.markers) {
|
|
39004
|
+
const score = scoreMarker(marker);
|
|
39005
|
+
if (score.hasAnswers) {
|
|
39006
|
+
numAnswered++;
|
|
39007
|
+
}
|
|
39008
|
+
if (score.isCorrect) {
|
|
39009
|
+
numCorrect++;
|
|
39010
|
+
}
|
|
39011
|
+
}
|
|
39012
|
+
|
|
39013
|
+
// We expect all question markers to be answered before grading.
|
|
39014
|
+
if (numAnswered !== userInput.markers.length) {
|
|
39015
|
+
return {
|
|
39016
|
+
type: "invalid",
|
|
39017
|
+
message: null
|
|
39018
|
+
};
|
|
39019
|
+
}
|
|
39020
|
+
return {
|
|
39021
|
+
type: "points",
|
|
39022
|
+
// Markers with no expected answers are graded as correct if user
|
|
39023
|
+
// makes no answer selection.
|
|
39024
|
+
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
39025
|
+
total: 1,
|
|
39026
|
+
message: null
|
|
39027
|
+
};
|
|
39028
|
+
}
|
|
39029
|
+
|
|
38551
39030
|
/**
|
|
38552
39031
|
* Represents a direction vector.
|
|
38553
39032
|
* No diagonals allowed.
|
|
@@ -38758,6 +39237,9 @@ class LabelImage extends React__namespace.Component {
|
|
|
38758
39237
|
markers
|
|
38759
39238
|
};
|
|
38760
39239
|
}
|
|
39240
|
+
getPromptJSON() {
|
|
39241
|
+
return getPromptJSON$a(this.props, this.getUserInput());
|
|
39242
|
+
}
|
|
38761
39243
|
|
|
38762
39244
|
// TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
|
|
38763
39245
|
showRationalesForCurrentlySelectedChoices(rubric) {
|
|
@@ -39123,7 +39605,7 @@ var LabelImage$1 = {
|
|
|
39123
39605
|
widget: LabelImageWithDependencies,
|
|
39124
39606
|
accessible: true,
|
|
39125
39607
|
isLintable: true,
|
|
39126
|
-
|
|
39608
|
+
scorer: scoreLabelImage
|
|
39127
39609
|
};
|
|
39128
39610
|
|
|
39129
39611
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -39833,7 +40315,23 @@ const styles$9 = aphrodite.StyleSheet.create({
|
|
|
39833
40315
|
}
|
|
39834
40316
|
});
|
|
39835
40317
|
|
|
39836
|
-
|
|
40318
|
+
const getPromptJSON$9 = (renderProps, userInput) => {
|
|
40319
|
+
return {
|
|
40320
|
+
type: "matcher",
|
|
40321
|
+
options: {
|
|
40322
|
+
labels: renderProps.labels,
|
|
40323
|
+
left: renderProps.left,
|
|
40324
|
+
right: renderProps.right,
|
|
40325
|
+
orderMatters: renderProps.orderMatters
|
|
40326
|
+
},
|
|
40327
|
+
userInput: {
|
|
40328
|
+
left: userInput.left,
|
|
40329
|
+
right: userInput.right
|
|
40330
|
+
}
|
|
40331
|
+
};
|
|
40332
|
+
};
|
|
40333
|
+
|
|
40334
|
+
function scoreMatcher(state, rubric) {
|
|
39837
40335
|
const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
|
|
39838
40336
|
return {
|
|
39839
40337
|
type: "points",
|
|
@@ -39900,6 +40398,9 @@ class Matcher extends React__namespace.Component {
|
|
|
39900
40398
|
right: this.refs.right.getOptions()
|
|
39901
40399
|
};
|
|
39902
40400
|
};
|
|
40401
|
+
getPromptJSON() {
|
|
40402
|
+
return getPromptJSON$9(this.props, this.getUserInput());
|
|
40403
|
+
}
|
|
39903
40404
|
|
|
39904
40405
|
// Programatic API for moving options
|
|
39905
40406
|
// This is used by testing
|
|
@@ -40043,10 +40544,23 @@ var Matcher$1 = {
|
|
|
40043
40544
|
displayName: "Matcher (two column)",
|
|
40044
40545
|
widget: Matcher,
|
|
40045
40546
|
isLintable: true,
|
|
40046
|
-
|
|
40547
|
+
scorer: scoreMatcher
|
|
40548
|
+
};
|
|
40549
|
+
|
|
40550
|
+
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
40551
|
+
return {
|
|
40552
|
+
type: "matrix",
|
|
40553
|
+
options: {
|
|
40554
|
+
height: renderProps.matrixBoardSize[0],
|
|
40555
|
+
width: renderProps.matrixBoardSize[1]
|
|
40556
|
+
},
|
|
40557
|
+
userInput: {
|
|
40558
|
+
answerRows: userInput.answers
|
|
40559
|
+
}
|
|
40560
|
+
};
|
|
40047
40561
|
};
|
|
40048
40562
|
|
|
40049
|
-
function
|
|
40563
|
+
function scoreMatrix(userInput, rubric, strings) {
|
|
40050
40564
|
const solution = rubric.answers;
|
|
40051
40565
|
const supplied = userInput.answers;
|
|
40052
40566
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -40323,6 +40837,9 @@ class Matrix extends React__namespace.Component {
|
|
|
40323
40837
|
answers: this.props.answers
|
|
40324
40838
|
};
|
|
40325
40839
|
}
|
|
40840
|
+
getPromptJSON() {
|
|
40841
|
+
return getPromptJSON$8(this.props, this.getUserInput());
|
|
40842
|
+
}
|
|
40326
40843
|
render() {
|
|
40327
40844
|
// Set the input sizes through JS so we can control the size of the
|
|
40328
40845
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40508,7 +41025,11 @@ var Matrix$1 = {
|
|
|
40508
41025
|
transform: propTransform$1,
|
|
40509
41026
|
staticTransform: staticTransform$2,
|
|
40510
41027
|
isLintable: true,
|
|
40511
|
-
|
|
41028
|
+
scorer: scoreMatrix
|
|
41029
|
+
};
|
|
41030
|
+
|
|
41031
|
+
const getPromptJSON$7 = () => {
|
|
41032
|
+
return getUnsupportedPromptJSON("measurer");
|
|
40512
41033
|
};
|
|
40513
41034
|
|
|
40514
41035
|
const defaultImage = {
|
|
@@ -40585,6 +41106,9 @@ class Measurer extends React__namespace.Component {
|
|
|
40585
41106
|
});
|
|
40586
41107
|
}
|
|
40587
41108
|
}
|
|
41109
|
+
getPromptJSON() {
|
|
41110
|
+
return getPromptJSON$7();
|
|
41111
|
+
}
|
|
40588
41112
|
render() {
|
|
40589
41113
|
const image = ___default["default"].extend({}, defaultImage, this.props.image);
|
|
40590
41114
|
|
|
@@ -40632,8 +41156,8 @@ var Measurer$1 = {
|
|
|
40632
41156
|
minor: 0
|
|
40633
41157
|
},
|
|
40634
41158
|
propUpgrades: propUpgrades,
|
|
40635
|
-
// TODO: things that aren't interactive shouldn't need
|
|
40636
|
-
|
|
41159
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41160
|
+
scorer: () => scoreNoop(1)
|
|
40637
41161
|
};
|
|
40638
41162
|
|
|
40639
41163
|
/**
|
|
@@ -41522,10 +42046,25 @@ var Molecule$1 = {
|
|
|
41522
42046
|
displayName: "Molecule renderer",
|
|
41523
42047
|
hidden: true,
|
|
41524
42048
|
widget: MoleculeWidget,
|
|
41525
|
-
|
|
42049
|
+
scorer: () => scoreNoop()
|
|
41526
42050
|
};
|
|
41527
42051
|
|
|
41528
|
-
|
|
42052
|
+
const getPromptJSON$6 = (renderProps, userInput) => {
|
|
42053
|
+
return {
|
|
42054
|
+
type: "number-line",
|
|
42055
|
+
options: {
|
|
42056
|
+
range: renderProps.range,
|
|
42057
|
+
numDivisions: renderProps.numDivisions,
|
|
42058
|
+
snapDivisions: renderProps.snapDivisions
|
|
42059
|
+
},
|
|
42060
|
+
userInput: {
|
|
42061
|
+
numLinePosition: userInput.numLinePosition,
|
|
42062
|
+
numDivisions: userInput.numDivisions
|
|
42063
|
+
}
|
|
42064
|
+
};
|
|
42065
|
+
};
|
|
42066
|
+
|
|
42067
|
+
function scoreNumberLine(state, rubric) {
|
|
41529
42068
|
const range = rubric.range;
|
|
41530
42069
|
const divisionRange = state.divisionRange;
|
|
41531
42070
|
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
@@ -42014,6 +42553,9 @@ class NumberLine extends React__namespace.Component {
|
|
|
42014
42553
|
divisionRange: this.props.divisionRange
|
|
42015
42554
|
};
|
|
42016
42555
|
}
|
|
42556
|
+
getPromptJSON() {
|
|
42557
|
+
return getPromptJSON$6(this.props, this.getUserInput());
|
|
42558
|
+
}
|
|
42017
42559
|
render() {
|
|
42018
42560
|
const {
|
|
42019
42561
|
strings
|
|
@@ -42118,10 +42660,22 @@ var NumberLine$1 = {
|
|
|
42118
42660
|
widget: NumberLine,
|
|
42119
42661
|
transform: numberLineTransform,
|
|
42120
42662
|
staticTransform: staticTransform$1,
|
|
42121
|
-
|
|
42663
|
+
scorer: scoreNumberLine
|
|
42664
|
+
};
|
|
42665
|
+
|
|
42666
|
+
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
42667
|
+
return {
|
|
42668
|
+
type: "orderer",
|
|
42669
|
+
options: {
|
|
42670
|
+
options: renderProps.options.map(option => option.content)
|
|
42671
|
+
},
|
|
42672
|
+
userInput: {
|
|
42673
|
+
values: userInput.current
|
|
42674
|
+
}
|
|
42675
|
+
};
|
|
42122
42676
|
};
|
|
42123
42677
|
|
|
42124
|
-
function
|
|
42678
|
+
function scoreOrderer(userInput, rubric) {
|
|
42125
42679
|
if (userInput.current.length === 0) {
|
|
42126
42680
|
return {
|
|
42127
42681
|
type: "invalid",
|
|
@@ -42567,6 +43121,9 @@ class Orderer extends React__namespace.Component {
|
|
|
42567
43121
|
})
|
|
42568
43122
|
};
|
|
42569
43123
|
}
|
|
43124
|
+
getPromptJSON() {
|
|
43125
|
+
return getPromptJSON$5(this.props, this.getUserInput());
|
|
43126
|
+
}
|
|
42570
43127
|
render() {
|
|
42571
43128
|
// This is the card we are currently dragging
|
|
42572
43129
|
const dragging = this.state.dragging && /*#__PURE__*/React__namespace.createElement(Card
|
|
@@ -42677,7 +43234,7 @@ var Orderer$1 = {
|
|
|
42677
43234
|
hidden: true,
|
|
42678
43235
|
widget: Orderer,
|
|
42679
43236
|
isLintable: true,
|
|
42680
|
-
|
|
43237
|
+
scorer: scoreOrderer
|
|
42681
43238
|
};
|
|
42682
43239
|
|
|
42683
43240
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -42731,8 +43288,12 @@ var PassageRefTarget$1 = {
|
|
|
42731
43288
|
minor: 0
|
|
42732
43289
|
},
|
|
42733
43290
|
isLintable: true,
|
|
42734
|
-
// TODO: things that aren't interactive shouldn't need
|
|
42735
|
-
|
|
43291
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
43292
|
+
scorer: () => scoreNoop()
|
|
43293
|
+
};
|
|
43294
|
+
|
|
43295
|
+
const getPromptJSON$4 = () => {
|
|
43296
|
+
return getUnsupportedPromptJSON("phet-simulation");
|
|
42736
43297
|
};
|
|
42737
43298
|
|
|
42738
43299
|
/**
|
|
@@ -42784,6 +43345,9 @@ class PhetSimulation extends React__namespace.Component {
|
|
|
42784
43345
|
return kaLocale;
|
|
42785
43346
|
}
|
|
42786
43347
|
};
|
|
43348
|
+
getPromptJSON() {
|
|
43349
|
+
return getPromptJSON$4();
|
|
43350
|
+
}
|
|
42787
43351
|
displayLoadFailure = () => {
|
|
42788
43352
|
this.setState({
|
|
42789
43353
|
url: null,
|
|
@@ -42938,10 +43502,14 @@ var PhetSimulation$1 = {
|
|
|
42938
43502
|
isLintable: true
|
|
42939
43503
|
};
|
|
42940
43504
|
|
|
43505
|
+
const getPromptJSON$3 = () => {
|
|
43506
|
+
return getUnsupportedPromptJSON("plotter");
|
|
43507
|
+
};
|
|
43508
|
+
|
|
42941
43509
|
const {
|
|
42942
43510
|
deepEq: deepEq$1
|
|
42943
43511
|
} = Util;
|
|
42944
|
-
function
|
|
43512
|
+
function scorePlotter(userInput, rubric) {
|
|
42945
43513
|
if (deepEq$1(userInput, rubric.starting)) {
|
|
42946
43514
|
return {
|
|
42947
43515
|
type: "invalid",
|
|
@@ -43729,6 +44297,9 @@ class Plotter extends React__namespace.Component {
|
|
|
43729
44297
|
getUserInput() {
|
|
43730
44298
|
return this.state.values;
|
|
43731
44299
|
}
|
|
44300
|
+
getPromptJSON() {
|
|
44301
|
+
return getPromptJSON$3();
|
|
44302
|
+
}
|
|
43732
44303
|
render() {
|
|
43733
44304
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
43734
44305
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -43755,7 +44326,11 @@ var Plotter$1 = {
|
|
|
43755
44326
|
hidden: true,
|
|
43756
44327
|
widget: Plotter,
|
|
43757
44328
|
staticTransform: staticTransform,
|
|
43758
|
-
|
|
44329
|
+
scorer: scorePlotter
|
|
44330
|
+
};
|
|
44331
|
+
|
|
44332
|
+
const getPromptJSON$2 = () => {
|
|
44333
|
+
return getUnsupportedPromptJSON("python-program");
|
|
43759
44334
|
};
|
|
43760
44335
|
|
|
43761
44336
|
/**
|
|
@@ -43781,6 +44356,9 @@ class PythonProgram extends React__namespace.Component {
|
|
|
43781
44356
|
getDOMNodeForPath(path) {
|
|
43782
44357
|
return null;
|
|
43783
44358
|
}
|
|
44359
|
+
getPromptJSON() {
|
|
44360
|
+
return getPromptJSON$2();
|
|
44361
|
+
}
|
|
43784
44362
|
render() {
|
|
43785
44363
|
const url = getUrlFromProgramID(this.props.programID);
|
|
43786
44364
|
const iframeStyle = {
|
|
@@ -43815,7 +44393,17 @@ var PythonProgram$1 = {
|
|
|
43815
44393
|
widget: PythonProgram
|
|
43816
44394
|
};
|
|
43817
44395
|
|
|
43818
|
-
|
|
44396
|
+
const getPromptJSON$1 = userInput => {
|
|
44397
|
+
return {
|
|
44398
|
+
type: "sorter",
|
|
44399
|
+
userInput: {
|
|
44400
|
+
values: userInput.options,
|
|
44401
|
+
changed: userInput.changed
|
|
44402
|
+
}
|
|
44403
|
+
};
|
|
44404
|
+
};
|
|
44405
|
+
|
|
44406
|
+
function scoreSorter(userInput, rubric) {
|
|
43819
44407
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43820
44408
|
// prevents the "Check" button from becoming active. We want the user
|
|
43821
44409
|
// to make a change before trying to move forward. This makes an
|
|
@@ -43887,6 +44475,9 @@ class Sorter extends React__namespace.Component {
|
|
|
43887
44475
|
changed: this.state.changed
|
|
43888
44476
|
};
|
|
43889
44477
|
}
|
|
44478
|
+
getPromptJSON() {
|
|
44479
|
+
return getPromptJSON$1(this.getUserInput());
|
|
44480
|
+
}
|
|
43890
44481
|
moveOptionToIndex = (option, index) => {
|
|
43891
44482
|
// eslint-disable-next-line react/no-string-refs
|
|
43892
44483
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
@@ -43918,10 +44509,10 @@ var Sorter$1 = {
|
|
|
43918
44509
|
displayName: "Sorter",
|
|
43919
44510
|
widget: Sorter,
|
|
43920
44511
|
isLintable: true,
|
|
43921
|
-
|
|
44512
|
+
scorer: scoreSorter
|
|
43922
44513
|
};
|
|
43923
44514
|
|
|
43924
|
-
function
|
|
44515
|
+
function scoreTable(state, rubric, strings) {
|
|
43925
44516
|
const filterNonEmpty = function (table) {
|
|
43926
44517
|
return ___default["default"].filter(table, function (row) {
|
|
43927
44518
|
// Check if row has a cell that is nonempty
|
|
@@ -44195,7 +44786,11 @@ var Table$1 = {
|
|
|
44195
44786
|
transform: propTransform,
|
|
44196
44787
|
hidden: true,
|
|
44197
44788
|
isLintable: true,
|
|
44198
|
-
|
|
44789
|
+
scorer: scoreTable
|
|
44790
|
+
};
|
|
44791
|
+
|
|
44792
|
+
const getPromptJSON = () => {
|
|
44793
|
+
return getUnsupportedPromptJSON("video");
|
|
44199
44794
|
};
|
|
44200
44795
|
|
|
44201
44796
|
/**
|
|
@@ -44294,6 +44889,9 @@ class Video extends React__namespace.Component {
|
|
|
44294
44889
|
return change.apply(_this, args);
|
|
44295
44890
|
};
|
|
44296
44891
|
})();
|
|
44892
|
+
getPromptJSON() {
|
|
44893
|
+
return getPromptJSON();
|
|
44894
|
+
}
|
|
44297
44895
|
render() {
|
|
44298
44896
|
const {
|
|
44299
44897
|
InitialRequestUrl
|
|
@@ -44353,8 +44951,8 @@ var Video$1 = {
|
|
|
44353
44951
|
defaultAlignment: "block",
|
|
44354
44952
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44355
44953
|
widget: Video,
|
|
44356
|
-
// TODO: things that aren't interactive shouldn't need
|
|
44357
|
-
|
|
44954
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44955
|
+
scorer: () => scoreNoop()
|
|
44358
44956
|
};
|
|
44359
44957
|
|
|
44360
44958
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -44364,41 +44962,16 @@ var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Defin
|
|
|
44364
44962
|
* This should be called by all clients, specifying whether extra widgets are
|
|
44365
44963
|
* needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
|
|
44366
44964
|
* call it multiple times.
|
|
44367
|
-
*
|
|
44368
|
-
* skipMathJax:
|
|
44369
|
-
* If false/undefined, MathJax will be configured, and the
|
|
44370
|
-
* promise will wait for MathJax to load (if it hasn't already).
|
|
44371
44965
|
*/
|
|
44372
|
-
const init = function (
|
|
44966
|
+
const init = function () {
|
|
44373
44967
|
registerWidgets(basicWidgets);
|
|
44374
44968
|
registerWidgets(extraWidgets);
|
|
44375
44969
|
replaceDeprecatedWidgets();
|
|
44376
|
-
|
|
44377
|
-
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
44378
|
-
const skipMathJax = options.skipMathJax;
|
|
44379
|
-
if (skipMathJax) {
|
|
44380
|
-
// @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
|
|
44381
|
-
return Promise.resolve();
|
|
44382
|
-
}
|
|
44383
|
-
return new Promise((resolve, reject) => {
|
|
44384
|
-
MathJax.Hub.Config({
|
|
44385
|
-
messageStyle: "none",
|
|
44386
|
-
skipStartupTypeset: "none",
|
|
44387
|
-
"HTML-CSS": {
|
|
44388
|
-
availableFonts: ["TeX"],
|
|
44389
|
-
imageFont: null,
|
|
44390
|
-
scale: 100,
|
|
44391
|
-
showMathMenu: false
|
|
44392
|
-
}
|
|
44393
|
-
});
|
|
44394
|
-
MathJax.Hub.Configured();
|
|
44395
|
-
MathJax.Hub.Queue(resolve);
|
|
44396
|
-
});
|
|
44397
44970
|
};
|
|
44398
44971
|
|
|
44399
44972
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44400
44973
|
const libName = "@khanacademy/perseus";
|
|
44401
|
-
const libVersion = "41.
|
|
44974
|
+
const libVersion = "41.3.0";
|
|
44402
44975
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44403
44976
|
|
|
44404
44977
|
const apiVersion = {
|
|
@@ -45134,6 +45707,9 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
45134
45707
|
getNumHints() {
|
|
45135
45708
|
return this.props.item.hints.length;
|
|
45136
45709
|
}
|
|
45710
|
+
getPromptJSON() {
|
|
45711
|
+
return this.questionRenderer.getPromptJSON();
|
|
45712
|
+
}
|
|
45137
45713
|
|
|
45138
45714
|
/**
|
|
45139
45715
|
* Grades the item.
|
|
@@ -45791,7 +46367,7 @@ function treeToItem(node) {
|
|
|
45791
46367
|
};
|
|
45792
46368
|
}
|
|
45793
46369
|
|
|
45794
|
-
var isObject = function (obj) {
|
|
46370
|
+
var isObject$1 = function (obj) {
|
|
45795
46371
|
return obj === Object(obj);
|
|
45796
46372
|
};
|
|
45797
46373
|
var merge = function () {
|
|
@@ -45807,7 +46383,7 @@ var merge = function () {
|
|
|
45807
46383
|
return obj;
|
|
45808
46384
|
};
|
|
45809
46385
|
var clone = function (obj) {
|
|
45810
|
-
if (!isObject(obj)) {
|
|
46386
|
+
if (!isObject$1(obj)) {
|
|
45811
46387
|
return obj;
|
|
45812
46388
|
}
|
|
45813
46389
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45823,7 +46399,7 @@ var clone = function (obj) {
|
|
|
45823
46399
|
var dispatch = function (x) {
|
|
45824
46400
|
if (Array.isArray(x)) {
|
|
45825
46401
|
return arr;
|
|
45826
|
-
} else if (isObject(x)) {
|
|
46402
|
+
} else if (isObject$1(x)) {
|
|
45827
46403
|
return obj;
|
|
45828
46404
|
} else if (typeof x === "string") {
|
|
45829
46405
|
return str;
|
|
@@ -47747,6 +48323,697 @@ function buildTestData(testObject) {
|
|
|
47747
48323
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47748
48324
|
}
|
|
47749
48325
|
|
|
48326
|
+
function success(value) {
|
|
48327
|
+
return {
|
|
48328
|
+
type: "success",
|
|
48329
|
+
value
|
|
48330
|
+
};
|
|
48331
|
+
}
|
|
48332
|
+
function failure(detail) {
|
|
48333
|
+
return {
|
|
48334
|
+
type: "failure",
|
|
48335
|
+
detail
|
|
48336
|
+
};
|
|
48337
|
+
}
|
|
48338
|
+
function isFailure(result) {
|
|
48339
|
+
return result.type === "failure";
|
|
48340
|
+
}
|
|
48341
|
+
function isSuccess(result) {
|
|
48342
|
+
return result.type === "success";
|
|
48343
|
+
}
|
|
48344
|
+
|
|
48345
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
48346
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
48347
|
+
function all(results) {
|
|
48348
|
+
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
48349
|
+
const values = [];
|
|
48350
|
+
const failureDetails = [];
|
|
48351
|
+
for (const result of results) {
|
|
48352
|
+
if (result.type === "success") {
|
|
48353
|
+
values.push(result.value);
|
|
48354
|
+
} else {
|
|
48355
|
+
failureDetails.push(result.detail);
|
|
48356
|
+
}
|
|
48357
|
+
}
|
|
48358
|
+
if (failureDetails.length > 0) {
|
|
48359
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
48360
|
+
}
|
|
48361
|
+
return success(values);
|
|
48362
|
+
}
|
|
48363
|
+
|
|
48364
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48365
|
+
|
|
48366
|
+
function array(elementParser) {
|
|
48367
|
+
return (rawValue, ctx) => {
|
|
48368
|
+
if (!Array.isArray(rawValue)) {
|
|
48369
|
+
return ctx.failure("array", rawValue);
|
|
48370
|
+
}
|
|
48371
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
48372
|
+
return all(elementResults, concat);
|
|
48373
|
+
};
|
|
48374
|
+
}
|
|
48375
|
+
function concat(a, b) {
|
|
48376
|
+
return [...a, ...b];
|
|
48377
|
+
}
|
|
48378
|
+
|
|
48379
|
+
function boolean(rawValue, ctx) {
|
|
48380
|
+
if (typeof rawValue === "boolean") {
|
|
48381
|
+
return ctx.success(rawValue);
|
|
48382
|
+
}
|
|
48383
|
+
return ctx.failure("boolean", rawValue);
|
|
48384
|
+
}
|
|
48385
|
+
|
|
48386
|
+
function constant(acceptedValue) {
|
|
48387
|
+
return (rawValue, ctx) => {
|
|
48388
|
+
if (rawValue !== acceptedValue) {
|
|
48389
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
48390
|
+
}
|
|
48391
|
+
return ctx.success(acceptedValue);
|
|
48392
|
+
};
|
|
48393
|
+
}
|
|
48394
|
+
|
|
48395
|
+
function enumeration() {
|
|
48396
|
+
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
48397
|
+
acceptedValues[_key] = arguments[_key];
|
|
48398
|
+
}
|
|
48399
|
+
return (rawValue, ctx) => {
|
|
48400
|
+
if (typeof rawValue === "string") {
|
|
48401
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
48402
|
+
if (index > -1) {
|
|
48403
|
+
return ctx.success(acceptedValues[index]);
|
|
48404
|
+
}
|
|
48405
|
+
}
|
|
48406
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
48407
|
+
return ctx.failure(expected, rawValue);
|
|
48408
|
+
};
|
|
48409
|
+
}
|
|
48410
|
+
|
|
48411
|
+
function isObject(x) {
|
|
48412
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
48413
|
+
}
|
|
48414
|
+
|
|
48415
|
+
function nullable(parseValue) {
|
|
48416
|
+
return (rawValue, ctx) => {
|
|
48417
|
+
if (rawValue === null) {
|
|
48418
|
+
return ctx.success(rawValue);
|
|
48419
|
+
}
|
|
48420
|
+
return parseValue(rawValue, ctx);
|
|
48421
|
+
};
|
|
48422
|
+
}
|
|
48423
|
+
|
|
48424
|
+
const number = (rawValue, ctx) => {
|
|
48425
|
+
if (typeof rawValue === "number") {
|
|
48426
|
+
return ctx.success(rawValue);
|
|
48427
|
+
}
|
|
48428
|
+
return ctx.failure("number", rawValue);
|
|
48429
|
+
};
|
|
48430
|
+
|
|
48431
|
+
function object(schema) {
|
|
48432
|
+
return (rawValue, ctx) => {
|
|
48433
|
+
if (!isObject(rawValue)) {
|
|
48434
|
+
return ctx.failure("object", rawValue);
|
|
48435
|
+
}
|
|
48436
|
+
const ret = {
|
|
48437
|
+
...rawValue
|
|
48438
|
+
};
|
|
48439
|
+
const mismatches = [];
|
|
48440
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
48441
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
48442
|
+
if (isSuccess(result)) {
|
|
48443
|
+
ret[prop] = result.value;
|
|
48444
|
+
} else {
|
|
48445
|
+
mismatches.push(...result.detail);
|
|
48446
|
+
}
|
|
48447
|
+
}
|
|
48448
|
+
if (mismatches.length > 0) {
|
|
48449
|
+
return failure(mismatches);
|
|
48450
|
+
}
|
|
48451
|
+
return ctx.success(ret);
|
|
48452
|
+
};
|
|
48453
|
+
}
|
|
48454
|
+
|
|
48455
|
+
function optional(parseValue) {
|
|
48456
|
+
return (rawValue, ctx) => {
|
|
48457
|
+
if (rawValue === undefined) {
|
|
48458
|
+
return ctx.success(rawValue);
|
|
48459
|
+
}
|
|
48460
|
+
return parseValue(rawValue, ctx);
|
|
48461
|
+
};
|
|
48462
|
+
}
|
|
48463
|
+
|
|
48464
|
+
function pair(parseA, parseB) {
|
|
48465
|
+
return (rawValue, ctx) => {
|
|
48466
|
+
if (!Array.isArray(rawValue)) {
|
|
48467
|
+
return ctx.failure("array", rawValue);
|
|
48468
|
+
}
|
|
48469
|
+
if (rawValue.length !== 2) {
|
|
48470
|
+
return ctx.failure("array of length 2", rawValue);
|
|
48471
|
+
}
|
|
48472
|
+
const [rawA, rawB] = rawValue;
|
|
48473
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
48474
|
+
if (isFailure(resultA)) {
|
|
48475
|
+
return resultA;
|
|
48476
|
+
}
|
|
48477
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
48478
|
+
if (isFailure(resultB)) {
|
|
48479
|
+
return resultB;
|
|
48480
|
+
}
|
|
48481
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
48482
|
+
};
|
|
48483
|
+
}
|
|
48484
|
+
|
|
48485
|
+
function record(parseKey, parseValue) {
|
|
48486
|
+
return (rawValue, ctx) => {
|
|
48487
|
+
if (!isObject(rawValue)) {
|
|
48488
|
+
return ctx.failure("object", rawValue);
|
|
48489
|
+
}
|
|
48490
|
+
const result = {};
|
|
48491
|
+
const mismatches = [];
|
|
48492
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
48493
|
+
const entryCtx = ctx.forSubtree(key);
|
|
48494
|
+
const keyResult = parseKey(key, entryCtx);
|
|
48495
|
+
if (isFailure(keyResult)) {
|
|
48496
|
+
mismatches.push(...keyResult.detail);
|
|
48497
|
+
}
|
|
48498
|
+
const valueResult = parseValue(value, entryCtx);
|
|
48499
|
+
if (isFailure(valueResult)) {
|
|
48500
|
+
mismatches.push(...valueResult.detail);
|
|
48501
|
+
}
|
|
48502
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
48503
|
+
result[keyResult.value] = valueResult.value;
|
|
48504
|
+
}
|
|
48505
|
+
}
|
|
48506
|
+
if (mismatches.length > 0) {
|
|
48507
|
+
return failure(mismatches);
|
|
48508
|
+
}
|
|
48509
|
+
return ctx.success(result);
|
|
48510
|
+
};
|
|
48511
|
+
}
|
|
48512
|
+
|
|
48513
|
+
const string = (rawValue, ctx) => {
|
|
48514
|
+
if (typeof rawValue === "string") {
|
|
48515
|
+
return ctx.success(rawValue);
|
|
48516
|
+
}
|
|
48517
|
+
return ctx.failure("string", rawValue);
|
|
48518
|
+
};
|
|
48519
|
+
|
|
48520
|
+
function trio(parseA, parseB, parseC) {
|
|
48521
|
+
return (rawValue, ctx) => {
|
|
48522
|
+
if (!Array.isArray(rawValue)) {
|
|
48523
|
+
return ctx.failure("array", rawValue);
|
|
48524
|
+
}
|
|
48525
|
+
if (rawValue.length !== 3) {
|
|
48526
|
+
return ctx.failure("array of length 3", rawValue);
|
|
48527
|
+
}
|
|
48528
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
48529
|
+
if (isFailure(resultA)) {
|
|
48530
|
+
return resultA;
|
|
48531
|
+
}
|
|
48532
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
48533
|
+
if (isFailure(resultB)) {
|
|
48534
|
+
return resultB;
|
|
48535
|
+
}
|
|
48536
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
48537
|
+
if (isFailure(resultC)) {
|
|
48538
|
+
return resultC;
|
|
48539
|
+
}
|
|
48540
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
48541
|
+
};
|
|
48542
|
+
}
|
|
48543
|
+
|
|
48544
|
+
function union(parseBranch) {
|
|
48545
|
+
return new UnionBuilder(parseBranch);
|
|
48546
|
+
}
|
|
48547
|
+
class UnionBuilder {
|
|
48548
|
+
constructor(parser) {
|
|
48549
|
+
this.parser = parser;
|
|
48550
|
+
}
|
|
48551
|
+
or(newBranch) {
|
|
48552
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
48553
|
+
}
|
|
48554
|
+
}
|
|
48555
|
+
function either(parseA, parseB) {
|
|
48556
|
+
return (rawValue, ctx) => {
|
|
48557
|
+
const resultA = parseA(rawValue, ctx);
|
|
48558
|
+
if (isSuccess(resultA)) {
|
|
48559
|
+
return resultA;
|
|
48560
|
+
}
|
|
48561
|
+
return parseB(rawValue, ctx);
|
|
48562
|
+
};
|
|
48563
|
+
}
|
|
48564
|
+
|
|
48565
|
+
function parseWidget(parseType, parseOptions) {
|
|
48566
|
+
return object({
|
|
48567
|
+
type: parseType,
|
|
48568
|
+
static: optional(boolean),
|
|
48569
|
+
graded: optional(boolean),
|
|
48570
|
+
alignment: optional(string),
|
|
48571
|
+
options: parseOptions,
|
|
48572
|
+
key: optional(number),
|
|
48573
|
+
version: object({
|
|
48574
|
+
major: number,
|
|
48575
|
+
minor: number
|
|
48576
|
+
})
|
|
48577
|
+
});
|
|
48578
|
+
}
|
|
48579
|
+
|
|
48580
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
48581
|
+
items: array(string),
|
|
48582
|
+
categories: array(string),
|
|
48583
|
+
randomizeItems: boolean,
|
|
48584
|
+
static: boolean,
|
|
48585
|
+
values: array(number),
|
|
48586
|
+
highlightLint: optional(boolean),
|
|
48587
|
+
linterContext: optional(object({
|
|
48588
|
+
contentType: string,
|
|
48589
|
+
paths: array(string),
|
|
48590
|
+
stack: array(string)
|
|
48591
|
+
}))
|
|
48592
|
+
}));
|
|
48593
|
+
|
|
48594
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
48595
|
+
programID: string,
|
|
48596
|
+
programType: any,
|
|
48597
|
+
settings: array(object({
|
|
48598
|
+
name: string,
|
|
48599
|
+
value: string
|
|
48600
|
+
})),
|
|
48601
|
+
showEditor: boolean,
|
|
48602
|
+
showButtons: boolean,
|
|
48603
|
+
width: number,
|
|
48604
|
+
height: number,
|
|
48605
|
+
static: boolean
|
|
48606
|
+
}));
|
|
48607
|
+
|
|
48608
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48609
|
+
togglePrompt: string,
|
|
48610
|
+
definition: string,
|
|
48611
|
+
static: boolean
|
|
48612
|
+
}));
|
|
48613
|
+
|
|
48614
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48615
|
+
placeholder: string,
|
|
48616
|
+
static: boolean,
|
|
48617
|
+
choices: array(object({
|
|
48618
|
+
content: string,
|
|
48619
|
+
correct: boolean
|
|
48620
|
+
}))
|
|
48621
|
+
}));
|
|
48622
|
+
|
|
48623
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
48624
|
+
showPrompt: string,
|
|
48625
|
+
hidePrompt: string,
|
|
48626
|
+
explanation: string,
|
|
48627
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
48628
|
+
// referenced before it is defined. There is an import cycle between
|
|
48629
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
48630
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48631
|
+
static: boolean
|
|
48632
|
+
}));
|
|
48633
|
+
|
|
48634
|
+
const parseAnswerForm = object({
|
|
48635
|
+
value: string,
|
|
48636
|
+
form: boolean,
|
|
48637
|
+
simplify: boolean,
|
|
48638
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48639
|
+
key: optional(string)
|
|
48640
|
+
});
|
|
48641
|
+
const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
48642
|
+
answerForms: array(parseAnswerForm),
|
|
48643
|
+
functions: array(string),
|
|
48644
|
+
times: boolean,
|
|
48645
|
+
visibleLabel: optional(string),
|
|
48646
|
+
ariaLabel: optional(string),
|
|
48647
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48648
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48649
|
+
}));
|
|
48650
|
+
|
|
48651
|
+
const parsePerseusImageBackground = object({
|
|
48652
|
+
url: optional(nullable(string)),
|
|
48653
|
+
width: optional(number),
|
|
48654
|
+
height: optional(number),
|
|
48655
|
+
top: optional(number),
|
|
48656
|
+
left: optional(number),
|
|
48657
|
+
bottom: optional(number),
|
|
48658
|
+
// TODO(benchristel): convert scale to a number
|
|
48659
|
+
scale: optional(union(number).or(string).parser)
|
|
48660
|
+
});
|
|
48661
|
+
|
|
48662
|
+
// Used to represent 2-D points and ranges
|
|
48663
|
+
const pairOfNumbers = pair(number, number);
|
|
48664
|
+
const parsePerseusGraphTypeAngle = object({
|
|
48665
|
+
type: constant("angle"),
|
|
48666
|
+
showAngles: optional(boolean),
|
|
48667
|
+
allowReflexAngles: optional(boolean),
|
|
48668
|
+
angleOffsetDeg: optional(number),
|
|
48669
|
+
snapDegrees: optional(number),
|
|
48670
|
+
match: optional(constant("congruent")),
|
|
48671
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48672
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
48673
|
+
});
|
|
48674
|
+
const parsePerseusGraphTypeCircle = object({
|
|
48675
|
+
type: constant("circle"),
|
|
48676
|
+
center: optional(pairOfNumbers),
|
|
48677
|
+
radius: optional(number),
|
|
48678
|
+
startCoords: optional(object({
|
|
48679
|
+
center: pairOfNumbers,
|
|
48680
|
+
radius: number
|
|
48681
|
+
})),
|
|
48682
|
+
// TODO: remove coord? it's legacy.
|
|
48683
|
+
coord: optional(pairOfNumbers)
|
|
48684
|
+
});
|
|
48685
|
+
const parsePerseusGraphTypeLinear = object({
|
|
48686
|
+
type: constant("linear"),
|
|
48687
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48688
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48689
|
+
// TODO: remove coord? it's legacy.
|
|
48690
|
+
coord: optional(pairOfNumbers)
|
|
48691
|
+
});
|
|
48692
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
48693
|
+
type: constant("linear-system"),
|
|
48694
|
+
// TODO(benchristel): default coords to empty array?
|
|
48695
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48696
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48697
|
+
// TODO: remove coord? it's legacy.
|
|
48698
|
+
coord: optional(pairOfNumbers)
|
|
48699
|
+
});
|
|
48700
|
+
const parsePerseusGraphTypeNone = object({
|
|
48701
|
+
type: constant("none")
|
|
48702
|
+
});
|
|
48703
|
+
const parsePerseusGraphTypePoint = object({
|
|
48704
|
+
type: constant("point"),
|
|
48705
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
48706
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48707
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48708
|
+
// TODO: remove coord? it's legacy.
|
|
48709
|
+
coord: optional(pairOfNumbers)
|
|
48710
|
+
});
|
|
48711
|
+
const parsePerseusGraphTypePolygon = object({
|
|
48712
|
+
type: constant("polygon"),
|
|
48713
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
48714
|
+
showAngles: optional(boolean),
|
|
48715
|
+
showSides: optional(boolean),
|
|
48716
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
48717
|
+
match: optional(enumeration("similar", "congruent", "approx")),
|
|
48718
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48719
|
+
// TODO: remove coord? it's legacy.
|
|
48720
|
+
coord: optional(pairOfNumbers)
|
|
48721
|
+
});
|
|
48722
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
48723
|
+
type: constant("quadratic"),
|
|
48724
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
48725
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48726
|
+
// TODO: remove coord? it's legacy.
|
|
48727
|
+
coord: optional(pairOfNumbers)
|
|
48728
|
+
});
|
|
48729
|
+
const parsePerseusGraphTypeRay = object({
|
|
48730
|
+
type: constant("ray"),
|
|
48731
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48732
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48733
|
+
// TODO: remove coord? it's legacy.
|
|
48734
|
+
coord: optional(pairOfNumbers)
|
|
48735
|
+
});
|
|
48736
|
+
const parsePerseusGraphTypeSegment = object({
|
|
48737
|
+
type: constant("segment"),
|
|
48738
|
+
// TODO(benchristel): default numSegments?
|
|
48739
|
+
numSegments: optional(number),
|
|
48740
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48741
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48742
|
+
// TODO: remove coord? it's legacy.
|
|
48743
|
+
coord: optional(pairOfNumbers)
|
|
48744
|
+
});
|
|
48745
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
48746
|
+
type: constant("sinusoid"),
|
|
48747
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48748
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48749
|
+
// TODO: remove coord? it's legacy.
|
|
48750
|
+
coord: optional(pairOfNumbers)
|
|
48751
|
+
});
|
|
48752
|
+
const parsePerseusGraphType = union(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeCircle).or(parsePerseusGraphTypeLinear).or(parsePerseusGraphTypeLinearSystem).or(parsePerseusGraphTypeNone).or(parsePerseusGraphTypePoint).or(parsePerseusGraphTypePolygon).or(parsePerseusGraphTypeQuadratic).or(parsePerseusGraphTypeRay).or(parsePerseusGraphTypeSegment).or(parsePerseusGraphTypeSinusoid).parser;
|
|
48753
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48754
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48755
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48756
|
+
const parseLockedLabelType = object({
|
|
48757
|
+
type: constant("label"),
|
|
48758
|
+
coord: pairOfNumbers,
|
|
48759
|
+
text: string,
|
|
48760
|
+
color: parseLockedFigureColor,
|
|
48761
|
+
size: enumeration("small", "medium", "large")
|
|
48762
|
+
});
|
|
48763
|
+
const parseLockedPointType = object({
|
|
48764
|
+
type: constant("point"),
|
|
48765
|
+
coord: pairOfNumbers,
|
|
48766
|
+
color: parseLockedFigureColor,
|
|
48767
|
+
filled: boolean,
|
|
48768
|
+
// TODO(benchristel): default labels to empty array?
|
|
48769
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48770
|
+
ariaLabel: optional(string)
|
|
48771
|
+
});
|
|
48772
|
+
const parseLockedLineType = object({
|
|
48773
|
+
type: constant("line"),
|
|
48774
|
+
kind: enumeration("line", "ray", "segment"),
|
|
48775
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48776
|
+
color: parseLockedFigureColor,
|
|
48777
|
+
lineStyle: parseLockedLineStyle,
|
|
48778
|
+
showPoint1: boolean,
|
|
48779
|
+
showPoint2: boolean,
|
|
48780
|
+
// TODO(benchristel): default labels to empty array?
|
|
48781
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48782
|
+
ariaLabel: optional(string)
|
|
48783
|
+
});
|
|
48784
|
+
const parseLockedVectorType = object({
|
|
48785
|
+
type: constant("vector"),
|
|
48786
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
48787
|
+
color: parseLockedFigureColor,
|
|
48788
|
+
// TODO(benchristel): default labels to empty array?
|
|
48789
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48790
|
+
ariaLabel: optional(string)
|
|
48791
|
+
});
|
|
48792
|
+
const parseLockedEllipseType = object({
|
|
48793
|
+
type: constant("ellipse"),
|
|
48794
|
+
center: pairOfNumbers,
|
|
48795
|
+
radius: pairOfNumbers,
|
|
48796
|
+
angle: number,
|
|
48797
|
+
color: parseLockedFigureColor,
|
|
48798
|
+
fillStyle: parseLockedFigureFillType,
|
|
48799
|
+
strokeStyle: parseLockedLineStyle,
|
|
48800
|
+
// TODO(benchristel): default labels to empty array?
|
|
48801
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48802
|
+
ariaLabel: optional(string)
|
|
48803
|
+
});
|
|
48804
|
+
const parseLockedPolygonType = object({
|
|
48805
|
+
type: constant("polygon"),
|
|
48806
|
+
points: array(pairOfNumbers),
|
|
48807
|
+
color: parseLockedFigureColor,
|
|
48808
|
+
showVertices: boolean,
|
|
48809
|
+
fillStyle: parseLockedFigureFillType,
|
|
48810
|
+
strokeStyle: parseLockedLineStyle,
|
|
48811
|
+
// TODO(benchristel): default labels to empty array?
|
|
48812
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48813
|
+
ariaLabel: optional(string)
|
|
48814
|
+
});
|
|
48815
|
+
const parseLockedFunctionType = object({
|
|
48816
|
+
type: constant("function"),
|
|
48817
|
+
color: parseLockedFigureColor,
|
|
48818
|
+
strokeStyle: parseLockedLineStyle,
|
|
48819
|
+
equation: string,
|
|
48820
|
+
directionalAxis: enumeration("x", "y"),
|
|
48821
|
+
domain: optional(pairOfNumbers),
|
|
48822
|
+
// TODO(benchristel): default labels to empty array?
|
|
48823
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48824
|
+
ariaLabel: optional(string)
|
|
48825
|
+
});
|
|
48826
|
+
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
48827
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48828
|
+
step: pairOfNumbers,
|
|
48829
|
+
gridStep: pairOfNumbers,
|
|
48830
|
+
snapStep: pairOfNumbers,
|
|
48831
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
48832
|
+
markings: enumeration("graph", "grid", "none"),
|
|
48833
|
+
labels: array(string),
|
|
48834
|
+
showProtractor: boolean,
|
|
48835
|
+
showRuler: optional(boolean),
|
|
48836
|
+
showTooltips: optional(boolean),
|
|
48837
|
+
rulerLabel: optional(string),
|
|
48838
|
+
rulerTicks: optional(number),
|
|
48839
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
48840
|
+
graph: parsePerseusGraphType,
|
|
48841
|
+
correct: parsePerseusGraphType,
|
|
48842
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
48843
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
48844
|
+
fullGraphLabel: optional(string),
|
|
48845
|
+
fullGraphAriaDescription: optional(string)
|
|
48846
|
+
}));
|
|
48847
|
+
|
|
48848
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
48849
|
+
if (!isObject(rawValue)) {
|
|
48850
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
48851
|
+
}
|
|
48852
|
+
const widgetsMap = {};
|
|
48853
|
+
for (const key of Object.keys(rawValue)) {
|
|
48854
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
48855
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
48856
|
+
// against the widget type.
|
|
48857
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
48858
|
+
if (isFailure(entryResult)) {
|
|
48859
|
+
return entryResult;
|
|
48860
|
+
}
|
|
48861
|
+
}
|
|
48862
|
+
return ctx.success(widgetsMap);
|
|
48863
|
+
};
|
|
48864
|
+
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
48865
|
+
let [key, widget] = _ref;
|
|
48866
|
+
const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
|
|
48867
|
+
if (isFailure(keyComponentsResult)) {
|
|
48868
|
+
return keyComponentsResult;
|
|
48869
|
+
}
|
|
48870
|
+
const [type, id] = keyComponentsResult.value;
|
|
48871
|
+
function parseAndAssign(key, parse) {
|
|
48872
|
+
const widgetResult = parse(widget, ctx);
|
|
48873
|
+
if (isFailure(widgetResult)) {
|
|
48874
|
+
return widgetResult;
|
|
48875
|
+
}
|
|
48876
|
+
widgetMap[key] = widgetResult.value;
|
|
48877
|
+
return ctx.success(undefined);
|
|
48878
|
+
}
|
|
48879
|
+
switch (type) {
|
|
48880
|
+
case "categorizer":
|
|
48881
|
+
return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
|
|
48882
|
+
case "cs-program":
|
|
48883
|
+
return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
|
|
48884
|
+
case "definition":
|
|
48885
|
+
return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
|
|
48886
|
+
case "dropdown":
|
|
48887
|
+
return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
|
|
48888
|
+
case "explanation":
|
|
48889
|
+
return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
|
|
48890
|
+
case "expression":
|
|
48891
|
+
return parseAndAssign(`expression ${id}`, parseExpressionWidget);
|
|
48892
|
+
case "grapher":
|
|
48893
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48894
|
+
return parseAndAssign(`grapher ${id}`, any);
|
|
48895
|
+
case "group":
|
|
48896
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48897
|
+
return parseAndAssign(`group ${id}`, any);
|
|
48898
|
+
case "graded-group":
|
|
48899
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48900
|
+
return parseAndAssign(`graded-group ${id}`, any);
|
|
48901
|
+
case "graded-group-set":
|
|
48902
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48903
|
+
return parseAndAssign(`graded-group-set ${id}`, any);
|
|
48904
|
+
case "iframe":
|
|
48905
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48906
|
+
return parseAndAssign(`iframe ${id}`, any);
|
|
48907
|
+
case "image":
|
|
48908
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48909
|
+
return parseAndAssign(`image ${id}`, any);
|
|
48910
|
+
case "input-number":
|
|
48911
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48912
|
+
return parseAndAssign(`input-number ${id}`, any);
|
|
48913
|
+
case "interaction":
|
|
48914
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48915
|
+
return parseAndAssign(`interaction ${id}`, any);
|
|
48916
|
+
case "interactive-graph":
|
|
48917
|
+
return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
|
|
48918
|
+
case "label-image":
|
|
48919
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48920
|
+
return parseAndAssign(`label-image ${id}`, any);
|
|
48921
|
+
case "matcher":
|
|
48922
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48923
|
+
return parseAndAssign(`matcher ${id}`, any);
|
|
48924
|
+
case "matrix":
|
|
48925
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48926
|
+
return parseAndAssign(`matrix ${id}`, any);
|
|
48927
|
+
case "measurer":
|
|
48928
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48929
|
+
return parseAndAssign(`measurer ${id}`, any);
|
|
48930
|
+
case "molecule-renderer":
|
|
48931
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48932
|
+
return parseAndAssign(`molecule-renderer ${id}`, any);
|
|
48933
|
+
case "number-line":
|
|
48934
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48935
|
+
return parseAndAssign(`number-line ${id}`, any);
|
|
48936
|
+
case "numeric-input":
|
|
48937
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48938
|
+
return parseAndAssign(`numeric-input ${id}`, any);
|
|
48939
|
+
case "orderer":
|
|
48940
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48941
|
+
return parseAndAssign(`orderer ${id}`, any);
|
|
48942
|
+
case "passage":
|
|
48943
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48944
|
+
return parseAndAssign(`passage ${id}`, any);
|
|
48945
|
+
case "passage-ref":
|
|
48946
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48947
|
+
return parseAndAssign(`passage-ref ${id}`, any);
|
|
48948
|
+
case "passage-ref-target":
|
|
48949
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48950
|
+
return parseAndAssign(`passage-ref-target ${id}`, any);
|
|
48951
|
+
case "phet-simulation":
|
|
48952
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48953
|
+
return parseAndAssign(`phet-simulation ${id}`, any);
|
|
48954
|
+
case "plotter":
|
|
48955
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48956
|
+
return parseAndAssign(`plotter ${id}`, any);
|
|
48957
|
+
case "python-program":
|
|
48958
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48959
|
+
return parseAndAssign(`python-program ${id}`, any);
|
|
48960
|
+
case "radio":
|
|
48961
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48962
|
+
return parseAndAssign(`radio ${id}`, any);
|
|
48963
|
+
case "sorter":
|
|
48964
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48965
|
+
return parseAndAssign(`sorter ${id}`, any);
|
|
48966
|
+
case "table":
|
|
48967
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48968
|
+
return parseAndAssign(`table ${id}`, any);
|
|
48969
|
+
case "video":
|
|
48970
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48971
|
+
return parseAndAssign(`video ${id}`, any);
|
|
48972
|
+
default:
|
|
48973
|
+
return ctx.failure("a valid widget type", type);
|
|
48974
|
+
}
|
|
48975
|
+
};
|
|
48976
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48977
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48978
|
+
return ctx.failure("numeric string", rawValue);
|
|
48979
|
+
}
|
|
48980
|
+
return ctx.success(+rawValue);
|
|
48981
|
+
};
|
|
48982
|
+
const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
48983
|
+
|
|
48984
|
+
const parseHint = object({
|
|
48985
|
+
replace: optional(boolean),
|
|
48986
|
+
content: string,
|
|
48987
|
+
widgets: parseWidgetsMap,
|
|
48988
|
+
metadata: optional(array(string)),
|
|
48989
|
+
images: record(string, object({
|
|
48990
|
+
width: number,
|
|
48991
|
+
height: number
|
|
48992
|
+
}))
|
|
48993
|
+
});
|
|
48994
|
+
|
|
48995
|
+
const parsePerseusRenderer = object({
|
|
48996
|
+
content: string,
|
|
48997
|
+
widgets: parseWidgetsMap,
|
|
48998
|
+
metadata: optional(array(string)),
|
|
48999
|
+
images: record(string, object({
|
|
49000
|
+
width: number,
|
|
49001
|
+
height: number
|
|
49002
|
+
}))
|
|
49003
|
+
});
|
|
49004
|
+
|
|
49005
|
+
object({
|
|
49006
|
+
question: parsePerseusRenderer,
|
|
49007
|
+
hints: array(parseHint),
|
|
49008
|
+
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
49009
|
+
itemDataVersion: object({
|
|
49010
|
+
major: number,
|
|
49011
|
+
minor: number
|
|
49012
|
+
}),
|
|
49013
|
+
// Deprecated field
|
|
49014
|
+
answer: any
|
|
49015
|
+
});
|
|
49016
|
+
|
|
47750
49017
|
/**
|
|
47751
49018
|
* Helper to parse PerseusItem JSON
|
|
47752
49019
|
* Why not just use JSON.parse? We want:
|