@khanacademy/perseus 41.2.0 → 41.4.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 +1643 -354
- package/dist/es/index.js.map +1 -1
- package/dist/extra-widgets.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1562 -272
- package/dist/index.js.map +1 -1
- 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/answer-types.d.ts +45 -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 +4 -5
- 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 +1 -1
- 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/table/utils.d.ts +7 -0
- package/dist/widgets/table/validate-table.d.ts +6 -0
- package/dist/widgets/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +5 -5
- 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.
|
|
@@ -1552,7 +1588,7 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
|
1552
1588
|
// TOOD(kevinb): Figure out how this relates to KEScore in
|
|
1553
1589
|
// perseus-all-package/types.js and see if there's a way to
|
|
1554
1590
|
// unify these types.
|
|
1555
|
-
|
|
1591
|
+
/**
|
|
1556
1592
|
* Answer types
|
|
1557
1593
|
*
|
|
1558
1594
|
* Utility for creating answerable questions displayed in exercises
|
|
@@ -1595,7 +1631,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
|
1595
1631
|
* TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
|
|
1596
1632
|
*
|
|
1597
1633
|
*/
|
|
1598
|
-
|
|
1599
1634
|
const KhanAnswerTypes = {
|
|
1600
1635
|
/*
|
|
1601
1636
|
* predicate answer type
|
|
@@ -2067,9 +2102,7 @@ const KhanAnswerTypes = {
|
|
|
2067
2102
|
})];
|
|
2068
2103
|
},
|
|
2069
2104
|
createValidatorFunctional: function (correctAnswer, options, strings) {
|
|
2070
|
-
return KhanAnswerTypes.predicate.createValidatorFunctional(
|
|
2071
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
2072
|
-
...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
2105
|
+
return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
2073
2106
|
}
|
|
2074
2107
|
},
|
|
2075
2108
|
/*
|
|
@@ -2225,31 +2258,6 @@ const KhanAnswerTypes = {
|
|
|
2225
2258
|
}
|
|
2226
2259
|
};
|
|
2227
2260
|
|
|
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
2261
|
/* Content creators input a list of answers which are matched from top to
|
|
2254
2262
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2255
2263
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2268,7 +2276,7 @@ const getDecimalSeparator = locale => {
|
|
|
2268
2276
|
* show the user an error. TODO(joel) - what error?
|
|
2269
2277
|
* - Otherwise, pass through the resulting points and message.
|
|
2270
2278
|
*/
|
|
2271
|
-
function
|
|
2279
|
+
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2272
2280
|
const options = ___default["default"].clone(rubric);
|
|
2273
2281
|
___default["default"].extend(options, {
|
|
2274
2282
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2480,6 +2488,9 @@ class Expression extends React__namespace.Component {
|
|
|
2480
2488
|
getUserInput() {
|
|
2481
2489
|
return Expression.getUserInputFromProps(this.props);
|
|
2482
2490
|
}
|
|
2491
|
+
getPromptJSON() {
|
|
2492
|
+
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2493
|
+
}
|
|
2483
2494
|
change = (() => {
|
|
2484
2495
|
var _this = this;
|
|
2485
2496
|
return function () {
|
|
@@ -2764,7 +2775,7 @@ var Expression$1 = {
|
|
|
2764
2775
|
propUpgrades: propUpgrades$2,
|
|
2765
2776
|
// For use by the editor
|
|
2766
2777
|
isLintable: true,
|
|
2767
|
-
|
|
2778
|
+
scorer: scoreExpression,
|
|
2768
2779
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2769
2780
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2770
2781
|
if (correctAnswers.length === 0) {
|
|
@@ -18502,10 +18513,10 @@ const styles$C = aphrodite.StyleSheet.create({
|
|
|
18502
18513
|
|
|
18503
18514
|
/* eslint-disable no-useless-escape, no-prototype-builtins */
|
|
18504
18515
|
const rules$1 = {
|
|
18505
|
-
...
|
|
18516
|
+
...SimpleMarkdown$1.pureMarkdownRules,
|
|
18506
18517
|
// NOTE: basically ignored by JIPT. wraps everything at the outer layer
|
|
18507
18518
|
columns: {
|
|
18508
|
-
...
|
|
18519
|
+
...SimpleMarkdown$1.pureMarkdownRules.columns,
|
|
18509
18520
|
react: (node, output, state) => {
|
|
18510
18521
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
18511
18522
|
className: "perseus-two-columns",
|
|
@@ -18533,7 +18544,7 @@ const rules$1 = {
|
|
|
18533
18544
|
// for exercises the renderer just renders the crowdin id to the
|
|
18534
18545
|
// renderer div.
|
|
18535
18546
|
crowdinId: {
|
|
18536
|
-
...
|
|
18547
|
+
...SimpleMarkdown$1.pureMarkdownRules.crowdinId,
|
|
18537
18548
|
react: (node, output, state) => node.id
|
|
18538
18549
|
},
|
|
18539
18550
|
// Ensure that the table is focusable in sequential keyboard navigation
|
|
@@ -18542,7 +18553,7 @@ const rules$1 = {
|
|
|
18542
18553
|
// left/right gesture. Note, setting `tabindex` doesn't appear required for
|
|
18543
18554
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18544
18555
|
table: {
|
|
18545
|
-
...
|
|
18556
|
+
...SimpleMarkdown$1.pureMarkdownRules.table,
|
|
18546
18557
|
react: function (node, output, state) {
|
|
18547
18558
|
const table = SimpleMarkdown__default["default"].defaultRules.table.react(node, output, state);
|
|
18548
18559
|
if (!state.isMobile) {
|
|
@@ -18560,7 +18571,7 @@ const rules$1 = {
|
|
|
18560
18571
|
// our table regex into tableCapture[1..], and we pass tableCapture to
|
|
18561
18572
|
// our nptable regex
|
|
18562
18573
|
titledTable: {
|
|
18563
|
-
...
|
|
18574
|
+
...SimpleMarkdown$1.pureMarkdownRules.titledTable,
|
|
18564
18575
|
react: (node, output, state) => {
|
|
18565
18576
|
let contents;
|
|
18566
18577
|
if (!node.table) {
|
|
@@ -18620,7 +18631,7 @@ const rules$1 = {
|
|
|
18620
18631
|
}
|
|
18621
18632
|
},
|
|
18622
18633
|
widget: {
|
|
18623
|
-
...
|
|
18634
|
+
...SimpleMarkdown$1.pureMarkdownRules.widget,
|
|
18624
18635
|
react: (node, output, state) => {
|
|
18625
18636
|
// The actual output is handled in the renderer, where
|
|
18626
18637
|
// we know the current widget props/state. This is
|
|
@@ -18631,7 +18642,7 @@ const rules$1 = {
|
|
|
18631
18642
|
}
|
|
18632
18643
|
},
|
|
18633
18644
|
blockMath: {
|
|
18634
|
-
...
|
|
18645
|
+
...SimpleMarkdown$1.pureMarkdownRules.blockMath,
|
|
18635
18646
|
react: (node, output, state) => {
|
|
18636
18647
|
const {
|
|
18637
18648
|
TeX
|
|
@@ -18645,7 +18656,7 @@ const rules$1 = {
|
|
|
18645
18656
|
}
|
|
18646
18657
|
},
|
|
18647
18658
|
math: {
|
|
18648
|
-
...
|
|
18659
|
+
...SimpleMarkdown$1.pureMarkdownRules.math,
|
|
18649
18660
|
react: (node, output, state) => {
|
|
18650
18661
|
const {
|
|
18651
18662
|
TeX
|
|
@@ -18659,7 +18670,7 @@ const rules$1 = {
|
|
|
18659
18670
|
}
|
|
18660
18671
|
},
|
|
18661
18672
|
unescapedDollar: {
|
|
18662
|
-
...
|
|
18673
|
+
...SimpleMarkdown$1.pureMarkdownRules.unescapedDollar,
|
|
18663
18674
|
react: (node, output, state) => {
|
|
18664
18675
|
// Unescaped dollar signs render correctly, but result in
|
|
18665
18676
|
// untranslatable text after the i18n python linter flags it
|
|
@@ -18669,7 +18680,7 @@ const rules$1 = {
|
|
|
18669
18680
|
// Extend the SimpleMarkdown link parser to make the link open in a new
|
|
18670
18681
|
// window and handle reverse tabnapping phishing attacks
|
|
18671
18682
|
link: {
|
|
18672
|
-
...
|
|
18683
|
+
...SimpleMarkdown$1.pureMarkdownRules.link,
|
|
18673
18684
|
react: function (node, output, state) {
|
|
18674
18685
|
const link = SimpleMarkdown__default["default"].defaultRules.link.react(node, output, state);
|
|
18675
18686
|
const href = link.props.href;
|
|
@@ -18696,7 +18707,7 @@ const rules$1 = {
|
|
|
18696
18707
|
}
|
|
18697
18708
|
},
|
|
18698
18709
|
codeBlock: {
|
|
18699
|
-
...
|
|
18710
|
+
...SimpleMarkdown$1.pureMarkdownRules.codeBlock,
|
|
18700
18711
|
react: (node, output, state) => {
|
|
18701
18712
|
// ideally this should be a different rule, with only an
|
|
18702
18713
|
// output function, but right now that breaks the parser.
|
|
@@ -18714,7 +18725,7 @@ const rules$1 = {
|
|
|
18714
18725
|
// transform the tree to add lint nodes. This rule is here
|
|
18715
18726
|
// just for the react() function
|
|
18716
18727
|
lint: {
|
|
18717
|
-
...
|
|
18728
|
+
...SimpleMarkdown$1.pureMarkdownRules.lint,
|
|
18718
18729
|
react: (node, output, state) => {
|
|
18719
18730
|
return /*#__PURE__*/React__namespace.createElement(Lint, {
|
|
18720
18731
|
message: node.message,
|
|
@@ -18964,8 +18975,8 @@ const getWidget = name => {
|
|
|
18964
18975
|
const getWidgetExport = name => {
|
|
18965
18976
|
return widgets[name] ?? null;
|
|
18966
18977
|
};
|
|
18967
|
-
const
|
|
18968
|
-
return widgets[name]?.
|
|
18978
|
+
const getWidgetScorer = name => {
|
|
18979
|
+
return widgets[name]?.scorer ?? null;
|
|
18969
18980
|
};
|
|
18970
18981
|
const getEditor = name => {
|
|
18971
18982
|
return ___default["default"].has(editors, name) ? editors[name] : null;
|
|
@@ -19273,7 +19284,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19273
19284
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
19274
19285
|
getWidget: getWidget,
|
|
19275
19286
|
getWidgetExport: getWidgetExport,
|
|
19276
|
-
|
|
19287
|
+
getWidgetScorer: getWidgetScorer,
|
|
19277
19288
|
getEditor: getEditor,
|
|
19278
19289
|
getTransform: getTransform,
|
|
19279
19290
|
getVersion: getVersion,
|
|
@@ -19305,12 +19316,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19305
19316
|
}
|
|
19306
19317
|
let score = null;
|
|
19307
19318
|
const userInput = userInputMap[id];
|
|
19308
|
-
const
|
|
19319
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19309
19320
|
if (widget.type === "group") {
|
|
19310
19321
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19311
19322
|
score = Util.flattenScores(scores);
|
|
19312
|
-
} else if (
|
|
19313
|
-
score =
|
|
19323
|
+
} else if (scorer) {
|
|
19324
|
+
score = scorer(userInput, widget.options, strings, locale);
|
|
19314
19325
|
}
|
|
19315
19326
|
if (score) {
|
|
19316
19327
|
return Util.scoreIsEmpty(score);
|
|
@@ -19335,12 +19346,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19335
19346
|
return;
|
|
19336
19347
|
}
|
|
19337
19348
|
const userInput = userInputMap[id];
|
|
19338
|
-
const
|
|
19349
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19339
19350
|
if (widget.type === "group") {
|
|
19340
19351
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19341
19352
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19342
|
-
} else if (
|
|
19343
|
-
widgetScores[id] =
|
|
19353
|
+
} else if (scorer) {
|
|
19354
|
+
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19344
19355
|
}
|
|
19345
19356
|
});
|
|
19346
19357
|
return widgetScores;
|
|
@@ -19824,9 +19835,9 @@ class Renderer extends React__namespace.Component {
|
|
|
19824
19835
|
// WidgetContainers don't update their widgets' props when
|
|
19825
19836
|
// they are re-rendered, so even if they've been
|
|
19826
19837
|
// re-rendered we need to call these methods on them.
|
|
19827
|
-
|
|
19838
|
+
this.widgetIds.forEach(id => {
|
|
19828
19839
|
const container = this._widgetContainers.get(makeContainerId(id));
|
|
19829
|
-
container
|
|
19840
|
+
container?.replaceWidgetProps(this.getWidgetProps(id));
|
|
19830
19841
|
});
|
|
19831
19842
|
if (this.props.serializedState && !___default["default"].isEqual(this.props.serializedState, this.getSerializedState())) {
|
|
19832
19843
|
this.restoreSerializedState(this.props.serializedState);
|
|
@@ -19965,25 +19976,25 @@ class Renderer extends React__namespace.Component {
|
|
|
19965
19976
|
}
|
|
19966
19977
|
return null;
|
|
19967
19978
|
};
|
|
19968
|
-
getWidgetProps
|
|
19979
|
+
getWidgetProps(widgetId) {
|
|
19969
19980
|
var _this = this;
|
|
19970
19981
|
const apiOptions = this.getApiOptions();
|
|
19971
|
-
const widgetProps = this.state.widgetProps[
|
|
19982
|
+
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19972
19983
|
|
|
19973
19984
|
// The widget needs access to its "rubric" at all times when in review
|
|
19974
19985
|
// mode (which is really just part of its widget info).
|
|
19975
|
-
const widgetInfo = this.state.widgetInfo[
|
|
19986
|
+
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19976
19987
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
19977
19988
|
if (!this._interactionTrackers) {
|
|
19978
19989
|
this._interactionTrackers = {};
|
|
19979
19990
|
}
|
|
19980
|
-
let interactionTracker = this._interactionTrackers[
|
|
19991
|
+
let interactionTracker = this._interactionTrackers[widgetId];
|
|
19981
19992
|
if (!interactionTracker) {
|
|
19982
|
-
interactionTracker = this._interactionTrackers[
|
|
19993
|
+
interactionTracker = this._interactionTrackers[widgetId] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, widgetId, getTracking(widgetInfo && widgetInfo.type));
|
|
19983
19994
|
}
|
|
19984
19995
|
return {
|
|
19985
19996
|
...widgetProps,
|
|
19986
|
-
widgetId:
|
|
19997
|
+
widgetId: widgetId,
|
|
19987
19998
|
alignment: widgetInfo && widgetInfo.alignment,
|
|
19988
19999
|
static: widgetInfo?.static,
|
|
19989
20000
|
problemNum: this.props.problemNum,
|
|
@@ -19991,19 +20002,19 @@ class Renderer extends React__namespace.Component {
|
|
|
19991
20002
|
keypadElement: this.props.keypadElement,
|
|
19992
20003
|
questionCompleted: this.props.questionCompleted,
|
|
19993
20004
|
showSolutions: this.props.showSolutions,
|
|
19994
|
-
onFocus: ___default["default"].partial(this._onWidgetFocus,
|
|
19995
|
-
onBlur: ___default["default"].partial(this._onWidgetBlur,
|
|
20005
|
+
onFocus: ___default["default"].partial(this._onWidgetFocus, widgetId),
|
|
20006
|
+
onBlur: ___default["default"].partial(this._onWidgetBlur, widgetId),
|
|
19996
20007
|
findWidgets: this.findWidgets,
|
|
19997
20008
|
reviewModeRubric: reviewModeRubric,
|
|
19998
20009
|
reviewMode: this.props.reviewMode,
|
|
19999
20010
|
onChange: function (newProps, cb) {
|
|
20000
20011
|
let silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
20001
|
-
_this._setWidgetProps(
|
|
20012
|
+
_this._setWidgetProps(widgetId, newProps, cb, silent);
|
|
20002
20013
|
},
|
|
20003
20014
|
trackInteraction: interactionTracker.track,
|
|
20004
|
-
isLastUsedWidget:
|
|
20015
|
+
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20005
20016
|
};
|
|
20006
|
-
}
|
|
20017
|
+
}
|
|
20007
20018
|
|
|
20008
20019
|
/**
|
|
20009
20020
|
* Serializes the questions state so it can be recovered.
|
|
@@ -20892,6 +20903,25 @@ class Renderer extends React__namespace.Component {
|
|
|
20892
20903
|
return this.widgetIds;
|
|
20893
20904
|
};
|
|
20894
20905
|
|
|
20906
|
+
/**
|
|
20907
|
+
* Returns a JSON representation of the content and widgets
|
|
20908
|
+
* that can be passed to an LLM for prompt context.
|
|
20909
|
+
*/
|
|
20910
|
+
getPromptJSON() {
|
|
20911
|
+
const {
|
|
20912
|
+
content
|
|
20913
|
+
} = this.props;
|
|
20914
|
+
const widgetJSON = {};
|
|
20915
|
+
this.widgetIds.forEach(id => {
|
|
20916
|
+
const widget = this.getWidgetInstance(id);
|
|
20917
|
+
widgetJSON[id] = widget?.getPromptJSON?.() || {};
|
|
20918
|
+
});
|
|
20919
|
+
return {
|
|
20920
|
+
content,
|
|
20921
|
+
widgets: widgetJSON
|
|
20922
|
+
};
|
|
20923
|
+
}
|
|
20924
|
+
|
|
20895
20925
|
/**
|
|
20896
20926
|
* Returns an object mapping from widget ID to perseus-style score.
|
|
20897
20927
|
* The keys of this object are the values of the array returned
|
|
@@ -21618,6 +21648,19 @@ SimpleKeypadInput.propTypes = {
|
|
|
21618
21648
|
value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
|
|
21619
21649
|
};
|
|
21620
21650
|
|
|
21651
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
21652
|
+
return {
|
|
21653
|
+
type: "input-number",
|
|
21654
|
+
options: {
|
|
21655
|
+
simplify: renderProps.simplify,
|
|
21656
|
+
answerType: renderProps.answerType
|
|
21657
|
+
},
|
|
21658
|
+
userInput: {
|
|
21659
|
+
value: userInput.currentValue
|
|
21660
|
+
}
|
|
21661
|
+
};
|
|
21662
|
+
};
|
|
21663
|
+
|
|
21621
21664
|
/*
|
|
21622
21665
|
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21623
21666
|
* curly brackets.
|
|
@@ -21808,7 +21851,7 @@ const answerTypes = {
|
|
|
21808
21851
|
forms: "pi"
|
|
21809
21852
|
}
|
|
21810
21853
|
};
|
|
21811
|
-
function
|
|
21854
|
+
function scoreInputNumber(userInput, rubric, strings) {
|
|
21812
21855
|
if (rubric.answerType == null) {
|
|
21813
21856
|
rubric.answerType = "number";
|
|
21814
21857
|
}
|
|
@@ -21939,6 +21982,9 @@ class InputNumber extends React__namespace.Component {
|
|
|
21939
21982
|
getUserInput() {
|
|
21940
21983
|
return InputNumber.getUserInputFromProps(this.props);
|
|
21941
21984
|
}
|
|
21985
|
+
getPromptJSON() {
|
|
21986
|
+
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21987
|
+
}
|
|
21942
21988
|
examples = () => {
|
|
21943
21989
|
const {
|
|
21944
21990
|
strings
|
|
@@ -22038,7 +22084,7 @@ var InputNumber$1 = {
|
|
|
22038
22084
|
widget: InputNumber,
|
|
22039
22085
|
transform: propTransform$3,
|
|
22040
22086
|
isLintable: true,
|
|
22041
|
-
|
|
22087
|
+
scorer: scoreInputNumber,
|
|
22042
22088
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22043
22089
|
if (rubric.value == null) {
|
|
22044
22090
|
return;
|
|
@@ -22051,6 +22097,16 @@ var InputNumber$1 = {
|
|
|
22051
22097
|
}
|
|
22052
22098
|
};
|
|
22053
22099
|
|
|
22100
|
+
const getPromptJSON$r = (renderProps, userInput) => {
|
|
22101
|
+
return {
|
|
22102
|
+
type: "numeric-input",
|
|
22103
|
+
label: renderProps.labelText,
|
|
22104
|
+
userInput: {
|
|
22105
|
+
value: userInput.currentValue
|
|
22106
|
+
}
|
|
22107
|
+
};
|
|
22108
|
+
};
|
|
22109
|
+
|
|
22054
22110
|
const ParseTex = TexWrangler.parseTex;
|
|
22055
22111
|
const answerFormButtons = [{
|
|
22056
22112
|
title: "Integers",
|
|
@@ -22109,7 +22165,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
22109
22165
|
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22110
22166
|
return value;
|
|
22111
22167
|
}
|
|
22112
|
-
function
|
|
22168
|
+
function scoreNumericInput(userInput, rubric, strings) {
|
|
22113
22169
|
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22114
22170
|
// Don't default to validating the answer as a pi answer
|
|
22115
22171
|
// if answerForm isn't set on the answer
|
|
@@ -22271,6 +22327,9 @@ class NumericInput extends React__namespace.Component {
|
|
|
22271
22327
|
getUserInput() {
|
|
22272
22328
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22273
22329
|
}
|
|
22330
|
+
getPromptJSON() {
|
|
22331
|
+
return getPromptJSON$r(this.props, this.getUserInput());
|
|
22332
|
+
}
|
|
22274
22333
|
handleChange = (newValue, cb) => {
|
|
22275
22334
|
this.props.onChange({
|
|
22276
22335
|
currentValue: newValue
|
|
@@ -22396,7 +22455,7 @@ var NumericInput$1 = {
|
|
|
22396
22455
|
widget: NumericInput,
|
|
22397
22456
|
transform: propsTransform,
|
|
22398
22457
|
isLintable: true,
|
|
22399
|
-
|
|
22458
|
+
scorer: scoreNumericInput,
|
|
22400
22459
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22401
22460
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22402
22461
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -22427,15 +22486,43 @@ var NumericInput$1 = {
|
|
|
22427
22486
|
}
|
|
22428
22487
|
};
|
|
22429
22488
|
|
|
22489
|
+
const getPromptJSON$q = (renderProps, userInput) => {
|
|
22490
|
+
const choices = renderProps.choices || [];
|
|
22491
|
+
const options = choices.map(choice => {
|
|
22492
|
+
return {
|
|
22493
|
+
value: choice.content
|
|
22494
|
+
};
|
|
22495
|
+
});
|
|
22496
|
+
return {
|
|
22497
|
+
type: "radio",
|
|
22498
|
+
hasNoneOfTheAbove: !!renderProps.hasNoneOfTheAbove,
|
|
22499
|
+
options,
|
|
22500
|
+
userInput: {
|
|
22501
|
+
selectedOptions: userInput.choicesSelected.slice()
|
|
22502
|
+
}
|
|
22503
|
+
};
|
|
22504
|
+
};
|
|
22505
|
+
|
|
22506
|
+
const getPromptJSON$p = renderProps => {
|
|
22507
|
+
return {
|
|
22508
|
+
type: "passage-ref",
|
|
22509
|
+
options: {
|
|
22510
|
+
passageNumber: renderProps.passageNumber,
|
|
22511
|
+
referenceNumber: renderProps.referenceNumber,
|
|
22512
|
+
summaryText: renderProps.summaryText
|
|
22513
|
+
}
|
|
22514
|
+
};
|
|
22515
|
+
};
|
|
22516
|
+
|
|
22430
22517
|
/**
|
|
22431
|
-
* Several widgets don't have "right"/"wrong"
|
|
22518
|
+
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
22432
22519
|
* so this just says to move on past those widgets
|
|
22433
22520
|
*
|
|
22434
22521
|
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
22435
|
-
*
|
|
22522
|
+
* scoring logic and the thing scoring an exercise
|
|
22436
22523
|
* should just know to skip these
|
|
22437
22524
|
*/
|
|
22438
|
-
function
|
|
22525
|
+
function scoreNoop() {
|
|
22439
22526
|
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
22440
22527
|
return {
|
|
22441
22528
|
type: "points",
|
|
@@ -23829,6 +23916,17 @@ const styles$x = aphrodite.StyleSheet.create({
|
|
|
23829
23916
|
}
|
|
23830
23917
|
});
|
|
23831
23918
|
|
|
23919
|
+
const getPromptJSON$o = renderProps => {
|
|
23920
|
+
return {
|
|
23921
|
+
type: "passage",
|
|
23922
|
+
options: {
|
|
23923
|
+
passageTitle: renderProps.passageTitle,
|
|
23924
|
+
passageText: renderProps.passageText,
|
|
23925
|
+
footnotes: renderProps.footnotes
|
|
23926
|
+
}
|
|
23927
|
+
};
|
|
23928
|
+
};
|
|
23929
|
+
|
|
23832
23930
|
function getInitialParseState() {
|
|
23833
23931
|
return {
|
|
23834
23932
|
currentRef: [],
|
|
@@ -24461,6 +24559,9 @@ class Passage extends React__namespace.Component {
|
|
|
24461
24559
|
content: refContent
|
|
24462
24560
|
};
|
|
24463
24561
|
}
|
|
24562
|
+
getPromptJSON() {
|
|
24563
|
+
return getPromptJSON$o(this.props);
|
|
24564
|
+
}
|
|
24464
24565
|
|
|
24465
24566
|
/**
|
|
24466
24567
|
* Rendering
|
|
@@ -24606,7 +24707,7 @@ var Passage$1 = {
|
|
|
24606
24707
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
24607
24708
|
},
|
|
24608
24709
|
isLintable: true,
|
|
24609
|
-
|
|
24710
|
+
scorer: () => scoreNoop()
|
|
24610
24711
|
};
|
|
24611
24712
|
|
|
24612
24713
|
/**
|
|
@@ -24671,6 +24772,9 @@ class PassageRef extends React__namespace.Component {
|
|
|
24671
24772
|
return change.apply(_this, args);
|
|
24672
24773
|
};
|
|
24673
24774
|
})();
|
|
24775
|
+
getPromptJSON() {
|
|
24776
|
+
return getPromptJSON$p(this.props);
|
|
24777
|
+
}
|
|
24674
24778
|
_deferredUpdateRange = () => {
|
|
24675
24779
|
___default["default"].defer(this._updateRange);
|
|
24676
24780
|
};
|
|
@@ -24740,8 +24844,8 @@ var PassageRef$1 = {
|
|
|
24740
24844
|
major: 0,
|
|
24741
24845
|
minor: 1
|
|
24742
24846
|
},
|
|
24743
|
-
// TODO: things that aren't interactive shouldn't need
|
|
24744
|
-
|
|
24847
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
24848
|
+
scorer: () => scoreNoop()
|
|
24745
24849
|
};
|
|
24746
24850
|
|
|
24747
24851
|
/**
|
|
@@ -26109,7 +26213,7 @@ const styles$p = aphrodite.StyleSheet.create({
|
|
|
26109
26213
|
}
|
|
26110
26214
|
});
|
|
26111
26215
|
|
|
26112
|
-
function
|
|
26216
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
26113
26217
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26114
26218
|
return sum + (selected ? 1 : 0);
|
|
26115
26219
|
}, 0);
|
|
@@ -26119,10 +26223,10 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26119
26223
|
message: null
|
|
26120
26224
|
};
|
|
26121
26225
|
}
|
|
26122
|
-
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
if (
|
|
26226
|
+
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
26227
|
+
return currentChoice.correct ? sum + 1 : sum;
|
|
26228
|
+
}, 0);
|
|
26229
|
+
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
26126
26230
|
return {
|
|
26127
26231
|
type: "invalid",
|
|
26128
26232
|
message: strings.chooseCorrectNum
|
|
@@ -26130,9 +26234,8 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26130
26234
|
// If NOTA and some other answer are checked, ...
|
|
26131
26235
|
}
|
|
26132
26236
|
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
26237
|
+
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
26238
|
+
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
26136
26239
|
return {
|
|
26137
26240
|
type: "invalid",
|
|
26138
26241
|
message: strings.notNoneOfTheAbove
|
|
@@ -26140,9 +26243,7 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26140
26243
|
}
|
|
26141
26244
|
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
26142
26245
|
let isCorrect;
|
|
26143
|
-
|
|
26144
|
-
// instead of the userInput?
|
|
26145
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
26246
|
+
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
26146
26247
|
isCorrect = rubric.choices.every((choice, j) => {
|
|
26147
26248
|
return i === j || !choice.correct;
|
|
26148
26249
|
});
|
|
@@ -26174,31 +26275,19 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26174
26275
|
showSolutions: "none"
|
|
26175
26276
|
};
|
|
26176
26277
|
static getUserInputFromProps(props) {
|
|
26278
|
+
let unshuffle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
26177
26279
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
26178
26280
|
// future timeline implementers: this used to be {value: i} before
|
|
26179
26281
|
// multiple select was added)
|
|
26180
26282
|
if (props.choiceStates) {
|
|
26181
|
-
let noneOfTheAboveIndex = null;
|
|
26182
|
-
let noneOfTheAboveSelected = false;
|
|
26183
26283
|
const choiceStates = props.choiceStates;
|
|
26184
26284
|
const choicesSelected = choiceStates.map(() => false);
|
|
26185
|
-
const numCorrect = props.numCorrect;
|
|
26186
26285
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26187
|
-
const index = props.choices[i].originalIndex;
|
|
26286
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26188
26287
|
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
26288
|
}
|
|
26197
26289
|
return {
|
|
26198
|
-
choicesSelected
|
|
26199
|
-
numCorrect,
|
|
26200
|
-
noneOfTheAboveIndex,
|
|
26201
|
-
noneOfTheAboveSelected
|
|
26290
|
+
choicesSelected
|
|
26202
26291
|
};
|
|
26203
26292
|
// Support legacy choiceState implementation
|
|
26204
26293
|
}
|
|
@@ -26207,27 +26296,14 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26207
26296
|
values
|
|
26208
26297
|
} = props;
|
|
26209
26298
|
if (values) {
|
|
26210
|
-
let noneOfTheAboveIndex = null;
|
|
26211
|
-
let noneOfTheAboveSelected = false;
|
|
26212
26299
|
const choicesSelected = [...values];
|
|
26213
|
-
const numCorrect = props.numCorrect;
|
|
26214
26300
|
const valuesLength = values.length;
|
|
26215
26301
|
for (let i = 0; i < valuesLength; i++) {
|
|
26216
|
-
const index = props.choices[i].originalIndex;
|
|
26302
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26217
26303
|
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
26304
|
}
|
|
26226
26305
|
return {
|
|
26227
|
-
choicesSelected
|
|
26228
|
-
noneOfTheAboveIndex,
|
|
26229
|
-
noneOfTheAboveSelected,
|
|
26230
|
-
numCorrect
|
|
26306
|
+
choicesSelected
|
|
26231
26307
|
};
|
|
26232
26308
|
}
|
|
26233
26309
|
// Nothing checked
|
|
@@ -26355,6 +26431,10 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26355
26431
|
getUserInput() {
|
|
26356
26432
|
return Radio$1.getUserInputFromProps(this.props);
|
|
26357
26433
|
}
|
|
26434
|
+
getPromptJSON() {
|
|
26435
|
+
const userInput = Radio$1.getUserInputFromProps(this.props, false);
|
|
26436
|
+
return getPromptJSON$q(this.props, userInput);
|
|
26437
|
+
}
|
|
26358
26438
|
|
|
26359
26439
|
/**
|
|
26360
26440
|
* Turn on rationale display for the currently selected choices. Note that
|
|
@@ -26367,7 +26447,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26367
26447
|
choiceStates
|
|
26368
26448
|
} = this.props;
|
|
26369
26449
|
if (choiceStates) {
|
|
26370
|
-
const score =
|
|
26450
|
+
const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
|
|
26371
26451
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26372
26452
|
const newStates = choiceStates.map(state => ({
|
|
26373
26453
|
...state,
|
|
@@ -26649,13 +26729,26 @@ var Radio = {
|
|
|
26649
26729
|
},
|
|
26650
26730
|
propUpgrades: propUpgrades$1,
|
|
26651
26731
|
isLintable: true,
|
|
26652
|
-
|
|
26732
|
+
scorer: scoreRadio
|
|
26653
26733
|
};
|
|
26654
26734
|
|
|
26655
26735
|
/* eslint-disable import/no-named-as-default */
|
|
26656
26736
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
26657
26737
|
|
|
26658
|
-
|
|
26738
|
+
const getPromptJSON$n = (renderProps, userInput) => {
|
|
26739
|
+
return {
|
|
26740
|
+
type: "categorizer",
|
|
26741
|
+
options: {
|
|
26742
|
+
items: renderProps.items,
|
|
26743
|
+
categories: renderProps.categories
|
|
26744
|
+
},
|
|
26745
|
+
userInput: {
|
|
26746
|
+
itemToCategoryMapping: userInput.values
|
|
26747
|
+
}
|
|
26748
|
+
};
|
|
26749
|
+
};
|
|
26750
|
+
|
|
26751
|
+
function scoreCategorizer(userInput, rubric, strings) {
|
|
26659
26752
|
let completed = true;
|
|
26660
26753
|
let allCorrect = true;
|
|
26661
26754
|
rubric.values.forEach((value, i) => {
|
|
@@ -26710,6 +26803,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
26710
26803
|
getUserInput() {
|
|
26711
26804
|
return Categorizer.getUserInputFromProps(this.props);
|
|
26712
26805
|
}
|
|
26806
|
+
getPromptJSON() {
|
|
26807
|
+
return getPromptJSON$n(this.props, this.getUserInput());
|
|
26808
|
+
}
|
|
26713
26809
|
onChange(itemNum, catNum) {
|
|
26714
26810
|
const values = [...this.props.values];
|
|
26715
26811
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -26856,7 +26952,7 @@ var Categorizer$1 = {
|
|
|
26856
26952
|
return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
26857
26953
|
},
|
|
26858
26954
|
isLintable: true,
|
|
26859
|
-
|
|
26955
|
+
scorer: scoreCategorizer
|
|
26860
26956
|
};
|
|
26861
26957
|
|
|
26862
26958
|
/**
|
|
@@ -26906,7 +27002,20 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
26906
27002
|
*/
|
|
26907
27003
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
26908
27004
|
|
|
26909
|
-
function
|
|
27005
|
+
const getUnsupportedPromptJSON = function (widgetType) {
|
|
27006
|
+
let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
|
|
27007
|
+
return {
|
|
27008
|
+
type: widgetType,
|
|
27009
|
+
isSupported: false,
|
|
27010
|
+
message
|
|
27011
|
+
};
|
|
27012
|
+
};
|
|
27013
|
+
|
|
27014
|
+
const getPromptJSON$m = () => {
|
|
27015
|
+
return getUnsupportedPromptJSON("cs-program");
|
|
27016
|
+
};
|
|
27017
|
+
|
|
27018
|
+
function scoreCSProgram(state) {
|
|
26910
27019
|
// The iframe can tell us whether it's correct or incorrect,
|
|
26911
27020
|
// and pass an optional message
|
|
26912
27021
|
if (state.status === "correct") {
|
|
@@ -27002,6 +27111,9 @@ class CSProgram extends React__namespace.Component {
|
|
|
27002
27111
|
message: this.props.message
|
|
27003
27112
|
};
|
|
27004
27113
|
}
|
|
27114
|
+
getPromptJSON() {
|
|
27115
|
+
return getPromptJSON$m();
|
|
27116
|
+
}
|
|
27005
27117
|
render() {
|
|
27006
27118
|
if (!this.props.programID) {
|
|
27007
27119
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -27077,7 +27189,15 @@ var CSProgram$1 = {
|
|
|
27077
27189
|
supportedAlignments: ["block", "full-width"],
|
|
27078
27190
|
widget: CSProgram,
|
|
27079
27191
|
hidden: true,
|
|
27080
|
-
|
|
27192
|
+
scorer: scoreCSProgram
|
|
27193
|
+
};
|
|
27194
|
+
|
|
27195
|
+
const getPromptJSON$l = renderProps => {
|
|
27196
|
+
return {
|
|
27197
|
+
type: "definition",
|
|
27198
|
+
definition: renderProps.definition,
|
|
27199
|
+
togglePrompt: renderProps.togglePrompt
|
|
27200
|
+
};
|
|
27081
27201
|
};
|
|
27082
27202
|
|
|
27083
27203
|
class Definition extends React__namespace.Component {
|
|
@@ -27090,6 +27210,9 @@ class Definition extends React__namespace.Component {
|
|
|
27090
27210
|
// this just helps with TS weak typing when a Widget
|
|
27091
27211
|
// doesn't implement any Widget methods
|
|
27092
27212
|
isWidget = true;
|
|
27213
|
+
getPromptJSON() {
|
|
27214
|
+
return getPromptJSON$l(this.props);
|
|
27215
|
+
}
|
|
27093
27216
|
render() {
|
|
27094
27217
|
return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
|
|
27095
27218
|
let {
|
|
@@ -27146,8 +27269,8 @@ var Definition$1 = {
|
|
|
27146
27269
|
defaultAlignment: "inline",
|
|
27147
27270
|
widget: Definition,
|
|
27148
27271
|
transform: x => x,
|
|
27149
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27150
|
-
|
|
27272
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27273
|
+
scorer: () => scoreNoop()
|
|
27151
27274
|
};
|
|
27152
27275
|
|
|
27153
27276
|
// The props are type `any` on purpose so that this can receive props
|
|
@@ -27175,11 +27298,23 @@ var DeprecatedStandin$1 = {
|
|
|
27175
27298
|
displayName: "Deprecated Standin",
|
|
27176
27299
|
widget: DeprecatedStandin,
|
|
27177
27300
|
hidden: true,
|
|
27178
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27179
|
-
|
|
27301
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27302
|
+
scorer: () => scoreNoop(1)
|
|
27303
|
+
};
|
|
27304
|
+
|
|
27305
|
+
const getPromptJSON$k = (renderProps, userInput) => {
|
|
27306
|
+
return {
|
|
27307
|
+
type: "dropdown",
|
|
27308
|
+
options: {
|
|
27309
|
+
items: renderProps.choices
|
|
27310
|
+
},
|
|
27311
|
+
userInput: {
|
|
27312
|
+
selectedIndex: userInput.value - 1 // Offset to account for placeholder
|
|
27313
|
+
}
|
|
27314
|
+
};
|
|
27180
27315
|
};
|
|
27181
27316
|
|
|
27182
|
-
function
|
|
27317
|
+
function scoreDropdown(userInput, rubric) {
|
|
27183
27318
|
const selected = userInput.value;
|
|
27184
27319
|
if (selected === 0) {
|
|
27185
27320
|
return {
|
|
@@ -27225,6 +27360,9 @@ class Dropdown extends React__namespace.Component {
|
|
|
27225
27360
|
value: this.props.selected
|
|
27226
27361
|
};
|
|
27227
27362
|
}
|
|
27363
|
+
getPromptJSON() {
|
|
27364
|
+
return getPromptJSON$k(this.props, this.getUserInput());
|
|
27365
|
+
}
|
|
27228
27366
|
render() {
|
|
27229
27367
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
27230
27368
|
key: "placeholder",
|
|
@@ -27266,7 +27404,15 @@ var Dropdown$1 = {
|
|
|
27266
27404
|
accessible: true,
|
|
27267
27405
|
widget: Dropdown,
|
|
27268
27406
|
transform: optionsTransform,
|
|
27269
|
-
|
|
27407
|
+
scorer: scoreDropdown
|
|
27408
|
+
};
|
|
27409
|
+
|
|
27410
|
+
const getPromptJSON$j = renderProps => {
|
|
27411
|
+
return {
|
|
27412
|
+
type: "explanation",
|
|
27413
|
+
showPrompt: renderProps.showPrompt,
|
|
27414
|
+
explanation: renderProps.explanation
|
|
27415
|
+
};
|
|
27270
27416
|
};
|
|
27271
27417
|
|
|
27272
27418
|
// transform = _.identity
|
|
@@ -27315,6 +27461,9 @@ class Explanation extends React__namespace.Component {
|
|
|
27315
27461
|
});
|
|
27316
27462
|
this.props.trackInteraction();
|
|
27317
27463
|
};
|
|
27464
|
+
getPromptJSON() {
|
|
27465
|
+
return getPromptJSON$j(this.props);
|
|
27466
|
+
}
|
|
27318
27467
|
render() {
|
|
27319
27468
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
27320
27469
|
const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
|
|
@@ -27421,8 +27570,26 @@ var Explanation$1 = {
|
|
|
27421
27570
|
widget: Explanation,
|
|
27422
27571
|
transform: ___default["default"].identity,
|
|
27423
27572
|
isLintable: true,
|
|
27424
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27425
|
-
|
|
27573
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27574
|
+
scorer: () => scoreNoop()
|
|
27575
|
+
};
|
|
27576
|
+
|
|
27577
|
+
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
27578
|
+
if (!rendererJSON) {
|
|
27579
|
+
return {
|
|
27580
|
+
type: "graded-group",
|
|
27581
|
+
title,
|
|
27582
|
+
content: "",
|
|
27583
|
+
widgets: {},
|
|
27584
|
+
hint: hintRendererJSON
|
|
27585
|
+
};
|
|
27586
|
+
}
|
|
27587
|
+
return {
|
|
27588
|
+
...rendererJSON,
|
|
27589
|
+
title,
|
|
27590
|
+
type: "graded-group",
|
|
27591
|
+
hint: hintRendererJSON
|
|
27592
|
+
};
|
|
27426
27593
|
};
|
|
27427
27594
|
|
|
27428
27595
|
class GradedGroupAnswerBar extends React__namespace.Component {
|
|
@@ -27566,6 +27733,8 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27566
27733
|
message: "",
|
|
27567
27734
|
answerBarState: "HIDDEN"
|
|
27568
27735
|
};
|
|
27736
|
+
rendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27737
|
+
hintRendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27569
27738
|
shouldComponentUpdate(nextProps, nextState) {
|
|
27570
27739
|
return nextProps !== this.props || nextState !== this.state;
|
|
27571
27740
|
}
|
|
@@ -27591,13 +27760,9 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27591
27760
|
status: GRADING_STATUSES.ungraded,
|
|
27592
27761
|
message: ""
|
|
27593
27762
|
});
|
|
27594
|
-
|
|
27595
|
-
// eslint-disable-next-line react/no-string-refs
|
|
27596
|
-
if (this.refs.renderer) {
|
|
27763
|
+
if (this.rendererRef.current) {
|
|
27597
27764
|
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();
|
|
27765
|
+
const emptyWidgets = this.rendererRef.current.emptyWidgets();
|
|
27601
27766
|
const answerable = emptyWidgets.length === 0;
|
|
27602
27767
|
const answerBarState = this.state.answerBarState;
|
|
27603
27768
|
this.setState({
|
|
@@ -27606,12 +27771,10 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27606
27771
|
}
|
|
27607
27772
|
};
|
|
27608
27773
|
_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();
|
|
27774
|
+
this.rendererRef.current?.showRationalesForCurrentlySelectedChoices();
|
|
27775
|
+
const score = this.rendererRef.current?.score() || {
|
|
27776
|
+
type: "invalid"
|
|
27777
|
+
};
|
|
27615
27778
|
const {
|
|
27616
27779
|
INVALID_MESSAGE_PREFIX,
|
|
27617
27780
|
DEFAULT_INVALID_MESSAGE_1,
|
|
@@ -27632,29 +27795,28 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27632
27795
|
|
|
27633
27796
|
// Mobile API
|
|
27634
27797
|
getInputPaths = () => {
|
|
27635
|
-
|
|
27636
|
-
// @ts-expect-error - TS2339 - Property 'getInputPaths' does not exist on type 'ReactInstance'.
|
|
27637
|
-
return this.refs.renderer.getInputPaths();
|
|
27798
|
+
return this.rendererRef.current?.getInputPaths() || [];
|
|
27638
27799
|
};
|
|
27800
|
+
getPromptJSON() {
|
|
27801
|
+
// If the hint isn't expanded, we can't get the prompt JSON from the rendered widgets.
|
|
27802
|
+
// We'll just pass in the hint content as a string instead.
|
|
27803
|
+
const hint = this.hintRendererRef.current?.getPromptJSON() || {
|
|
27804
|
+
content: this.props.hint?.content || "",
|
|
27805
|
+
widgets: {}
|
|
27806
|
+
};
|
|
27807
|
+
return getPromptJSON$i(this.props.title, this.rendererRef.current?.getPromptJSON(), hint);
|
|
27808
|
+
}
|
|
27639
27809
|
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);
|
|
27810
|
+
return this.rendererRef.current?.setInputValue(path, newValue, cb);
|
|
27643
27811
|
};
|
|
27644
27812
|
focus = () => {
|
|
27645
|
-
|
|
27646
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
27647
|
-
return this.refs.renderer.focus();
|
|
27813
|
+
return !!this.rendererRef.current?.focus();
|
|
27648
27814
|
};
|
|
27649
27815
|
focusInputPath = path => {
|
|
27650
|
-
|
|
27651
|
-
// @ts-expect-error - TS2339 - Property 'focusPath' does not exist on type 'ReactInstance'.
|
|
27652
|
-
this.refs.renderer.focusPath(path);
|
|
27816
|
+
this.rendererRef.current?.focusPath(path);
|
|
27653
27817
|
};
|
|
27654
27818
|
blurInputPath = path => {
|
|
27655
|
-
|
|
27656
|
-
// @ts-expect-error - TS2339 - Property 'blurPath' does not exist on type 'ReactInstance'.
|
|
27657
|
-
this.refs.renderer.blurPath(path);
|
|
27819
|
+
this.rendererRef.current?.blurPath(path);
|
|
27658
27820
|
};
|
|
27659
27821
|
render() {
|
|
27660
27822
|
const apiOptions = ___default["default"].extend({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
@@ -27712,8 +27874,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27712
27874
|
}, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
|
|
27713
27875
|
className: aphrodite.css(styles$j.title)
|
|
27714
27876
|
}, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
|
|
27715
|
-
|
|
27716
|
-
ref: "renderer",
|
|
27877
|
+
ref: this.rendererRef,
|
|
27717
27878
|
apiOptions: {
|
|
27718
27879
|
...apiOptions,
|
|
27719
27880
|
readOnly
|
|
@@ -27756,8 +27917,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27756
27917
|
});
|
|
27757
27918
|
}
|
|
27758
27919
|
}, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
|
|
27759
|
-
|
|
27760
|
-
ref: "hints-renderer",
|
|
27920
|
+
ref: this.hintRendererRef,
|
|
27761
27921
|
apiOptions: apiOptions,
|
|
27762
27922
|
linterContext: this.props.linterContext,
|
|
27763
27923
|
strings: this.context.strings
|
|
@@ -27849,6 +28009,16 @@ var GradedGroup$1 = {
|
|
|
27849
28009
|
isLintable: true
|
|
27850
28010
|
};
|
|
27851
28011
|
|
|
28012
|
+
const getPromptJSON$h = (renderProps, activeGroupJSON) => {
|
|
28013
|
+
return {
|
|
28014
|
+
type: "graded-group-set",
|
|
28015
|
+
options: {
|
|
28016
|
+
groupCount: renderProps.gradedGroups.length,
|
|
28017
|
+
currentGroup: activeGroupJSON
|
|
28018
|
+
}
|
|
28019
|
+
};
|
|
28020
|
+
};
|
|
28021
|
+
|
|
27852
28022
|
class Indicators extends React__namespace.Component {
|
|
27853
28023
|
static contextType = PerseusI18nContext;
|
|
27854
28024
|
handleKeyDown = (e, i) => {
|
|
@@ -27927,6 +28097,10 @@ class GradedGroupSet extends React__namespace.Component {
|
|
|
27927
28097
|
getInputPaths = () => {
|
|
27928
28098
|
return this._childGroup.getInputPaths();
|
|
27929
28099
|
};
|
|
28100
|
+
getPromptJSON() {
|
|
28101
|
+
const activeGroupPromptJSON = this._childGroup.getPromptJSON();
|
|
28102
|
+
return getPromptJSON$h(this.props, activeGroupPromptJSON);
|
|
28103
|
+
}
|
|
27930
28104
|
setInputValue = (path, newValue, cb) => {
|
|
27931
28105
|
return this._childGroup.setInputValue(path, newValue, cb);
|
|
27932
28106
|
};
|
|
@@ -28180,6 +28354,33 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
28180
28354
|
}
|
|
28181
28355
|
});
|
|
28182
28356
|
|
|
28357
|
+
const getPromptJSON$g = (renderProps, userInput) => {
|
|
28358
|
+
const {
|
|
28359
|
+
type,
|
|
28360
|
+
coords
|
|
28361
|
+
} = userInput;
|
|
28362
|
+
const input = {
|
|
28363
|
+
type,
|
|
28364
|
+
coords
|
|
28365
|
+
};
|
|
28366
|
+
if (userInput.type === "logarithm" || userInput.type === "exponential") {
|
|
28367
|
+
input["asymptote"] = userInput.asymptote;
|
|
28368
|
+
}
|
|
28369
|
+
return {
|
|
28370
|
+
type: "grapher",
|
|
28371
|
+
options: {
|
|
28372
|
+
availableTypes: renderProps.availableTypes,
|
|
28373
|
+
range: renderProps.graph.range,
|
|
28374
|
+
labels: renderProps.graph.labels,
|
|
28375
|
+
tickStep: renderProps.graph.step,
|
|
28376
|
+
gridStep: renderProps.graph.gridStep,
|
|
28377
|
+
snapStep: renderProps.graph.snapStep,
|
|
28378
|
+
backgroundImageUrl: renderProps.graph.backgroundImage.url
|
|
28379
|
+
},
|
|
28380
|
+
userInput: input
|
|
28381
|
+
};
|
|
28382
|
+
};
|
|
28383
|
+
|
|
28183
28384
|
/* eslint-disable one-var */
|
|
28184
28385
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
28185
28386
|
const Plot$1 = Graphie.Plot;
|
|
@@ -28703,7 +28904,7 @@ function getCoefficientsByType(data) {
|
|
|
28703
28904
|
throw new perseusCore.PerseusError("Invalid grapher type", perseusCore.Errors.InvalidInput);
|
|
28704
28905
|
}
|
|
28705
28906
|
}
|
|
28706
|
-
function
|
|
28907
|
+
function scoreGrapher(userInput, rubric) {
|
|
28707
28908
|
if (userInput.type !== rubric.correct.type) {
|
|
28708
28909
|
return {
|
|
28709
28910
|
type: "points",
|
|
@@ -29054,6 +29255,9 @@ class Grapher extends React__namespace.Component {
|
|
|
29054
29255
|
getUserInput() {
|
|
29055
29256
|
return Grapher.getUserInputFromProps(this.props);
|
|
29056
29257
|
}
|
|
29258
|
+
getPromptJSON() {
|
|
29259
|
+
return getPromptJSON$g(this.props, this.getUserInput());
|
|
29260
|
+
}
|
|
29057
29261
|
render() {
|
|
29058
29262
|
const type = this.props.plot.type;
|
|
29059
29263
|
const coords = this.props.plot.coords;
|
|
@@ -29143,7 +29347,21 @@ var Grapher$1 = {
|
|
|
29143
29347
|
widget: Grapher,
|
|
29144
29348
|
transform: propTransform$2,
|
|
29145
29349
|
staticTransform: staticTransform$4,
|
|
29146
|
-
|
|
29350
|
+
scorer: scoreGrapher
|
|
29351
|
+
};
|
|
29352
|
+
|
|
29353
|
+
const getPromptJSON$f = rendererJSON => {
|
|
29354
|
+
if (!rendererJSON) {
|
|
29355
|
+
return {
|
|
29356
|
+
type: "group",
|
|
29357
|
+
content: "",
|
|
29358
|
+
widgets: {}
|
|
29359
|
+
};
|
|
29360
|
+
}
|
|
29361
|
+
return {
|
|
29362
|
+
...rendererJSON,
|
|
29363
|
+
type: "group"
|
|
29364
|
+
};
|
|
29147
29365
|
};
|
|
29148
29366
|
|
|
29149
29367
|
// exports has no 'transform'
|
|
@@ -29182,6 +29400,9 @@ class Group extends React__namespace.Component {
|
|
|
29182
29400
|
getUserInput() {
|
|
29183
29401
|
return this.rendererRef?.getUserInput();
|
|
29184
29402
|
}
|
|
29403
|
+
getPromptJSON() {
|
|
29404
|
+
return getPromptJSON$f(this.rendererRef?.getPromptJSON());
|
|
29405
|
+
}
|
|
29185
29406
|
getSerializedState = () => {
|
|
29186
29407
|
return this.rendererRef?.getSerializedState();
|
|
29187
29408
|
};
|
|
@@ -29295,7 +29516,11 @@ var Group$1 = {
|
|
|
29295
29516
|
isLintable: true
|
|
29296
29517
|
};
|
|
29297
29518
|
|
|
29298
|
-
|
|
29519
|
+
const getPromptJSON$e = () => {
|
|
29520
|
+
return getUnsupportedPromptJSON("iframe");
|
|
29521
|
+
};
|
|
29522
|
+
|
|
29523
|
+
function scoreIframe(userInput) {
|
|
29299
29524
|
// The iframe can tell us whether it's correct or incorrect,
|
|
29300
29525
|
// and pass an optional message
|
|
29301
29526
|
if (userInput.status === "correct") {
|
|
@@ -29352,6 +29577,9 @@ class Iframe extends React__namespace.Component {
|
|
|
29352
29577
|
message: this.props.message
|
|
29353
29578
|
};
|
|
29354
29579
|
}
|
|
29580
|
+
getPromptJSON() {
|
|
29581
|
+
return getPromptJSON$e();
|
|
29582
|
+
}
|
|
29355
29583
|
handleMessageEvent = e => {
|
|
29356
29584
|
// We receive data from the iframe that contains {passed: true/false}
|
|
29357
29585
|
// and use that to set the status
|
|
@@ -29449,7 +29677,19 @@ var Iframe$1 = {
|
|
|
29449
29677
|
widget: Iframe,
|
|
29450
29678
|
// Let's not expose it to all content creators yet
|
|
29451
29679
|
hidden: true,
|
|
29452
|
-
|
|
29680
|
+
scorer: scoreIframe
|
|
29681
|
+
};
|
|
29682
|
+
|
|
29683
|
+
const getPromptJSON$d = renderProps => {
|
|
29684
|
+
return {
|
|
29685
|
+
type: "image",
|
|
29686
|
+
options: {
|
|
29687
|
+
altText: renderProps.alt,
|
|
29688
|
+
title: renderProps.title,
|
|
29689
|
+
caption: renderProps.caption,
|
|
29690
|
+
imageUrl: renderProps.backgroundImage.url
|
|
29691
|
+
}
|
|
29692
|
+
};
|
|
29453
29693
|
};
|
|
29454
29694
|
|
|
29455
29695
|
const defaultBoxSize = 400;
|
|
@@ -29490,6 +29730,9 @@ class ImageWidget extends React__namespace.Component {
|
|
|
29490
29730
|
return change.apply(_this, args);
|
|
29491
29731
|
};
|
|
29492
29732
|
})();
|
|
29733
|
+
getPromptJSON() {
|
|
29734
|
+
return getPromptJSON$d(this.props);
|
|
29735
|
+
}
|
|
29493
29736
|
render() {
|
|
29494
29737
|
let image;
|
|
29495
29738
|
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
@@ -29636,8 +29879,12 @@ var Image$1 = {
|
|
|
29636
29879
|
displayName: "Image",
|
|
29637
29880
|
widget: ImageWidget,
|
|
29638
29881
|
isLintable: true,
|
|
29639
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29640
|
-
|
|
29882
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
29883
|
+
scorer: () => scoreNoop()
|
|
29884
|
+
};
|
|
29885
|
+
|
|
29886
|
+
const getPromptJSON$c = () => {
|
|
29887
|
+
return getUnsupportedPromptJSON("interaction");
|
|
29641
29888
|
};
|
|
29642
29889
|
|
|
29643
29890
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -29826,6 +30073,9 @@ class Interaction extends React__namespace.Component {
|
|
|
29826
30073
|
return change.apply(_this, args);
|
|
29827
30074
|
};
|
|
29828
30075
|
})();
|
|
30076
|
+
getPromptJSON() {
|
|
30077
|
+
return getPromptJSON$c();
|
|
30078
|
+
}
|
|
29829
30079
|
render() {
|
|
29830
30080
|
const range = this.props.graph.range;
|
|
29831
30081
|
let labels = this.props.graph.labels;
|
|
@@ -30234,8 +30484,8 @@ var Interactive = {
|
|
|
30234
30484
|
widget: Interaction,
|
|
30235
30485
|
transform: ___default["default"].identity,
|
|
30236
30486
|
hidden: true,
|
|
30237
|
-
// TODO: things that aren't interactive shouldn't need
|
|
30238
|
-
|
|
30487
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
30488
|
+
scorer: () => scoreNoop()
|
|
30239
30489
|
};
|
|
30240
30490
|
|
|
30241
30491
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -30539,6 +30789,137 @@ class Graph extends React__namespace.Component {
|
|
|
30539
30789
|
}
|
|
30540
30790
|
}
|
|
30541
30791
|
|
|
30792
|
+
const getPromptJSON$b = (props, userInput) => {
|
|
30793
|
+
return {
|
|
30794
|
+
type: "interactive-graph",
|
|
30795
|
+
options: {
|
|
30796
|
+
graph: getGraphOptionsForProps(props),
|
|
30797
|
+
backgroundImageUrl: props.backgroundImage?.url,
|
|
30798
|
+
range: props.range,
|
|
30799
|
+
labels: props.labels
|
|
30800
|
+
},
|
|
30801
|
+
userInput: getUserInput(userInput)
|
|
30802
|
+
};
|
|
30803
|
+
};
|
|
30804
|
+
const getGraphOptionsForProps = props => {
|
|
30805
|
+
const type = props.graph.type;
|
|
30806
|
+
switch (type) {
|
|
30807
|
+
case "angle":
|
|
30808
|
+
return {
|
|
30809
|
+
type: props.graph.type,
|
|
30810
|
+
angleOffsetDegrees: props.graph.angleOffsetDeg,
|
|
30811
|
+
startCoords: props.graph.startCoords
|
|
30812
|
+
};
|
|
30813
|
+
case "circle":
|
|
30814
|
+
return {
|
|
30815
|
+
type: props.graph.type,
|
|
30816
|
+
startParams: {
|
|
30817
|
+
center: props.graph.startCoords?.center,
|
|
30818
|
+
radius: props.graph.startCoords?.radius
|
|
30819
|
+
}
|
|
30820
|
+
};
|
|
30821
|
+
case "linear":
|
|
30822
|
+
return {
|
|
30823
|
+
type: props.graph.type,
|
|
30824
|
+
startCoords: props.graph.startCoords
|
|
30825
|
+
};
|
|
30826
|
+
case "linear-system":
|
|
30827
|
+
return {
|
|
30828
|
+
type: props.graph.type,
|
|
30829
|
+
startCoords: props.graph.startCoords
|
|
30830
|
+
};
|
|
30831
|
+
case "point":
|
|
30832
|
+
return {
|
|
30833
|
+
type: props.graph.type,
|
|
30834
|
+
numPoints: props.graph.numPoints,
|
|
30835
|
+
startCoords: props.graph.startCoords
|
|
30836
|
+
};
|
|
30837
|
+
case "polygon":
|
|
30838
|
+
return {
|
|
30839
|
+
type: props.graph.type,
|
|
30840
|
+
match: props.graph.match,
|
|
30841
|
+
numSides: props.graph.numSides,
|
|
30842
|
+
startCoords: props.graph.startCoords
|
|
30843
|
+
};
|
|
30844
|
+
case "quadratic":
|
|
30845
|
+
return {
|
|
30846
|
+
type: props.graph.type,
|
|
30847
|
+
startCoords: props.graph.startCoords
|
|
30848
|
+
};
|
|
30849
|
+
case "ray":
|
|
30850
|
+
return {
|
|
30851
|
+
type: props.graph.type,
|
|
30852
|
+
startCoords: props.graph.startCoords
|
|
30853
|
+
};
|
|
30854
|
+
case "segment":
|
|
30855
|
+
return {
|
|
30856
|
+
type: props.graph.type,
|
|
30857
|
+
numSegments: props.graph.numSegments,
|
|
30858
|
+
startCoords: props.graph.startCoords
|
|
30859
|
+
};
|
|
30860
|
+
case "sinusoid":
|
|
30861
|
+
return {
|
|
30862
|
+
type: props.graph.type,
|
|
30863
|
+
startCoords: props.graph.startCoords
|
|
30864
|
+
};
|
|
30865
|
+
case "none":
|
|
30866
|
+
return {};
|
|
30867
|
+
default:
|
|
30868
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30869
|
+
}
|
|
30870
|
+
};
|
|
30871
|
+
const getUserInput = userInput => {
|
|
30872
|
+
const type = userInput.type;
|
|
30873
|
+
switch (type) {
|
|
30874
|
+
case "angle":
|
|
30875
|
+
return {
|
|
30876
|
+
coords: userInput.coords,
|
|
30877
|
+
angleOffsetDegrees: userInput.angleOffsetDeg
|
|
30878
|
+
};
|
|
30879
|
+
case "circle":
|
|
30880
|
+
return {
|
|
30881
|
+
center: userInput.center,
|
|
30882
|
+
radius: userInput.radius
|
|
30883
|
+
};
|
|
30884
|
+
case "linear":
|
|
30885
|
+
return {
|
|
30886
|
+
coords: userInput.coords
|
|
30887
|
+
};
|
|
30888
|
+
case "linear-system":
|
|
30889
|
+
return {
|
|
30890
|
+
coords: userInput.coords
|
|
30891
|
+
};
|
|
30892
|
+
case "point":
|
|
30893
|
+
return {
|
|
30894
|
+
coords: userInput.coords
|
|
30895
|
+
};
|
|
30896
|
+
case "polygon":
|
|
30897
|
+
return {
|
|
30898
|
+
coords: userInput.coords
|
|
30899
|
+
};
|
|
30900
|
+
case "quadratic":
|
|
30901
|
+
return {
|
|
30902
|
+
coords: userInput.coords
|
|
30903
|
+
};
|
|
30904
|
+
case "ray":
|
|
30905
|
+
return {
|
|
30906
|
+
coords: userInput.coords
|
|
30907
|
+
};
|
|
30908
|
+
case "segment":
|
|
30909
|
+
return {
|
|
30910
|
+
coords: userInput.coords
|
|
30911
|
+
};
|
|
30912
|
+
case "sinusoid":
|
|
30913
|
+
return {
|
|
30914
|
+
coords: userInput.coords
|
|
30915
|
+
};
|
|
30916
|
+
case "none":
|
|
30917
|
+
return {};
|
|
30918
|
+
default:
|
|
30919
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30920
|
+
}
|
|
30921
|
+
};
|
|
30922
|
+
|
|
30542
30923
|
function pathBuilder() {
|
|
30543
30924
|
return new PathBuilder();
|
|
30544
30925
|
}
|
|
@@ -31790,6 +32171,10 @@ const LegacyGrid = _ref => {
|
|
|
31790
32171
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
31791
32172
|
* area.
|
|
31792
32173
|
*/
|
|
32174
|
+
/**
|
|
32175
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32176
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32177
|
+
*/
|
|
31793
32178
|
/**
|
|
31794
32179
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
31795
32180
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
@@ -31886,6 +32271,10 @@ const ItemExtras = [
|
|
|
31886
32271
|
// A background image applied to various widgets.
|
|
31887
32272
|
|
|
31888
32273
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32274
|
+
|
|
32275
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
32276
|
+
|
|
32277
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
31889
32278
|
const lockedFigureColors = {
|
|
31890
32279
|
blue: "#3D7586",
|
|
31891
32280
|
green: "#447A53",
|
|
@@ -31953,17 +32342,61 @@ function isUnlimitedGraphState(state) {
|
|
|
31953
32342
|
* and the text outside of the $ blocks will be non-TeX text.
|
|
31954
32343
|
*/
|
|
31955
32344
|
function replaceOutsideTeX(mathString) {
|
|
31956
|
-
|
|
32345
|
+
// All the information we need is in the first section,
|
|
32346
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
32347
|
+
const firstSection = SimpleMarkdown__namespace.parse(mathString)[0];
|
|
32348
|
+
|
|
32349
|
+
// If it's blockMath, the outer level has the full math content.
|
|
32350
|
+
if (firstSection.type === "blockMath") {
|
|
32351
|
+
return firstSection.content;
|
|
32352
|
+
}
|
|
32353
|
+
|
|
32354
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
32355
|
+
// to look for individual math blocks.
|
|
32356
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31957
32357
|
let result = "";
|
|
31958
|
-
const
|
|
31959
|
-
|
|
31960
|
-
|
|
31961
|
-
|
|
31962
|
-
|
|
32358
|
+
for (const piece of condensedNodes) {
|
|
32359
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
32360
|
+
}
|
|
32361
|
+
return result;
|
|
32362
|
+
}
|
|
32363
|
+
// Helper function for replaceOutsideTeX()
|
|
32364
|
+
// Condense adjacent text nodes into a single text node
|
|
32365
|
+
function condenseTextNodes(nodes) {
|
|
32366
|
+
const result = [];
|
|
32367
|
+
if (!nodes) {
|
|
32368
|
+
return result;
|
|
32369
|
+
}
|
|
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
|
+
});
|
|
31963
32390
|
}
|
|
31964
32391
|
return result;
|
|
31965
32392
|
}
|
|
31966
32393
|
|
|
32394
|
+
// Helper function for replaceOutsideTeX()
|
|
32395
|
+
function escapeSpecialChars(str) {
|
|
32396
|
+
// Escape $, \, {, and } characters
|
|
32397
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32398
|
+
}
|
|
32399
|
+
|
|
31967
32400
|
function LockedLabel(props) {
|
|
31968
32401
|
const {
|
|
31969
32402
|
coord,
|
|
@@ -36298,7 +36731,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
36298
36731
|
|
|
36299
36732
|
const eq$1 = Util.eq;
|
|
36300
36733
|
const deepEq$2 = Util.deepEq;
|
|
36301
|
-
function
|
|
36734
|
+
function scoreInteractiveGraph(userInput, rubric) {
|
|
36302
36735
|
// None-type graphs are not graded
|
|
36303
36736
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
36304
36737
|
return {
|
|
@@ -37796,6 +38229,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
37796
38229
|
}
|
|
37797
38230
|
throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
|
|
37798
38231
|
}
|
|
38232
|
+
getPromptJSON() {
|
|
38233
|
+
return getPromptJSON$b(this.props, this.getUserInput());
|
|
38234
|
+
}
|
|
37799
38235
|
render() {
|
|
37800
38236
|
// Mafs shim
|
|
37801
38237
|
const mafsFlags = this.props.apiOptions?.flags?.["mafs"];
|
|
@@ -38160,7 +38596,7 @@ var InteractiveGraph$1 = {
|
|
|
38160
38596
|
displayName: "Interactive graph (Assessments only)",
|
|
38161
38597
|
widget: InteractiveGraph,
|
|
38162
38598
|
staticTransform: staticTransform$3,
|
|
38163
|
-
|
|
38599
|
+
scorer: scoreInteractiveGraph
|
|
38164
38600
|
};
|
|
38165
38601
|
|
|
38166
38602
|
// TODO: can we use WB typography?
|
|
@@ -38172,6 +38608,32 @@ const bodyXsmallBold = {
|
|
|
38172
38608
|
lineHeight: "22px"
|
|
38173
38609
|
};
|
|
38174
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
|
+
|
|
38175
38637
|
const AnswerChoices = props => {
|
|
38176
38638
|
const {
|
|
38177
38639
|
strings
|
|
@@ -38257,59 +38719,6 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38257
38719
|
}
|
|
38258
38720
|
});
|
|
38259
38721
|
|
|
38260
|
-
// Question state for marker as result of user selected answers.
|
|
38261
|
-
|
|
38262
|
-
function scoreMarker(marker) {
|
|
38263
|
-
const score = {
|
|
38264
|
-
hasAnswers: false,
|
|
38265
|
-
isCorrect: false
|
|
38266
|
-
};
|
|
38267
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
38268
|
-
score.hasAnswers = true;
|
|
38269
|
-
}
|
|
38270
|
-
if (marker.answers.length > 0) {
|
|
38271
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38272
|
-
// All correct answers are selected by the user.
|
|
38273
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38274
|
-
}
|
|
38275
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38276
|
-
// Correct as no answers should be selected by the user.
|
|
38277
|
-
score.isCorrect = true;
|
|
38278
|
-
}
|
|
38279
|
-
return score;
|
|
38280
|
-
}
|
|
38281
|
-
|
|
38282
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38283
|
-
function labelImageValidator(userInput, rubric) {
|
|
38284
|
-
let numAnswered = 0;
|
|
38285
|
-
let numCorrect = 0;
|
|
38286
|
-
for (const marker of userInput.markers) {
|
|
38287
|
-
const score = scoreMarker(marker);
|
|
38288
|
-
if (score.hasAnswers) {
|
|
38289
|
-
numAnswered++;
|
|
38290
|
-
}
|
|
38291
|
-
if (score.isCorrect) {
|
|
38292
|
-
numCorrect++;
|
|
38293
|
-
}
|
|
38294
|
-
}
|
|
38295
|
-
|
|
38296
|
-
// We expect all question markers to be answered before grading.
|
|
38297
|
-
if (numAnswered !== userInput.markers.length) {
|
|
38298
|
-
return {
|
|
38299
|
-
type: "invalid",
|
|
38300
|
-
message: null
|
|
38301
|
-
};
|
|
38302
|
-
}
|
|
38303
|
-
return {
|
|
38304
|
-
type: "points",
|
|
38305
|
-
// Markers with no expected answers are graded as correct if user
|
|
38306
|
-
// makes no answer selection.
|
|
38307
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38308
|
-
total: 1,
|
|
38309
|
-
message: null
|
|
38310
|
-
};
|
|
38311
|
-
}
|
|
38312
|
-
|
|
38313
38722
|
const BringToFront = {
|
|
38314
38723
|
boxShadow: `0 8px 8px ${wonderBlocksTokens.color.offBlack64}`,
|
|
38315
38724
|
zIndex: 1000
|
|
@@ -38565,6 +38974,59 @@ const styles$b = aphrodite.StyleSheet.create({
|
|
|
38565
38974
|
}
|
|
38566
38975
|
});
|
|
38567
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
|
+
|
|
38568
39030
|
/**
|
|
38569
39031
|
* Represents a direction vector.
|
|
38570
39032
|
* No diagonals allowed.
|
|
@@ -38775,6 +39237,9 @@ class LabelImage extends React__namespace.Component {
|
|
|
38775
39237
|
markers
|
|
38776
39238
|
};
|
|
38777
39239
|
}
|
|
39240
|
+
getPromptJSON() {
|
|
39241
|
+
return getPromptJSON$a(this.props, this.getUserInput());
|
|
39242
|
+
}
|
|
38778
39243
|
|
|
38779
39244
|
// TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
|
|
38780
39245
|
showRationalesForCurrentlySelectedChoices(rubric) {
|
|
@@ -39140,7 +39605,7 @@ var LabelImage$1 = {
|
|
|
39140
39605
|
widget: LabelImageWithDependencies,
|
|
39141
39606
|
accessible: true,
|
|
39142
39607
|
isLintable: true,
|
|
39143
|
-
|
|
39608
|
+
scorer: scoreLabelImage
|
|
39144
39609
|
};
|
|
39145
39610
|
|
|
39146
39611
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -39850,7 +40315,23 @@ const styles$9 = aphrodite.StyleSheet.create({
|
|
|
39850
40315
|
}
|
|
39851
40316
|
});
|
|
39852
40317
|
|
|
39853
|
-
|
|
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) {
|
|
39854
40335
|
const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
|
|
39855
40336
|
return {
|
|
39856
40337
|
type: "points",
|
|
@@ -39917,6 +40398,9 @@ class Matcher extends React__namespace.Component {
|
|
|
39917
40398
|
right: this.refs.right.getOptions()
|
|
39918
40399
|
};
|
|
39919
40400
|
};
|
|
40401
|
+
getPromptJSON() {
|
|
40402
|
+
return getPromptJSON$9(this.props, this.getUserInput());
|
|
40403
|
+
}
|
|
39920
40404
|
|
|
39921
40405
|
// Programatic API for moving options
|
|
39922
40406
|
// This is used by testing
|
|
@@ -40060,10 +40544,23 @@ var Matcher$1 = {
|
|
|
40060
40544
|
displayName: "Matcher (two column)",
|
|
40061
40545
|
widget: Matcher,
|
|
40062
40546
|
isLintable: true,
|
|
40063
|
-
|
|
40547
|
+
scorer: scoreMatcher
|
|
40064
40548
|
};
|
|
40065
40549
|
|
|
40066
|
-
|
|
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
|
+
};
|
|
40561
|
+
};
|
|
40562
|
+
|
|
40563
|
+
function scoreMatrix(userInput, rubric, strings) {
|
|
40067
40564
|
const solution = rubric.answers;
|
|
40068
40565
|
const supplied = userInput.answers;
|
|
40069
40566
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -40340,6 +40837,9 @@ class Matrix extends React__namespace.Component {
|
|
|
40340
40837
|
answers: this.props.answers
|
|
40341
40838
|
};
|
|
40342
40839
|
}
|
|
40840
|
+
getPromptJSON() {
|
|
40841
|
+
return getPromptJSON$8(this.props, this.getUserInput());
|
|
40842
|
+
}
|
|
40343
40843
|
render() {
|
|
40344
40844
|
// Set the input sizes through JS so we can control the size of the
|
|
40345
40845
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40525,7 +41025,11 @@ var Matrix$1 = {
|
|
|
40525
41025
|
transform: propTransform$1,
|
|
40526
41026
|
staticTransform: staticTransform$2,
|
|
40527
41027
|
isLintable: true,
|
|
40528
|
-
|
|
41028
|
+
scorer: scoreMatrix
|
|
41029
|
+
};
|
|
41030
|
+
|
|
41031
|
+
const getPromptJSON$7 = () => {
|
|
41032
|
+
return getUnsupportedPromptJSON("measurer");
|
|
40529
41033
|
};
|
|
40530
41034
|
|
|
40531
41035
|
const defaultImage = {
|
|
@@ -40602,6 +41106,9 @@ class Measurer extends React__namespace.Component {
|
|
|
40602
41106
|
});
|
|
40603
41107
|
}
|
|
40604
41108
|
}
|
|
41109
|
+
getPromptJSON() {
|
|
41110
|
+
return getPromptJSON$7();
|
|
41111
|
+
}
|
|
40605
41112
|
render() {
|
|
40606
41113
|
const image = ___default["default"].extend({}, defaultImage, this.props.image);
|
|
40607
41114
|
|
|
@@ -40649,8 +41156,8 @@ var Measurer$1 = {
|
|
|
40649
41156
|
minor: 0
|
|
40650
41157
|
},
|
|
40651
41158
|
propUpgrades: propUpgrades,
|
|
40652
|
-
// TODO: things that aren't interactive shouldn't need
|
|
40653
|
-
|
|
41159
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41160
|
+
scorer: () => scoreNoop(1)
|
|
40654
41161
|
};
|
|
40655
41162
|
|
|
40656
41163
|
/**
|
|
@@ -41539,10 +42046,25 @@ var Molecule$1 = {
|
|
|
41539
42046
|
displayName: "Molecule renderer",
|
|
41540
42047
|
hidden: true,
|
|
41541
42048
|
widget: MoleculeWidget,
|
|
41542
|
-
|
|
42049
|
+
scorer: () => scoreNoop()
|
|
42050
|
+
};
|
|
42051
|
+
|
|
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
|
+
};
|
|
41543
42065
|
};
|
|
41544
42066
|
|
|
41545
|
-
function
|
|
42067
|
+
function scoreNumberLine(state, rubric) {
|
|
41546
42068
|
const range = rubric.range;
|
|
41547
42069
|
const divisionRange = state.divisionRange;
|
|
41548
42070
|
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
@@ -42031,6 +42553,9 @@ class NumberLine extends React__namespace.Component {
|
|
|
42031
42553
|
divisionRange: this.props.divisionRange
|
|
42032
42554
|
};
|
|
42033
42555
|
}
|
|
42556
|
+
getPromptJSON() {
|
|
42557
|
+
return getPromptJSON$6(this.props, this.getUserInput());
|
|
42558
|
+
}
|
|
42034
42559
|
render() {
|
|
42035
42560
|
const {
|
|
42036
42561
|
strings
|
|
@@ -42135,10 +42660,22 @@ var NumberLine$1 = {
|
|
|
42135
42660
|
widget: NumberLine,
|
|
42136
42661
|
transform: numberLineTransform,
|
|
42137
42662
|
staticTransform: staticTransform$1,
|
|
42138
|
-
|
|
42663
|
+
scorer: scoreNumberLine
|
|
42139
42664
|
};
|
|
42140
42665
|
|
|
42141
|
-
|
|
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
|
+
};
|
|
42676
|
+
};
|
|
42677
|
+
|
|
42678
|
+
function scoreOrderer(userInput, rubric) {
|
|
42142
42679
|
if (userInput.current.length === 0) {
|
|
42143
42680
|
return {
|
|
42144
42681
|
type: "invalid",
|
|
@@ -42584,6 +43121,9 @@ class Orderer extends React__namespace.Component {
|
|
|
42584
43121
|
})
|
|
42585
43122
|
};
|
|
42586
43123
|
}
|
|
43124
|
+
getPromptJSON() {
|
|
43125
|
+
return getPromptJSON$5(this.props, this.getUserInput());
|
|
43126
|
+
}
|
|
42587
43127
|
render() {
|
|
42588
43128
|
// This is the card we are currently dragging
|
|
42589
43129
|
const dragging = this.state.dragging && /*#__PURE__*/React__namespace.createElement(Card
|
|
@@ -42694,7 +43234,7 @@ var Orderer$1 = {
|
|
|
42694
43234
|
hidden: true,
|
|
42695
43235
|
widget: Orderer,
|
|
42696
43236
|
isLintable: true,
|
|
42697
|
-
|
|
43237
|
+
scorer: scoreOrderer
|
|
42698
43238
|
};
|
|
42699
43239
|
|
|
42700
43240
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -42748,8 +43288,12 @@ var PassageRefTarget$1 = {
|
|
|
42748
43288
|
minor: 0
|
|
42749
43289
|
},
|
|
42750
43290
|
isLintable: true,
|
|
42751
|
-
// TODO: things that aren't interactive shouldn't need
|
|
42752
|
-
|
|
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");
|
|
42753
43297
|
};
|
|
42754
43298
|
|
|
42755
43299
|
/**
|
|
@@ -42801,6 +43345,9 @@ class PhetSimulation extends React__namespace.Component {
|
|
|
42801
43345
|
return kaLocale;
|
|
42802
43346
|
}
|
|
42803
43347
|
};
|
|
43348
|
+
getPromptJSON() {
|
|
43349
|
+
return getPromptJSON$4();
|
|
43350
|
+
}
|
|
42804
43351
|
displayLoadFailure = () => {
|
|
42805
43352
|
this.setState({
|
|
42806
43353
|
url: null,
|
|
@@ -42955,10 +43502,14 @@ var PhetSimulation$1 = {
|
|
|
42955
43502
|
isLintable: true
|
|
42956
43503
|
};
|
|
42957
43504
|
|
|
43505
|
+
const getPromptJSON$3 = () => {
|
|
43506
|
+
return getUnsupportedPromptJSON("plotter");
|
|
43507
|
+
};
|
|
43508
|
+
|
|
42958
43509
|
const {
|
|
42959
43510
|
deepEq: deepEq$1
|
|
42960
43511
|
} = Util;
|
|
42961
|
-
function
|
|
43512
|
+
function scorePlotter(userInput, rubric) {
|
|
42962
43513
|
if (deepEq$1(userInput, rubric.starting)) {
|
|
42963
43514
|
return {
|
|
42964
43515
|
type: "invalid",
|
|
@@ -43746,6 +44297,9 @@ class Plotter extends React__namespace.Component {
|
|
|
43746
44297
|
getUserInput() {
|
|
43747
44298
|
return this.state.values;
|
|
43748
44299
|
}
|
|
44300
|
+
getPromptJSON() {
|
|
44301
|
+
return getPromptJSON$3();
|
|
44302
|
+
}
|
|
43749
44303
|
render() {
|
|
43750
44304
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
43751
44305
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -43772,7 +44326,11 @@ var Plotter$1 = {
|
|
|
43772
44326
|
hidden: true,
|
|
43773
44327
|
widget: Plotter,
|
|
43774
44328
|
staticTransform: staticTransform,
|
|
43775
|
-
|
|
44329
|
+
scorer: scorePlotter
|
|
44330
|
+
};
|
|
44331
|
+
|
|
44332
|
+
const getPromptJSON$2 = () => {
|
|
44333
|
+
return getUnsupportedPromptJSON("python-program");
|
|
43776
44334
|
};
|
|
43777
44335
|
|
|
43778
44336
|
/**
|
|
@@ -43798,6 +44356,9 @@ class PythonProgram extends React__namespace.Component {
|
|
|
43798
44356
|
getDOMNodeForPath(path) {
|
|
43799
44357
|
return null;
|
|
43800
44358
|
}
|
|
44359
|
+
getPromptJSON() {
|
|
44360
|
+
return getPromptJSON$2();
|
|
44361
|
+
}
|
|
43801
44362
|
render() {
|
|
43802
44363
|
const url = getUrlFromProgramID(this.props.programID);
|
|
43803
44364
|
const iframeStyle = {
|
|
@@ -43832,7 +44393,17 @@ var PythonProgram$1 = {
|
|
|
43832
44393
|
widget: PythonProgram
|
|
43833
44394
|
};
|
|
43834
44395
|
|
|
43835
|
-
|
|
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) {
|
|
43836
44407
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43837
44408
|
// prevents the "Check" button from becoming active. We want the user
|
|
43838
44409
|
// to make a change before trying to move forward. This makes an
|
|
@@ -43904,6 +44475,9 @@ class Sorter extends React__namespace.Component {
|
|
|
43904
44475
|
changed: this.state.changed
|
|
43905
44476
|
};
|
|
43906
44477
|
}
|
|
44478
|
+
getPromptJSON() {
|
|
44479
|
+
return getPromptJSON$1(this.getUserInput());
|
|
44480
|
+
}
|
|
43907
44481
|
moveOptionToIndex = (option, index) => {
|
|
43908
44482
|
// eslint-disable-next-line react/no-string-refs
|
|
43909
44483
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
@@ -43935,20 +44509,26 @@ var Sorter$1 = {
|
|
|
43935
44509
|
displayName: "Sorter",
|
|
43936
44510
|
widget: Sorter,
|
|
43937
44511
|
isLintable: true,
|
|
43938
|
-
|
|
44512
|
+
scorer: scoreSorter
|
|
43939
44513
|
};
|
|
43940
44514
|
|
|
43941
|
-
|
|
43942
|
-
|
|
43943
|
-
|
|
43944
|
-
|
|
43945
|
-
|
|
43946
|
-
|
|
43947
|
-
|
|
43948
|
-
|
|
43949
|
-
|
|
43950
|
-
|
|
43951
|
-
|
|
44515
|
+
/**
|
|
44516
|
+
* Filters the given table (modelled as a 2D array) to remove any rows that are
|
|
44517
|
+
* completely empty.
|
|
44518
|
+
*
|
|
44519
|
+
* @returns A new table with only non-empty rows.
|
|
44520
|
+
*/
|
|
44521
|
+
const filterNonEmpty = function (table) {
|
|
44522
|
+
return table.filter(function (row) {
|
|
44523
|
+
// Return only rows that are non-empty.
|
|
44524
|
+
return row.some(cell => cell);
|
|
44525
|
+
});
|
|
44526
|
+
};
|
|
44527
|
+
|
|
44528
|
+
function validateTable(userInput) {
|
|
44529
|
+
const supplied = filterNonEmpty(userInput);
|
|
44530
|
+
const hasEmptyCell = supplied.some(function (row) {
|
|
44531
|
+
return row.some(function (cell) {
|
|
43952
44532
|
return cell === "";
|
|
43953
44533
|
});
|
|
43954
44534
|
});
|
|
@@ -43958,6 +44538,16 @@ function tableValidator(state, rubric, strings) {
|
|
|
43958
44538
|
message: null
|
|
43959
44539
|
};
|
|
43960
44540
|
}
|
|
44541
|
+
return null;
|
|
44542
|
+
}
|
|
44543
|
+
|
|
44544
|
+
function scoreTable(userInput, rubric, strings) {
|
|
44545
|
+
const validationResult = validateTable(userInput);
|
|
44546
|
+
if (validationResult != null) {
|
|
44547
|
+
return validationResult;
|
|
44548
|
+
}
|
|
44549
|
+
const supplied = filterNonEmpty(userInput);
|
|
44550
|
+
const solution = filterNonEmpty(rubric.answers);
|
|
43961
44551
|
if (supplied.length !== solution.length) {
|
|
43962
44552
|
return {
|
|
43963
44553
|
type: "points",
|
|
@@ -43968,17 +44558,16 @@ function tableValidator(state, rubric, strings) {
|
|
|
43968
44558
|
}
|
|
43969
44559
|
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
43970
44560
|
let message = null;
|
|
43971
|
-
const allCorrect =
|
|
44561
|
+
const allCorrect = solution.every(function (rowSolution) {
|
|
43972
44562
|
for (let i = 0; i < supplied.length; i++) {
|
|
43973
44563
|
const rowSupplied = supplied[i];
|
|
43974
|
-
const correct =
|
|
44564
|
+
const correct = rowSupplied.every(function (cellSupplied, i) {
|
|
43975
44565
|
const cellSolution = rowSolution[i];
|
|
43976
44566
|
const validator = createValidator(cellSolution, {
|
|
43977
44567
|
simplify: true
|
|
43978
44568
|
}, strings);
|
|
43979
44569
|
const result = validator(cellSupplied);
|
|
43980
44570
|
if (result.message) {
|
|
43981
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
43982
44571
|
message = result.message;
|
|
43983
44572
|
}
|
|
43984
44573
|
return result.correct;
|
|
@@ -43994,7 +44583,7 @@ function tableValidator(state, rubric, strings) {
|
|
|
43994
44583
|
type: "points",
|
|
43995
44584
|
earned: allCorrect ? 1 : 0,
|
|
43996
44585
|
total: 1,
|
|
43997
|
-
message
|
|
44586
|
+
message
|
|
43998
44587
|
};
|
|
43999
44588
|
}
|
|
44000
44589
|
|
|
@@ -44212,7 +44801,11 @@ var Table$1 = {
|
|
|
44212
44801
|
transform: propTransform,
|
|
44213
44802
|
hidden: true,
|
|
44214
44803
|
isLintable: true,
|
|
44215
|
-
|
|
44804
|
+
scorer: scoreTable
|
|
44805
|
+
};
|
|
44806
|
+
|
|
44807
|
+
const getPromptJSON = () => {
|
|
44808
|
+
return getUnsupportedPromptJSON("video");
|
|
44216
44809
|
};
|
|
44217
44810
|
|
|
44218
44811
|
/**
|
|
@@ -44311,6 +44904,9 @@ class Video extends React__namespace.Component {
|
|
|
44311
44904
|
return change.apply(_this, args);
|
|
44312
44905
|
};
|
|
44313
44906
|
})();
|
|
44907
|
+
getPromptJSON() {
|
|
44908
|
+
return getPromptJSON();
|
|
44909
|
+
}
|
|
44314
44910
|
render() {
|
|
44315
44911
|
const {
|
|
44316
44912
|
InitialRequestUrl
|
|
@@ -44370,8 +44966,8 @@ var Video$1 = {
|
|
|
44370
44966
|
defaultAlignment: "block",
|
|
44371
44967
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44372
44968
|
widget: Video,
|
|
44373
|
-
// TODO: things that aren't interactive shouldn't need
|
|
44374
|
-
|
|
44969
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44970
|
+
scorer: () => scoreNoop()
|
|
44375
44971
|
};
|
|
44376
44972
|
|
|
44377
44973
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -44390,7 +44986,7 @@ const init = function () {
|
|
|
44390
44986
|
|
|
44391
44987
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44392
44988
|
const libName = "@khanacademy/perseus";
|
|
44393
|
-
const libVersion = "41.
|
|
44989
|
+
const libVersion = "41.4.0";
|
|
44394
44990
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44395
44991
|
|
|
44396
44992
|
const apiVersion = {
|
|
@@ -45126,6 +45722,9 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
45126
45722
|
getNumHints() {
|
|
45127
45723
|
return this.props.item.hints.length;
|
|
45128
45724
|
}
|
|
45725
|
+
getPromptJSON() {
|
|
45726
|
+
return this.questionRenderer.getPromptJSON();
|
|
45727
|
+
}
|
|
45129
45728
|
|
|
45130
45729
|
/**
|
|
45131
45730
|
* Grades the item.
|
|
@@ -45783,7 +46382,7 @@ function treeToItem(node) {
|
|
|
45783
46382
|
};
|
|
45784
46383
|
}
|
|
45785
46384
|
|
|
45786
|
-
var isObject = function (obj) {
|
|
46385
|
+
var isObject$1 = function (obj) {
|
|
45787
46386
|
return obj === Object(obj);
|
|
45788
46387
|
};
|
|
45789
46388
|
var merge = function () {
|
|
@@ -45799,7 +46398,7 @@ var merge = function () {
|
|
|
45799
46398
|
return obj;
|
|
45800
46399
|
};
|
|
45801
46400
|
var clone = function (obj) {
|
|
45802
|
-
if (!isObject(obj)) {
|
|
46401
|
+
if (!isObject$1(obj)) {
|
|
45803
46402
|
return obj;
|
|
45804
46403
|
}
|
|
45805
46404
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45815,7 +46414,7 @@ var clone = function (obj) {
|
|
|
45815
46414
|
var dispatch = function (x) {
|
|
45816
46415
|
if (Array.isArray(x)) {
|
|
45817
46416
|
return arr;
|
|
45818
|
-
} else if (isObject(x)) {
|
|
46417
|
+
} else if (isObject$1(x)) {
|
|
45819
46418
|
return obj;
|
|
45820
46419
|
} else if (typeof x === "string") {
|
|
45821
46420
|
return str;
|
|
@@ -47739,6 +48338,697 @@ function buildTestData(testObject) {
|
|
|
47739
48338
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47740
48339
|
}
|
|
47741
48340
|
|
|
48341
|
+
function success(value) {
|
|
48342
|
+
return {
|
|
48343
|
+
type: "success",
|
|
48344
|
+
value
|
|
48345
|
+
};
|
|
48346
|
+
}
|
|
48347
|
+
function failure(detail) {
|
|
48348
|
+
return {
|
|
48349
|
+
type: "failure",
|
|
48350
|
+
detail
|
|
48351
|
+
};
|
|
48352
|
+
}
|
|
48353
|
+
function isFailure(result) {
|
|
48354
|
+
return result.type === "failure";
|
|
48355
|
+
}
|
|
48356
|
+
function isSuccess(result) {
|
|
48357
|
+
return result.type === "success";
|
|
48358
|
+
}
|
|
48359
|
+
|
|
48360
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
48361
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
48362
|
+
function all(results) {
|
|
48363
|
+
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
48364
|
+
const values = [];
|
|
48365
|
+
const failureDetails = [];
|
|
48366
|
+
for (const result of results) {
|
|
48367
|
+
if (result.type === "success") {
|
|
48368
|
+
values.push(result.value);
|
|
48369
|
+
} else {
|
|
48370
|
+
failureDetails.push(result.detail);
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48373
|
+
if (failureDetails.length > 0) {
|
|
48374
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
48375
|
+
}
|
|
48376
|
+
return success(values);
|
|
48377
|
+
}
|
|
48378
|
+
|
|
48379
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48380
|
+
|
|
48381
|
+
function array(elementParser) {
|
|
48382
|
+
return (rawValue, ctx) => {
|
|
48383
|
+
if (!Array.isArray(rawValue)) {
|
|
48384
|
+
return ctx.failure("array", rawValue);
|
|
48385
|
+
}
|
|
48386
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
48387
|
+
return all(elementResults, concat);
|
|
48388
|
+
};
|
|
48389
|
+
}
|
|
48390
|
+
function concat(a, b) {
|
|
48391
|
+
return [...a, ...b];
|
|
48392
|
+
}
|
|
48393
|
+
|
|
48394
|
+
function boolean(rawValue, ctx) {
|
|
48395
|
+
if (typeof rawValue === "boolean") {
|
|
48396
|
+
return ctx.success(rawValue);
|
|
48397
|
+
}
|
|
48398
|
+
return ctx.failure("boolean", rawValue);
|
|
48399
|
+
}
|
|
48400
|
+
|
|
48401
|
+
function constant(acceptedValue) {
|
|
48402
|
+
return (rawValue, ctx) => {
|
|
48403
|
+
if (rawValue !== acceptedValue) {
|
|
48404
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
48405
|
+
}
|
|
48406
|
+
return ctx.success(acceptedValue);
|
|
48407
|
+
};
|
|
48408
|
+
}
|
|
48409
|
+
|
|
48410
|
+
function enumeration() {
|
|
48411
|
+
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
48412
|
+
acceptedValues[_key] = arguments[_key];
|
|
48413
|
+
}
|
|
48414
|
+
return (rawValue, ctx) => {
|
|
48415
|
+
if (typeof rawValue === "string") {
|
|
48416
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
48417
|
+
if (index > -1) {
|
|
48418
|
+
return ctx.success(acceptedValues[index]);
|
|
48419
|
+
}
|
|
48420
|
+
}
|
|
48421
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
48422
|
+
return ctx.failure(expected, rawValue);
|
|
48423
|
+
};
|
|
48424
|
+
}
|
|
48425
|
+
|
|
48426
|
+
function isObject(x) {
|
|
48427
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
48428
|
+
}
|
|
48429
|
+
|
|
48430
|
+
function nullable(parseValue) {
|
|
48431
|
+
return (rawValue, ctx) => {
|
|
48432
|
+
if (rawValue === null) {
|
|
48433
|
+
return ctx.success(rawValue);
|
|
48434
|
+
}
|
|
48435
|
+
return parseValue(rawValue, ctx);
|
|
48436
|
+
};
|
|
48437
|
+
}
|
|
48438
|
+
|
|
48439
|
+
const number = (rawValue, ctx) => {
|
|
48440
|
+
if (typeof rawValue === "number") {
|
|
48441
|
+
return ctx.success(rawValue);
|
|
48442
|
+
}
|
|
48443
|
+
return ctx.failure("number", rawValue);
|
|
48444
|
+
};
|
|
48445
|
+
|
|
48446
|
+
function object(schema) {
|
|
48447
|
+
return (rawValue, ctx) => {
|
|
48448
|
+
if (!isObject(rawValue)) {
|
|
48449
|
+
return ctx.failure("object", rawValue);
|
|
48450
|
+
}
|
|
48451
|
+
const ret = {
|
|
48452
|
+
...rawValue
|
|
48453
|
+
};
|
|
48454
|
+
const mismatches = [];
|
|
48455
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
48456
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
48457
|
+
if (isSuccess(result)) {
|
|
48458
|
+
ret[prop] = result.value;
|
|
48459
|
+
} else {
|
|
48460
|
+
mismatches.push(...result.detail);
|
|
48461
|
+
}
|
|
48462
|
+
}
|
|
48463
|
+
if (mismatches.length > 0) {
|
|
48464
|
+
return failure(mismatches);
|
|
48465
|
+
}
|
|
48466
|
+
return ctx.success(ret);
|
|
48467
|
+
};
|
|
48468
|
+
}
|
|
48469
|
+
|
|
48470
|
+
function optional(parseValue) {
|
|
48471
|
+
return (rawValue, ctx) => {
|
|
48472
|
+
if (rawValue === undefined) {
|
|
48473
|
+
return ctx.success(rawValue);
|
|
48474
|
+
}
|
|
48475
|
+
return parseValue(rawValue, ctx);
|
|
48476
|
+
};
|
|
48477
|
+
}
|
|
48478
|
+
|
|
48479
|
+
function pair(parseA, parseB) {
|
|
48480
|
+
return (rawValue, ctx) => {
|
|
48481
|
+
if (!Array.isArray(rawValue)) {
|
|
48482
|
+
return ctx.failure("array", rawValue);
|
|
48483
|
+
}
|
|
48484
|
+
if (rawValue.length !== 2) {
|
|
48485
|
+
return ctx.failure("array of length 2", rawValue);
|
|
48486
|
+
}
|
|
48487
|
+
const [rawA, rawB] = rawValue;
|
|
48488
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
48489
|
+
if (isFailure(resultA)) {
|
|
48490
|
+
return resultA;
|
|
48491
|
+
}
|
|
48492
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
48493
|
+
if (isFailure(resultB)) {
|
|
48494
|
+
return resultB;
|
|
48495
|
+
}
|
|
48496
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
48497
|
+
};
|
|
48498
|
+
}
|
|
48499
|
+
|
|
48500
|
+
function record(parseKey, parseValue) {
|
|
48501
|
+
return (rawValue, ctx) => {
|
|
48502
|
+
if (!isObject(rawValue)) {
|
|
48503
|
+
return ctx.failure("object", rawValue);
|
|
48504
|
+
}
|
|
48505
|
+
const result = {};
|
|
48506
|
+
const mismatches = [];
|
|
48507
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
48508
|
+
const entryCtx = ctx.forSubtree(key);
|
|
48509
|
+
const keyResult = parseKey(key, entryCtx);
|
|
48510
|
+
if (isFailure(keyResult)) {
|
|
48511
|
+
mismatches.push(...keyResult.detail);
|
|
48512
|
+
}
|
|
48513
|
+
const valueResult = parseValue(value, entryCtx);
|
|
48514
|
+
if (isFailure(valueResult)) {
|
|
48515
|
+
mismatches.push(...valueResult.detail);
|
|
48516
|
+
}
|
|
48517
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
48518
|
+
result[keyResult.value] = valueResult.value;
|
|
48519
|
+
}
|
|
48520
|
+
}
|
|
48521
|
+
if (mismatches.length > 0) {
|
|
48522
|
+
return failure(mismatches);
|
|
48523
|
+
}
|
|
48524
|
+
return ctx.success(result);
|
|
48525
|
+
};
|
|
48526
|
+
}
|
|
48527
|
+
|
|
48528
|
+
const string = (rawValue, ctx) => {
|
|
48529
|
+
if (typeof rawValue === "string") {
|
|
48530
|
+
return ctx.success(rawValue);
|
|
48531
|
+
}
|
|
48532
|
+
return ctx.failure("string", rawValue);
|
|
48533
|
+
};
|
|
48534
|
+
|
|
48535
|
+
function trio(parseA, parseB, parseC) {
|
|
48536
|
+
return (rawValue, ctx) => {
|
|
48537
|
+
if (!Array.isArray(rawValue)) {
|
|
48538
|
+
return ctx.failure("array", rawValue);
|
|
48539
|
+
}
|
|
48540
|
+
if (rawValue.length !== 3) {
|
|
48541
|
+
return ctx.failure("array of length 3", rawValue);
|
|
48542
|
+
}
|
|
48543
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
48544
|
+
if (isFailure(resultA)) {
|
|
48545
|
+
return resultA;
|
|
48546
|
+
}
|
|
48547
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
48548
|
+
if (isFailure(resultB)) {
|
|
48549
|
+
return resultB;
|
|
48550
|
+
}
|
|
48551
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
48552
|
+
if (isFailure(resultC)) {
|
|
48553
|
+
return resultC;
|
|
48554
|
+
}
|
|
48555
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
48556
|
+
};
|
|
48557
|
+
}
|
|
48558
|
+
|
|
48559
|
+
function union(parseBranch) {
|
|
48560
|
+
return new UnionBuilder(parseBranch);
|
|
48561
|
+
}
|
|
48562
|
+
class UnionBuilder {
|
|
48563
|
+
constructor(parser) {
|
|
48564
|
+
this.parser = parser;
|
|
48565
|
+
}
|
|
48566
|
+
or(newBranch) {
|
|
48567
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
48568
|
+
}
|
|
48569
|
+
}
|
|
48570
|
+
function either(parseA, parseB) {
|
|
48571
|
+
return (rawValue, ctx) => {
|
|
48572
|
+
const resultA = parseA(rawValue, ctx);
|
|
48573
|
+
if (isSuccess(resultA)) {
|
|
48574
|
+
return resultA;
|
|
48575
|
+
}
|
|
48576
|
+
return parseB(rawValue, ctx);
|
|
48577
|
+
};
|
|
48578
|
+
}
|
|
48579
|
+
|
|
48580
|
+
function parseWidget(parseType, parseOptions) {
|
|
48581
|
+
return object({
|
|
48582
|
+
type: parseType,
|
|
48583
|
+
static: optional(boolean),
|
|
48584
|
+
graded: optional(boolean),
|
|
48585
|
+
alignment: optional(string),
|
|
48586
|
+
options: parseOptions,
|
|
48587
|
+
key: optional(number),
|
|
48588
|
+
version: object({
|
|
48589
|
+
major: number,
|
|
48590
|
+
minor: number
|
|
48591
|
+
})
|
|
48592
|
+
});
|
|
48593
|
+
}
|
|
48594
|
+
|
|
48595
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
48596
|
+
items: array(string),
|
|
48597
|
+
categories: array(string),
|
|
48598
|
+
randomizeItems: boolean,
|
|
48599
|
+
static: boolean,
|
|
48600
|
+
values: array(number),
|
|
48601
|
+
highlightLint: optional(boolean),
|
|
48602
|
+
linterContext: optional(object({
|
|
48603
|
+
contentType: string,
|
|
48604
|
+
paths: array(string),
|
|
48605
|
+
stack: array(string)
|
|
48606
|
+
}))
|
|
48607
|
+
}));
|
|
48608
|
+
|
|
48609
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
48610
|
+
programID: string,
|
|
48611
|
+
programType: any,
|
|
48612
|
+
settings: array(object({
|
|
48613
|
+
name: string,
|
|
48614
|
+
value: string
|
|
48615
|
+
})),
|
|
48616
|
+
showEditor: boolean,
|
|
48617
|
+
showButtons: boolean,
|
|
48618
|
+
width: number,
|
|
48619
|
+
height: number,
|
|
48620
|
+
static: boolean
|
|
48621
|
+
}));
|
|
48622
|
+
|
|
48623
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48624
|
+
togglePrompt: string,
|
|
48625
|
+
definition: string,
|
|
48626
|
+
static: boolean
|
|
48627
|
+
}));
|
|
48628
|
+
|
|
48629
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48630
|
+
placeholder: string,
|
|
48631
|
+
static: boolean,
|
|
48632
|
+
choices: array(object({
|
|
48633
|
+
content: string,
|
|
48634
|
+
correct: boolean
|
|
48635
|
+
}))
|
|
48636
|
+
}));
|
|
48637
|
+
|
|
48638
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
48639
|
+
showPrompt: string,
|
|
48640
|
+
hidePrompt: string,
|
|
48641
|
+
explanation: string,
|
|
48642
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
48643
|
+
// referenced before it is defined. There is an import cycle between
|
|
48644
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
48645
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48646
|
+
static: boolean
|
|
48647
|
+
}));
|
|
48648
|
+
|
|
48649
|
+
const parseAnswerForm = object({
|
|
48650
|
+
value: string,
|
|
48651
|
+
form: boolean,
|
|
48652
|
+
simplify: boolean,
|
|
48653
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48654
|
+
key: optional(string)
|
|
48655
|
+
});
|
|
48656
|
+
const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
48657
|
+
answerForms: array(parseAnswerForm),
|
|
48658
|
+
functions: array(string),
|
|
48659
|
+
times: boolean,
|
|
48660
|
+
visibleLabel: optional(string),
|
|
48661
|
+
ariaLabel: optional(string),
|
|
48662
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48663
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48664
|
+
}));
|
|
48665
|
+
|
|
48666
|
+
const parsePerseusImageBackground = object({
|
|
48667
|
+
url: optional(nullable(string)),
|
|
48668
|
+
width: optional(number),
|
|
48669
|
+
height: optional(number),
|
|
48670
|
+
top: optional(number),
|
|
48671
|
+
left: optional(number),
|
|
48672
|
+
bottom: optional(number),
|
|
48673
|
+
// TODO(benchristel): convert scale to a number
|
|
48674
|
+
scale: optional(union(number).or(string).parser)
|
|
48675
|
+
});
|
|
48676
|
+
|
|
48677
|
+
// Used to represent 2-D points and ranges
|
|
48678
|
+
const pairOfNumbers = pair(number, number);
|
|
48679
|
+
const parsePerseusGraphTypeAngle = object({
|
|
48680
|
+
type: constant("angle"),
|
|
48681
|
+
showAngles: optional(boolean),
|
|
48682
|
+
allowReflexAngles: optional(boolean),
|
|
48683
|
+
angleOffsetDeg: optional(number),
|
|
48684
|
+
snapDegrees: optional(number),
|
|
48685
|
+
match: optional(constant("congruent")),
|
|
48686
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48687
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
48688
|
+
});
|
|
48689
|
+
const parsePerseusGraphTypeCircle = object({
|
|
48690
|
+
type: constant("circle"),
|
|
48691
|
+
center: optional(pairOfNumbers),
|
|
48692
|
+
radius: optional(number),
|
|
48693
|
+
startCoords: optional(object({
|
|
48694
|
+
center: pairOfNumbers,
|
|
48695
|
+
radius: number
|
|
48696
|
+
})),
|
|
48697
|
+
// TODO: remove coord? it's legacy.
|
|
48698
|
+
coord: optional(pairOfNumbers)
|
|
48699
|
+
});
|
|
48700
|
+
const parsePerseusGraphTypeLinear = object({
|
|
48701
|
+
type: constant("linear"),
|
|
48702
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48703
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48704
|
+
// TODO: remove coord? it's legacy.
|
|
48705
|
+
coord: optional(pairOfNumbers)
|
|
48706
|
+
});
|
|
48707
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
48708
|
+
type: constant("linear-system"),
|
|
48709
|
+
// TODO(benchristel): default coords to empty array?
|
|
48710
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48711
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48712
|
+
// TODO: remove coord? it's legacy.
|
|
48713
|
+
coord: optional(pairOfNumbers)
|
|
48714
|
+
});
|
|
48715
|
+
const parsePerseusGraphTypeNone = object({
|
|
48716
|
+
type: constant("none")
|
|
48717
|
+
});
|
|
48718
|
+
const parsePerseusGraphTypePoint = object({
|
|
48719
|
+
type: constant("point"),
|
|
48720
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
48721
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48722
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48723
|
+
// TODO: remove coord? it's legacy.
|
|
48724
|
+
coord: optional(pairOfNumbers)
|
|
48725
|
+
});
|
|
48726
|
+
const parsePerseusGraphTypePolygon = object({
|
|
48727
|
+
type: constant("polygon"),
|
|
48728
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
48729
|
+
showAngles: optional(boolean),
|
|
48730
|
+
showSides: optional(boolean),
|
|
48731
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
48732
|
+
match: optional(enumeration("similar", "congruent", "approx")),
|
|
48733
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48734
|
+
// TODO: remove coord? it's legacy.
|
|
48735
|
+
coord: optional(pairOfNumbers)
|
|
48736
|
+
});
|
|
48737
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
48738
|
+
type: constant("quadratic"),
|
|
48739
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
48740
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48741
|
+
// TODO: remove coord? it's legacy.
|
|
48742
|
+
coord: optional(pairOfNumbers)
|
|
48743
|
+
});
|
|
48744
|
+
const parsePerseusGraphTypeRay = object({
|
|
48745
|
+
type: constant("ray"),
|
|
48746
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48747
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48748
|
+
// TODO: remove coord? it's legacy.
|
|
48749
|
+
coord: optional(pairOfNumbers)
|
|
48750
|
+
});
|
|
48751
|
+
const parsePerseusGraphTypeSegment = object({
|
|
48752
|
+
type: constant("segment"),
|
|
48753
|
+
// TODO(benchristel): default numSegments?
|
|
48754
|
+
numSegments: optional(number),
|
|
48755
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48756
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48757
|
+
// TODO: remove coord? it's legacy.
|
|
48758
|
+
coord: optional(pairOfNumbers)
|
|
48759
|
+
});
|
|
48760
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
48761
|
+
type: constant("sinusoid"),
|
|
48762
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48763
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48764
|
+
// TODO: remove coord? it's legacy.
|
|
48765
|
+
coord: optional(pairOfNumbers)
|
|
48766
|
+
});
|
|
48767
|
+
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;
|
|
48768
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48769
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48770
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48771
|
+
const parseLockedLabelType = object({
|
|
48772
|
+
type: constant("label"),
|
|
48773
|
+
coord: pairOfNumbers,
|
|
48774
|
+
text: string,
|
|
48775
|
+
color: parseLockedFigureColor,
|
|
48776
|
+
size: enumeration("small", "medium", "large")
|
|
48777
|
+
});
|
|
48778
|
+
const parseLockedPointType = object({
|
|
48779
|
+
type: constant("point"),
|
|
48780
|
+
coord: pairOfNumbers,
|
|
48781
|
+
color: parseLockedFigureColor,
|
|
48782
|
+
filled: boolean,
|
|
48783
|
+
// TODO(benchristel): default labels to empty array?
|
|
48784
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48785
|
+
ariaLabel: optional(string)
|
|
48786
|
+
});
|
|
48787
|
+
const parseLockedLineType = object({
|
|
48788
|
+
type: constant("line"),
|
|
48789
|
+
kind: enumeration("line", "ray", "segment"),
|
|
48790
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48791
|
+
color: parseLockedFigureColor,
|
|
48792
|
+
lineStyle: parseLockedLineStyle,
|
|
48793
|
+
showPoint1: boolean,
|
|
48794
|
+
showPoint2: boolean,
|
|
48795
|
+
// TODO(benchristel): default labels to empty array?
|
|
48796
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48797
|
+
ariaLabel: optional(string)
|
|
48798
|
+
});
|
|
48799
|
+
const parseLockedVectorType = object({
|
|
48800
|
+
type: constant("vector"),
|
|
48801
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
48802
|
+
color: parseLockedFigureColor,
|
|
48803
|
+
// TODO(benchristel): default labels to empty array?
|
|
48804
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48805
|
+
ariaLabel: optional(string)
|
|
48806
|
+
});
|
|
48807
|
+
const parseLockedEllipseType = object({
|
|
48808
|
+
type: constant("ellipse"),
|
|
48809
|
+
center: pairOfNumbers,
|
|
48810
|
+
radius: pairOfNumbers,
|
|
48811
|
+
angle: number,
|
|
48812
|
+
color: parseLockedFigureColor,
|
|
48813
|
+
fillStyle: parseLockedFigureFillType,
|
|
48814
|
+
strokeStyle: parseLockedLineStyle,
|
|
48815
|
+
// TODO(benchristel): default labels to empty array?
|
|
48816
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48817
|
+
ariaLabel: optional(string)
|
|
48818
|
+
});
|
|
48819
|
+
const parseLockedPolygonType = object({
|
|
48820
|
+
type: constant("polygon"),
|
|
48821
|
+
points: array(pairOfNumbers),
|
|
48822
|
+
color: parseLockedFigureColor,
|
|
48823
|
+
showVertices: boolean,
|
|
48824
|
+
fillStyle: parseLockedFigureFillType,
|
|
48825
|
+
strokeStyle: parseLockedLineStyle,
|
|
48826
|
+
// TODO(benchristel): default labels to empty array?
|
|
48827
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48828
|
+
ariaLabel: optional(string)
|
|
48829
|
+
});
|
|
48830
|
+
const parseLockedFunctionType = object({
|
|
48831
|
+
type: constant("function"),
|
|
48832
|
+
color: parseLockedFigureColor,
|
|
48833
|
+
strokeStyle: parseLockedLineStyle,
|
|
48834
|
+
equation: string,
|
|
48835
|
+
directionalAxis: enumeration("x", "y"),
|
|
48836
|
+
domain: optional(pairOfNumbers),
|
|
48837
|
+
// TODO(benchristel): default labels to empty array?
|
|
48838
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48839
|
+
ariaLabel: optional(string)
|
|
48840
|
+
});
|
|
48841
|
+
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
48842
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48843
|
+
step: pairOfNumbers,
|
|
48844
|
+
gridStep: pairOfNumbers,
|
|
48845
|
+
snapStep: pairOfNumbers,
|
|
48846
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
48847
|
+
markings: enumeration("graph", "grid", "none"),
|
|
48848
|
+
labels: array(string),
|
|
48849
|
+
showProtractor: boolean,
|
|
48850
|
+
showRuler: optional(boolean),
|
|
48851
|
+
showTooltips: optional(boolean),
|
|
48852
|
+
rulerLabel: optional(string),
|
|
48853
|
+
rulerTicks: optional(number),
|
|
48854
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
48855
|
+
graph: parsePerseusGraphType,
|
|
48856
|
+
correct: parsePerseusGraphType,
|
|
48857
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
48858
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
48859
|
+
fullGraphLabel: optional(string),
|
|
48860
|
+
fullGraphAriaDescription: optional(string)
|
|
48861
|
+
}));
|
|
48862
|
+
|
|
48863
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
48864
|
+
if (!isObject(rawValue)) {
|
|
48865
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
48866
|
+
}
|
|
48867
|
+
const widgetsMap = {};
|
|
48868
|
+
for (const key of Object.keys(rawValue)) {
|
|
48869
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
48870
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
48871
|
+
// against the widget type.
|
|
48872
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
48873
|
+
if (isFailure(entryResult)) {
|
|
48874
|
+
return entryResult;
|
|
48875
|
+
}
|
|
48876
|
+
}
|
|
48877
|
+
return ctx.success(widgetsMap);
|
|
48878
|
+
};
|
|
48879
|
+
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
48880
|
+
let [key, widget] = _ref;
|
|
48881
|
+
const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
|
|
48882
|
+
if (isFailure(keyComponentsResult)) {
|
|
48883
|
+
return keyComponentsResult;
|
|
48884
|
+
}
|
|
48885
|
+
const [type, id] = keyComponentsResult.value;
|
|
48886
|
+
function parseAndAssign(key, parse) {
|
|
48887
|
+
const widgetResult = parse(widget, ctx);
|
|
48888
|
+
if (isFailure(widgetResult)) {
|
|
48889
|
+
return widgetResult;
|
|
48890
|
+
}
|
|
48891
|
+
widgetMap[key] = widgetResult.value;
|
|
48892
|
+
return ctx.success(undefined);
|
|
48893
|
+
}
|
|
48894
|
+
switch (type) {
|
|
48895
|
+
case "categorizer":
|
|
48896
|
+
return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
|
|
48897
|
+
case "cs-program":
|
|
48898
|
+
return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
|
|
48899
|
+
case "definition":
|
|
48900
|
+
return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
|
|
48901
|
+
case "dropdown":
|
|
48902
|
+
return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
|
|
48903
|
+
case "explanation":
|
|
48904
|
+
return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
|
|
48905
|
+
case "expression":
|
|
48906
|
+
return parseAndAssign(`expression ${id}`, parseExpressionWidget);
|
|
48907
|
+
case "grapher":
|
|
48908
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48909
|
+
return parseAndAssign(`grapher ${id}`, any);
|
|
48910
|
+
case "group":
|
|
48911
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48912
|
+
return parseAndAssign(`group ${id}`, any);
|
|
48913
|
+
case "graded-group":
|
|
48914
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48915
|
+
return parseAndAssign(`graded-group ${id}`, any);
|
|
48916
|
+
case "graded-group-set":
|
|
48917
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48918
|
+
return parseAndAssign(`graded-group-set ${id}`, any);
|
|
48919
|
+
case "iframe":
|
|
48920
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48921
|
+
return parseAndAssign(`iframe ${id}`, any);
|
|
48922
|
+
case "image":
|
|
48923
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48924
|
+
return parseAndAssign(`image ${id}`, any);
|
|
48925
|
+
case "input-number":
|
|
48926
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48927
|
+
return parseAndAssign(`input-number ${id}`, any);
|
|
48928
|
+
case "interaction":
|
|
48929
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48930
|
+
return parseAndAssign(`interaction ${id}`, any);
|
|
48931
|
+
case "interactive-graph":
|
|
48932
|
+
return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
|
|
48933
|
+
case "label-image":
|
|
48934
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48935
|
+
return parseAndAssign(`label-image ${id}`, any);
|
|
48936
|
+
case "matcher":
|
|
48937
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48938
|
+
return parseAndAssign(`matcher ${id}`, any);
|
|
48939
|
+
case "matrix":
|
|
48940
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48941
|
+
return parseAndAssign(`matrix ${id}`, any);
|
|
48942
|
+
case "measurer":
|
|
48943
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48944
|
+
return parseAndAssign(`measurer ${id}`, any);
|
|
48945
|
+
case "molecule-renderer":
|
|
48946
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48947
|
+
return parseAndAssign(`molecule-renderer ${id}`, any);
|
|
48948
|
+
case "number-line":
|
|
48949
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48950
|
+
return parseAndAssign(`number-line ${id}`, any);
|
|
48951
|
+
case "numeric-input":
|
|
48952
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48953
|
+
return parseAndAssign(`numeric-input ${id}`, any);
|
|
48954
|
+
case "orderer":
|
|
48955
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48956
|
+
return parseAndAssign(`orderer ${id}`, any);
|
|
48957
|
+
case "passage":
|
|
48958
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48959
|
+
return parseAndAssign(`passage ${id}`, any);
|
|
48960
|
+
case "passage-ref":
|
|
48961
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48962
|
+
return parseAndAssign(`passage-ref ${id}`, any);
|
|
48963
|
+
case "passage-ref-target":
|
|
48964
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48965
|
+
return parseAndAssign(`passage-ref-target ${id}`, any);
|
|
48966
|
+
case "phet-simulation":
|
|
48967
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48968
|
+
return parseAndAssign(`phet-simulation ${id}`, any);
|
|
48969
|
+
case "plotter":
|
|
48970
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48971
|
+
return parseAndAssign(`plotter ${id}`, any);
|
|
48972
|
+
case "python-program":
|
|
48973
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48974
|
+
return parseAndAssign(`python-program ${id}`, any);
|
|
48975
|
+
case "radio":
|
|
48976
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48977
|
+
return parseAndAssign(`radio ${id}`, any);
|
|
48978
|
+
case "sorter":
|
|
48979
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48980
|
+
return parseAndAssign(`sorter ${id}`, any);
|
|
48981
|
+
case "table":
|
|
48982
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48983
|
+
return parseAndAssign(`table ${id}`, any);
|
|
48984
|
+
case "video":
|
|
48985
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48986
|
+
return parseAndAssign(`video ${id}`, any);
|
|
48987
|
+
default:
|
|
48988
|
+
return ctx.failure("a valid widget type", type);
|
|
48989
|
+
}
|
|
48990
|
+
};
|
|
48991
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48992
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48993
|
+
return ctx.failure("numeric string", rawValue);
|
|
48994
|
+
}
|
|
48995
|
+
return ctx.success(+rawValue);
|
|
48996
|
+
};
|
|
48997
|
+
const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
48998
|
+
|
|
48999
|
+
const parseHint = object({
|
|
49000
|
+
replace: optional(boolean),
|
|
49001
|
+
content: string,
|
|
49002
|
+
widgets: parseWidgetsMap,
|
|
49003
|
+
metadata: optional(array(string)),
|
|
49004
|
+
images: record(string, object({
|
|
49005
|
+
width: number,
|
|
49006
|
+
height: number
|
|
49007
|
+
}))
|
|
49008
|
+
});
|
|
49009
|
+
|
|
49010
|
+
const parsePerseusRenderer = object({
|
|
49011
|
+
content: string,
|
|
49012
|
+
widgets: parseWidgetsMap,
|
|
49013
|
+
metadata: optional(array(string)),
|
|
49014
|
+
images: record(string, object({
|
|
49015
|
+
width: number,
|
|
49016
|
+
height: number
|
|
49017
|
+
}))
|
|
49018
|
+
});
|
|
49019
|
+
|
|
49020
|
+
object({
|
|
49021
|
+
question: parsePerseusRenderer,
|
|
49022
|
+
hints: array(parseHint),
|
|
49023
|
+
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
49024
|
+
itemDataVersion: object({
|
|
49025
|
+
major: number,
|
|
49026
|
+
minor: number
|
|
49027
|
+
}),
|
|
49028
|
+
// Deprecated field
|
|
49029
|
+
answer: any
|
|
49030
|
+
});
|
|
49031
|
+
|
|
47742
49032
|
/**
|
|
47743
49033
|
* Helper to parse PerseusItem JSON
|
|
47744
49034
|
* Why not just use JSON.parse? We want:
|