@khanacademy/perseus 41.2.0 → 41.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-widgets.d.ts +1 -1
- package/dist/article-renderer.d.ts +2 -2
- package/dist/basic-widgets.d.ts +1 -1
- package/dist/es/index.js +1609 -335
- 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 +1528 -253
- 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/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +15 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/util/{parse-perseus-json.d.ts → parse-perseus-json/index.d.ts} +7 -1
- package/dist/util/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse.d.ts +3 -0
- package/dist/util/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/util/parse-perseus-json/result.d.ts +16 -0
- package/dist/validation.types.d.ts +0 -3
- package/dist/widget-ai-utils/categorizer/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/cs-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/definition/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/dropdown/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/explanation/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/expression/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/graded-group/prompt-utils.d.ts +7 -0
- package/dist/widget-ai-utils/graded-group-set/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/grapher/prompt-utils.d.ts +18 -0
- package/dist/widget-ai-utils/group/prompt-utils.d.ts +5 -0
- package/dist/widget-ai-utils/iframe/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/image/prompt-utils.d.ts +12 -0
- package/dist/widget-ai-utils/input-number/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/interaction/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/interactive-graph/prompt-utils.d.ts +94 -0
- package/dist/widget-ai-utils/label-image/prompt-utils.d.ts +24 -0
- package/dist/widget-ai-utils/matcher/prompt-utils.d.ts +17 -0
- package/dist/widget-ai-utils/matrix/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/measurer/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/number-line/prompt-utils.d.ts +16 -0
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/orderer/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/passage/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/passage-ref/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/phet-simulation/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/plotter/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/python-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/radio/prompt-utils.d.ts +15 -0
- package/dist/widget-ai-utils/sorter/prompt-utils.d.ts +9 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +11 -0
- package/dist/widget-ai-utils/video/prompt-utils.d.ts +2 -0
- package/dist/widgets/__shared__/{noop-validator.d.ts → score-noop.d.ts} +4 -4
- package/dist/widgets/categorizer/categorizer.d.ts +10 -19
- package/dist/widgets/categorizer/{categorizer-validator.d.ts → score-categorizer.d.ts} +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +8 -19
- package/dist/widgets/cs-program/{cs-program-validator.d.ts → score-cs-program.d.ts} +2 -1
- package/dist/widgets/definition/definition.d.ts +8 -19
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +4 -19
- package/dist/widgets/dropdown/dropdown.d.ts +10 -19
- package/dist/widgets/dropdown/score-dropdown.d.ts +4 -0
- package/dist/widgets/explanation/explanation.d.ts +9 -19
- package/dist/widgets/expression/expression.d.ts +26 -290
- package/dist/widgets/expression/{expression-validator.d.ts → score-expression.d.ts} +2 -2
- package/dist/widgets/graded-group/graded-group.d.ts +13 -21
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +9 -20
- package/dist/widgets/grapher/grapher.d.ts +9 -19
- package/dist/widgets/grapher/score-grapher.d.ts +4 -0
- package/dist/widgets/group/group.d.ts +7 -19
- package/dist/widgets/iframe/iframe.d.ts +7 -19
- package/dist/widgets/iframe/{iframe-validator.d.ts → score-iframe.d.ts} +1 -1
- package/dist/widgets/image/image.d.ts +9 -19
- package/dist/widgets/input-number/input-number.d.ts +11 -19
- package/dist/widgets/input-number/{input-number-validator.d.ts → score-input-number.d.ts} +2 -2
- package/dist/widgets/interaction/interaction.d.ts +7 -19
- package/dist/widgets/interactive-graph.d.ts +8 -19
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +4 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +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/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +4 -4
- package/dist/widgets/dropdown/dropdown-validator.d.ts +0 -4
- package/dist/widgets/grapher/grapher-validator.d.ts +0 -4
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +0 -4
- package/dist/widgets/number-line/number-line-validator.d.ts +0 -4
- package/dist/widgets/plotter/plotter-validator.d.ts +0 -4
- package/dist/widgets/sorter/sorter-validator.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var kmath = require('@khanacademy/kmath');
|
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
26
26
|
var SimpleMarkdown = require('@khanacademy/simple-markdown');
|
|
27
|
-
var
|
|
27
|
+
var SimpleMarkdown$1 = require('@khanacademy/pure-markdown');
|
|
28
28
|
var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
|
|
29
29
|
var Button = require('@khanacademy/wonder-blocks-button');
|
|
30
30
|
var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
|
|
@@ -77,6 +77,7 @@ var Clickable__default = /*#__PURE__*/_interopDefaultLegacy(Clickable);
|
|
|
77
77
|
var $__default = /*#__PURE__*/_interopDefaultLegacy($);
|
|
78
78
|
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
79
79
|
var SimpleMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(SimpleMarkdown);
|
|
80
|
+
var SimpleMarkdown__namespace = /*#__PURE__*/_interopNamespace(SimpleMarkdown$1);
|
|
80
81
|
var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
|
|
81
82
|
var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
|
|
82
83
|
var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
|
|
@@ -1293,6 +1294,41 @@ const ClassNames = {
|
|
|
1293
1294
|
MOBILE: "perseus-mobile"
|
|
1294
1295
|
};
|
|
1295
1296
|
|
|
1297
|
+
const getPromptJSON$t = (renderProps, userInput) => {
|
|
1298
|
+
return {
|
|
1299
|
+
type: "expression",
|
|
1300
|
+
label: renderProps.visibleLabel,
|
|
1301
|
+
userInput: {
|
|
1302
|
+
value: userInput
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Get the character used for separating decimals.
|
|
1309
|
+
*/
|
|
1310
|
+
const getDecimalSeparator = locale => {
|
|
1311
|
+
switch (locale) {
|
|
1312
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1313
|
+
// locale properly.
|
|
1314
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1315
|
+
//
|
|
1316
|
+
// Supported locales in Chrome:
|
|
1317
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1318
|
+
case "ka":
|
|
1319
|
+
return ",";
|
|
1320
|
+
default:
|
|
1321
|
+
const numberWithDecimalSeparator = 1.1;
|
|
1322
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1323
|
+
// support Safari 12.
|
|
1324
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1325
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1326
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1327
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
1328
|
+
return match?.[0] ?? ".";
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1296
1332
|
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1297
1333
|
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1298
1334
|
// which get's the injected logger.
|
|
@@ -2225,31 +2261,6 @@ const KhanAnswerTypes = {
|
|
|
2225
2261
|
}
|
|
2226
2262
|
};
|
|
2227
2263
|
|
|
2228
|
-
/**
|
|
2229
|
-
* Get the character used for separating decimals.
|
|
2230
|
-
*/
|
|
2231
|
-
const getDecimalSeparator = locale => {
|
|
2232
|
-
switch (locale) {
|
|
2233
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2234
|
-
// locale properly.
|
|
2235
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2236
|
-
//
|
|
2237
|
-
// Supported locales in Chrome:
|
|
2238
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2239
|
-
case "ka":
|
|
2240
|
-
return ",";
|
|
2241
|
-
default:
|
|
2242
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2243
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2244
|
-
// support Safari 12.
|
|
2245
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2246
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2247
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2248
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2249
|
-
return match?.[0] ?? ".";
|
|
2250
|
-
}
|
|
2251
|
-
};
|
|
2252
|
-
|
|
2253
2264
|
/* Content creators input a list of answers which are matched from top to
|
|
2254
2265
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2255
2266
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2268,7 +2279,7 @@ const getDecimalSeparator = locale => {
|
|
|
2268
2279
|
* show the user an error. TODO(joel) - what error?
|
|
2269
2280
|
* - Otherwise, pass through the resulting points and message.
|
|
2270
2281
|
*/
|
|
2271
|
-
function
|
|
2282
|
+
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2272
2283
|
const options = ___default["default"].clone(rubric);
|
|
2273
2284
|
___default["default"].extend(options, {
|
|
2274
2285
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2480,6 +2491,9 @@ class Expression extends React__namespace.Component {
|
|
|
2480
2491
|
getUserInput() {
|
|
2481
2492
|
return Expression.getUserInputFromProps(this.props);
|
|
2482
2493
|
}
|
|
2494
|
+
getPromptJSON() {
|
|
2495
|
+
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2496
|
+
}
|
|
2483
2497
|
change = (() => {
|
|
2484
2498
|
var _this = this;
|
|
2485
2499
|
return function () {
|
|
@@ -2764,7 +2778,7 @@ var Expression$1 = {
|
|
|
2764
2778
|
propUpgrades: propUpgrades$2,
|
|
2765
2779
|
// For use by the editor
|
|
2766
2780
|
isLintable: true,
|
|
2767
|
-
|
|
2781
|
+
scorer: scoreExpression,
|
|
2768
2782
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2769
2783
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2770
2784
|
if (correctAnswers.length === 0) {
|
|
@@ -18502,10 +18516,10 @@ const styles$C = aphrodite.StyleSheet.create({
|
|
|
18502
18516
|
|
|
18503
18517
|
/* eslint-disable no-useless-escape, no-prototype-builtins */
|
|
18504
18518
|
const rules$1 = {
|
|
18505
|
-
...
|
|
18519
|
+
...SimpleMarkdown$1.pureMarkdownRules,
|
|
18506
18520
|
// NOTE: basically ignored by JIPT. wraps everything at the outer layer
|
|
18507
18521
|
columns: {
|
|
18508
|
-
...
|
|
18522
|
+
...SimpleMarkdown$1.pureMarkdownRules.columns,
|
|
18509
18523
|
react: (node, output, state) => {
|
|
18510
18524
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
18511
18525
|
className: "perseus-two-columns",
|
|
@@ -18533,7 +18547,7 @@ const rules$1 = {
|
|
|
18533
18547
|
// for exercises the renderer just renders the crowdin id to the
|
|
18534
18548
|
// renderer div.
|
|
18535
18549
|
crowdinId: {
|
|
18536
|
-
...
|
|
18550
|
+
...SimpleMarkdown$1.pureMarkdownRules.crowdinId,
|
|
18537
18551
|
react: (node, output, state) => node.id
|
|
18538
18552
|
},
|
|
18539
18553
|
// Ensure that the table is focusable in sequential keyboard navigation
|
|
@@ -18542,7 +18556,7 @@ const rules$1 = {
|
|
|
18542
18556
|
// left/right gesture. Note, setting `tabindex` doesn't appear required for
|
|
18543
18557
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18544
18558
|
table: {
|
|
18545
|
-
...
|
|
18559
|
+
...SimpleMarkdown$1.pureMarkdownRules.table,
|
|
18546
18560
|
react: function (node, output, state) {
|
|
18547
18561
|
const table = SimpleMarkdown__default["default"].defaultRules.table.react(node, output, state);
|
|
18548
18562
|
if (!state.isMobile) {
|
|
@@ -18560,7 +18574,7 @@ const rules$1 = {
|
|
|
18560
18574
|
// our table regex into tableCapture[1..], and we pass tableCapture to
|
|
18561
18575
|
// our nptable regex
|
|
18562
18576
|
titledTable: {
|
|
18563
|
-
...
|
|
18577
|
+
...SimpleMarkdown$1.pureMarkdownRules.titledTable,
|
|
18564
18578
|
react: (node, output, state) => {
|
|
18565
18579
|
let contents;
|
|
18566
18580
|
if (!node.table) {
|
|
@@ -18620,7 +18634,7 @@ const rules$1 = {
|
|
|
18620
18634
|
}
|
|
18621
18635
|
},
|
|
18622
18636
|
widget: {
|
|
18623
|
-
...
|
|
18637
|
+
...SimpleMarkdown$1.pureMarkdownRules.widget,
|
|
18624
18638
|
react: (node, output, state) => {
|
|
18625
18639
|
// The actual output is handled in the renderer, where
|
|
18626
18640
|
// we know the current widget props/state. This is
|
|
@@ -18631,7 +18645,7 @@ const rules$1 = {
|
|
|
18631
18645
|
}
|
|
18632
18646
|
},
|
|
18633
18647
|
blockMath: {
|
|
18634
|
-
...
|
|
18648
|
+
...SimpleMarkdown$1.pureMarkdownRules.blockMath,
|
|
18635
18649
|
react: (node, output, state) => {
|
|
18636
18650
|
const {
|
|
18637
18651
|
TeX
|
|
@@ -18645,7 +18659,7 @@ const rules$1 = {
|
|
|
18645
18659
|
}
|
|
18646
18660
|
},
|
|
18647
18661
|
math: {
|
|
18648
|
-
...
|
|
18662
|
+
...SimpleMarkdown$1.pureMarkdownRules.math,
|
|
18649
18663
|
react: (node, output, state) => {
|
|
18650
18664
|
const {
|
|
18651
18665
|
TeX
|
|
@@ -18659,7 +18673,7 @@ const rules$1 = {
|
|
|
18659
18673
|
}
|
|
18660
18674
|
},
|
|
18661
18675
|
unescapedDollar: {
|
|
18662
|
-
...
|
|
18676
|
+
...SimpleMarkdown$1.pureMarkdownRules.unescapedDollar,
|
|
18663
18677
|
react: (node, output, state) => {
|
|
18664
18678
|
// Unescaped dollar signs render correctly, but result in
|
|
18665
18679
|
// untranslatable text after the i18n python linter flags it
|
|
@@ -18669,7 +18683,7 @@ const rules$1 = {
|
|
|
18669
18683
|
// Extend the SimpleMarkdown link parser to make the link open in a new
|
|
18670
18684
|
// window and handle reverse tabnapping phishing attacks
|
|
18671
18685
|
link: {
|
|
18672
|
-
...
|
|
18686
|
+
...SimpleMarkdown$1.pureMarkdownRules.link,
|
|
18673
18687
|
react: function (node, output, state) {
|
|
18674
18688
|
const link = SimpleMarkdown__default["default"].defaultRules.link.react(node, output, state);
|
|
18675
18689
|
const href = link.props.href;
|
|
@@ -18696,7 +18710,7 @@ const rules$1 = {
|
|
|
18696
18710
|
}
|
|
18697
18711
|
},
|
|
18698
18712
|
codeBlock: {
|
|
18699
|
-
...
|
|
18713
|
+
...SimpleMarkdown$1.pureMarkdownRules.codeBlock,
|
|
18700
18714
|
react: (node, output, state) => {
|
|
18701
18715
|
// ideally this should be a different rule, with only an
|
|
18702
18716
|
// output function, but right now that breaks the parser.
|
|
@@ -18714,7 +18728,7 @@ const rules$1 = {
|
|
|
18714
18728
|
// transform the tree to add lint nodes. This rule is here
|
|
18715
18729
|
// just for the react() function
|
|
18716
18730
|
lint: {
|
|
18717
|
-
...
|
|
18731
|
+
...SimpleMarkdown$1.pureMarkdownRules.lint,
|
|
18718
18732
|
react: (node, output, state) => {
|
|
18719
18733
|
return /*#__PURE__*/React__namespace.createElement(Lint, {
|
|
18720
18734
|
message: node.message,
|
|
@@ -18964,8 +18978,8 @@ const getWidget = name => {
|
|
|
18964
18978
|
const getWidgetExport = name => {
|
|
18965
18979
|
return widgets[name] ?? null;
|
|
18966
18980
|
};
|
|
18967
|
-
const
|
|
18968
|
-
return widgets[name]?.
|
|
18981
|
+
const getWidgetScorer = name => {
|
|
18982
|
+
return widgets[name]?.scorer ?? null;
|
|
18969
18983
|
};
|
|
18970
18984
|
const getEditor = name => {
|
|
18971
18985
|
return ___default["default"].has(editors, name) ? editors[name] : null;
|
|
@@ -19273,7 +19287,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19273
19287
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
19274
19288
|
getWidget: getWidget,
|
|
19275
19289
|
getWidgetExport: getWidgetExport,
|
|
19276
|
-
|
|
19290
|
+
getWidgetScorer: getWidgetScorer,
|
|
19277
19291
|
getEditor: getEditor,
|
|
19278
19292
|
getTransform: getTransform,
|
|
19279
19293
|
getVersion: getVersion,
|
|
@@ -19305,12 +19319,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19305
19319
|
}
|
|
19306
19320
|
let score = null;
|
|
19307
19321
|
const userInput = userInputMap[id];
|
|
19308
|
-
const
|
|
19322
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19309
19323
|
if (widget.type === "group") {
|
|
19310
19324
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19311
19325
|
score = Util.flattenScores(scores);
|
|
19312
|
-
} else if (
|
|
19313
|
-
score =
|
|
19326
|
+
} else if (scorer) {
|
|
19327
|
+
score = scorer(userInput, widget.options, strings, locale);
|
|
19314
19328
|
}
|
|
19315
19329
|
if (score) {
|
|
19316
19330
|
return Util.scoreIsEmpty(score);
|
|
@@ -19335,12 +19349,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19335
19349
|
return;
|
|
19336
19350
|
}
|
|
19337
19351
|
const userInput = userInputMap[id];
|
|
19338
|
-
const
|
|
19352
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19339
19353
|
if (widget.type === "group") {
|
|
19340
19354
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19341
19355
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19342
|
-
} else if (
|
|
19343
|
-
widgetScores[id] =
|
|
19356
|
+
} else if (scorer) {
|
|
19357
|
+
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19344
19358
|
}
|
|
19345
19359
|
});
|
|
19346
19360
|
return widgetScores;
|
|
@@ -19824,9 +19838,9 @@ class Renderer extends React__namespace.Component {
|
|
|
19824
19838
|
// WidgetContainers don't update their widgets' props when
|
|
19825
19839
|
// they are re-rendered, so even if they've been
|
|
19826
19840
|
// re-rendered we need to call these methods on them.
|
|
19827
|
-
|
|
19841
|
+
this.widgetIds.forEach(id => {
|
|
19828
19842
|
const container = this._widgetContainers.get(makeContainerId(id));
|
|
19829
|
-
container
|
|
19843
|
+
container?.replaceWidgetProps(this.getWidgetProps(id));
|
|
19830
19844
|
});
|
|
19831
19845
|
if (this.props.serializedState && !___default["default"].isEqual(this.props.serializedState, this.getSerializedState())) {
|
|
19832
19846
|
this.restoreSerializedState(this.props.serializedState);
|
|
@@ -19965,25 +19979,25 @@ class Renderer extends React__namespace.Component {
|
|
|
19965
19979
|
}
|
|
19966
19980
|
return null;
|
|
19967
19981
|
};
|
|
19968
|
-
getWidgetProps
|
|
19982
|
+
getWidgetProps(widgetId) {
|
|
19969
19983
|
var _this = this;
|
|
19970
19984
|
const apiOptions = this.getApiOptions();
|
|
19971
|
-
const widgetProps = this.state.widgetProps[
|
|
19985
|
+
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19972
19986
|
|
|
19973
19987
|
// The widget needs access to its "rubric" at all times when in review
|
|
19974
19988
|
// mode (which is really just part of its widget info).
|
|
19975
|
-
const widgetInfo = this.state.widgetInfo[
|
|
19989
|
+
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19976
19990
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
19977
19991
|
if (!this._interactionTrackers) {
|
|
19978
19992
|
this._interactionTrackers = {};
|
|
19979
19993
|
}
|
|
19980
|
-
let interactionTracker = this._interactionTrackers[
|
|
19994
|
+
let interactionTracker = this._interactionTrackers[widgetId];
|
|
19981
19995
|
if (!interactionTracker) {
|
|
19982
|
-
interactionTracker = this._interactionTrackers[
|
|
19996
|
+
interactionTracker = this._interactionTrackers[widgetId] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, widgetId, getTracking(widgetInfo && widgetInfo.type));
|
|
19983
19997
|
}
|
|
19984
19998
|
return {
|
|
19985
19999
|
...widgetProps,
|
|
19986
|
-
widgetId:
|
|
20000
|
+
widgetId: widgetId,
|
|
19987
20001
|
alignment: widgetInfo && widgetInfo.alignment,
|
|
19988
20002
|
static: widgetInfo?.static,
|
|
19989
20003
|
problemNum: this.props.problemNum,
|
|
@@ -19991,19 +20005,19 @@ class Renderer extends React__namespace.Component {
|
|
|
19991
20005
|
keypadElement: this.props.keypadElement,
|
|
19992
20006
|
questionCompleted: this.props.questionCompleted,
|
|
19993
20007
|
showSolutions: this.props.showSolutions,
|
|
19994
|
-
onFocus: ___default["default"].partial(this._onWidgetFocus,
|
|
19995
|
-
onBlur: ___default["default"].partial(this._onWidgetBlur,
|
|
20008
|
+
onFocus: ___default["default"].partial(this._onWidgetFocus, widgetId),
|
|
20009
|
+
onBlur: ___default["default"].partial(this._onWidgetBlur, widgetId),
|
|
19996
20010
|
findWidgets: this.findWidgets,
|
|
19997
20011
|
reviewModeRubric: reviewModeRubric,
|
|
19998
20012
|
reviewMode: this.props.reviewMode,
|
|
19999
20013
|
onChange: function (newProps, cb) {
|
|
20000
20014
|
let silent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
20001
|
-
_this._setWidgetProps(
|
|
20015
|
+
_this._setWidgetProps(widgetId, newProps, cb, silent);
|
|
20002
20016
|
},
|
|
20003
20017
|
trackInteraction: interactionTracker.track,
|
|
20004
|
-
isLastUsedWidget:
|
|
20018
|
+
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20005
20019
|
};
|
|
20006
|
-
}
|
|
20020
|
+
}
|
|
20007
20021
|
|
|
20008
20022
|
/**
|
|
20009
20023
|
* Serializes the questions state so it can be recovered.
|
|
@@ -20892,6 +20906,25 @@ class Renderer extends React__namespace.Component {
|
|
|
20892
20906
|
return this.widgetIds;
|
|
20893
20907
|
};
|
|
20894
20908
|
|
|
20909
|
+
/**
|
|
20910
|
+
* Returns a JSON representation of the content and widgets
|
|
20911
|
+
* that can be passed to an LLM for prompt context.
|
|
20912
|
+
*/
|
|
20913
|
+
getPromptJSON() {
|
|
20914
|
+
const {
|
|
20915
|
+
content
|
|
20916
|
+
} = this.props;
|
|
20917
|
+
const widgetJSON = {};
|
|
20918
|
+
this.widgetIds.forEach(id => {
|
|
20919
|
+
const widget = this.getWidgetInstance(id);
|
|
20920
|
+
widgetJSON[id] = widget?.getPromptJSON?.() || {};
|
|
20921
|
+
});
|
|
20922
|
+
return {
|
|
20923
|
+
content,
|
|
20924
|
+
widgets: widgetJSON
|
|
20925
|
+
};
|
|
20926
|
+
}
|
|
20927
|
+
|
|
20895
20928
|
/**
|
|
20896
20929
|
* Returns an object mapping from widget ID to perseus-style score.
|
|
20897
20930
|
* The keys of this object are the values of the array returned
|
|
@@ -21618,6 +21651,19 @@ SimpleKeypadInput.propTypes = {
|
|
|
21618
21651
|
value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
|
|
21619
21652
|
};
|
|
21620
21653
|
|
|
21654
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
21655
|
+
return {
|
|
21656
|
+
type: "input-number",
|
|
21657
|
+
options: {
|
|
21658
|
+
simplify: renderProps.simplify,
|
|
21659
|
+
answerType: renderProps.answerType
|
|
21660
|
+
},
|
|
21661
|
+
userInput: {
|
|
21662
|
+
value: userInput.currentValue
|
|
21663
|
+
}
|
|
21664
|
+
};
|
|
21665
|
+
};
|
|
21666
|
+
|
|
21621
21667
|
/*
|
|
21622
21668
|
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21623
21669
|
* curly brackets.
|
|
@@ -21808,7 +21854,7 @@ const answerTypes = {
|
|
|
21808
21854
|
forms: "pi"
|
|
21809
21855
|
}
|
|
21810
21856
|
};
|
|
21811
|
-
function
|
|
21857
|
+
function scoreInputNumber(userInput, rubric, strings) {
|
|
21812
21858
|
if (rubric.answerType == null) {
|
|
21813
21859
|
rubric.answerType = "number";
|
|
21814
21860
|
}
|
|
@@ -21939,6 +21985,9 @@ class InputNumber extends React__namespace.Component {
|
|
|
21939
21985
|
getUserInput() {
|
|
21940
21986
|
return InputNumber.getUserInputFromProps(this.props);
|
|
21941
21987
|
}
|
|
21988
|
+
getPromptJSON() {
|
|
21989
|
+
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21990
|
+
}
|
|
21942
21991
|
examples = () => {
|
|
21943
21992
|
const {
|
|
21944
21993
|
strings
|
|
@@ -22038,7 +22087,7 @@ var InputNumber$1 = {
|
|
|
22038
22087
|
widget: InputNumber,
|
|
22039
22088
|
transform: propTransform$3,
|
|
22040
22089
|
isLintable: true,
|
|
22041
|
-
|
|
22090
|
+
scorer: scoreInputNumber,
|
|
22042
22091
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22043
22092
|
if (rubric.value == null) {
|
|
22044
22093
|
return;
|
|
@@ -22051,6 +22100,16 @@ var InputNumber$1 = {
|
|
|
22051
22100
|
}
|
|
22052
22101
|
};
|
|
22053
22102
|
|
|
22103
|
+
const getPromptJSON$r = (renderProps, userInput) => {
|
|
22104
|
+
return {
|
|
22105
|
+
type: "numeric-input",
|
|
22106
|
+
label: renderProps.labelText,
|
|
22107
|
+
userInput: {
|
|
22108
|
+
value: userInput.currentValue
|
|
22109
|
+
}
|
|
22110
|
+
};
|
|
22111
|
+
};
|
|
22112
|
+
|
|
22054
22113
|
const ParseTex = TexWrangler.parseTex;
|
|
22055
22114
|
const answerFormButtons = [{
|
|
22056
22115
|
title: "Integers",
|
|
@@ -22109,7 +22168,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
22109
22168
|
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
22110
22169
|
return value;
|
|
22111
22170
|
}
|
|
22112
|
-
function
|
|
22171
|
+
function scoreNumericInput(userInput, rubric, strings) {
|
|
22113
22172
|
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
22114
22173
|
// Don't default to validating the answer as a pi answer
|
|
22115
22174
|
// if answerForm isn't set on the answer
|
|
@@ -22271,6 +22330,9 @@ class NumericInput extends React__namespace.Component {
|
|
|
22271
22330
|
getUserInput() {
|
|
22272
22331
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22273
22332
|
}
|
|
22333
|
+
getPromptJSON() {
|
|
22334
|
+
return getPromptJSON$r(this.props, this.getUserInput());
|
|
22335
|
+
}
|
|
22274
22336
|
handleChange = (newValue, cb) => {
|
|
22275
22337
|
this.props.onChange({
|
|
22276
22338
|
currentValue: newValue
|
|
@@ -22396,7 +22458,7 @@ var NumericInput$1 = {
|
|
|
22396
22458
|
widget: NumericInput,
|
|
22397
22459
|
transform: propsTransform,
|
|
22398
22460
|
isLintable: true,
|
|
22399
|
-
|
|
22461
|
+
scorer: scoreNumericInput,
|
|
22400
22462
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22401
22463
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22402
22464
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -22427,15 +22489,43 @@ var NumericInput$1 = {
|
|
|
22427
22489
|
}
|
|
22428
22490
|
};
|
|
22429
22491
|
|
|
22492
|
+
const getPromptJSON$q = (renderProps, userInput) => {
|
|
22493
|
+
const choices = renderProps.choices || [];
|
|
22494
|
+
const options = choices.map(choice => {
|
|
22495
|
+
return {
|
|
22496
|
+
value: choice.content
|
|
22497
|
+
};
|
|
22498
|
+
});
|
|
22499
|
+
return {
|
|
22500
|
+
type: "radio",
|
|
22501
|
+
hasNoneOfTheAbove: !!renderProps.hasNoneOfTheAbove,
|
|
22502
|
+
options,
|
|
22503
|
+
userInput: {
|
|
22504
|
+
selectedOptions: userInput.choicesSelected.slice()
|
|
22505
|
+
}
|
|
22506
|
+
};
|
|
22507
|
+
};
|
|
22508
|
+
|
|
22509
|
+
const getPromptJSON$p = renderProps => {
|
|
22510
|
+
return {
|
|
22511
|
+
type: "passage-ref",
|
|
22512
|
+
options: {
|
|
22513
|
+
passageNumber: renderProps.passageNumber,
|
|
22514
|
+
referenceNumber: renderProps.referenceNumber,
|
|
22515
|
+
summaryText: renderProps.summaryText
|
|
22516
|
+
}
|
|
22517
|
+
};
|
|
22518
|
+
};
|
|
22519
|
+
|
|
22430
22520
|
/**
|
|
22431
|
-
* Several widgets don't have "right"/"wrong"
|
|
22521
|
+
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
22432
22522
|
* so this just says to move on past those widgets
|
|
22433
22523
|
*
|
|
22434
22524
|
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
22435
|
-
*
|
|
22525
|
+
* scoring logic and the thing scoring an exercise
|
|
22436
22526
|
* should just know to skip these
|
|
22437
22527
|
*/
|
|
22438
|
-
function
|
|
22528
|
+
function scoreNoop() {
|
|
22439
22529
|
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
22440
22530
|
return {
|
|
22441
22531
|
type: "points",
|
|
@@ -23829,6 +23919,17 @@ const styles$x = aphrodite.StyleSheet.create({
|
|
|
23829
23919
|
}
|
|
23830
23920
|
});
|
|
23831
23921
|
|
|
23922
|
+
const getPromptJSON$o = renderProps => {
|
|
23923
|
+
return {
|
|
23924
|
+
type: "passage",
|
|
23925
|
+
options: {
|
|
23926
|
+
passageTitle: renderProps.passageTitle,
|
|
23927
|
+
passageText: renderProps.passageText,
|
|
23928
|
+
footnotes: renderProps.footnotes
|
|
23929
|
+
}
|
|
23930
|
+
};
|
|
23931
|
+
};
|
|
23932
|
+
|
|
23832
23933
|
function getInitialParseState() {
|
|
23833
23934
|
return {
|
|
23834
23935
|
currentRef: [],
|
|
@@ -24461,6 +24562,9 @@ class Passage extends React__namespace.Component {
|
|
|
24461
24562
|
content: refContent
|
|
24462
24563
|
};
|
|
24463
24564
|
}
|
|
24565
|
+
getPromptJSON() {
|
|
24566
|
+
return getPromptJSON$o(this.props);
|
|
24567
|
+
}
|
|
24464
24568
|
|
|
24465
24569
|
/**
|
|
24466
24570
|
* Rendering
|
|
@@ -24606,7 +24710,7 @@ var Passage$1 = {
|
|
|
24606
24710
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
24607
24711
|
},
|
|
24608
24712
|
isLintable: true,
|
|
24609
|
-
|
|
24713
|
+
scorer: () => scoreNoop()
|
|
24610
24714
|
};
|
|
24611
24715
|
|
|
24612
24716
|
/**
|
|
@@ -24671,6 +24775,9 @@ class PassageRef extends React__namespace.Component {
|
|
|
24671
24775
|
return change.apply(_this, args);
|
|
24672
24776
|
};
|
|
24673
24777
|
})();
|
|
24778
|
+
getPromptJSON() {
|
|
24779
|
+
return getPromptJSON$p(this.props);
|
|
24780
|
+
}
|
|
24674
24781
|
_deferredUpdateRange = () => {
|
|
24675
24782
|
___default["default"].defer(this._updateRange);
|
|
24676
24783
|
};
|
|
@@ -24740,8 +24847,8 @@ var PassageRef$1 = {
|
|
|
24740
24847
|
major: 0,
|
|
24741
24848
|
minor: 1
|
|
24742
24849
|
},
|
|
24743
|
-
// TODO: things that aren't interactive shouldn't need
|
|
24744
|
-
|
|
24850
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
24851
|
+
scorer: () => scoreNoop()
|
|
24745
24852
|
};
|
|
24746
24853
|
|
|
24747
24854
|
/**
|
|
@@ -26109,7 +26216,7 @@ const styles$p = aphrodite.StyleSheet.create({
|
|
|
26109
26216
|
}
|
|
26110
26217
|
});
|
|
26111
26218
|
|
|
26112
|
-
function
|
|
26219
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
26113
26220
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
26114
26221
|
return sum + (selected ? 1 : 0);
|
|
26115
26222
|
}, 0);
|
|
@@ -26119,10 +26226,10 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26119
26226
|
message: null
|
|
26120
26227
|
};
|
|
26121
26228
|
}
|
|
26122
|
-
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
if (
|
|
26229
|
+
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
26230
|
+
return currentChoice.correct ? sum + 1 : sum;
|
|
26231
|
+
}, 0);
|
|
26232
|
+
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
26126
26233
|
return {
|
|
26127
26234
|
type: "invalid",
|
|
26128
26235
|
message: strings.chooseCorrectNum
|
|
@@ -26130,9 +26237,8 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26130
26237
|
// If NOTA and some other answer are checked, ...
|
|
26131
26238
|
}
|
|
26132
26239
|
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
26240
|
+
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
26241
|
+
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
26136
26242
|
return {
|
|
26137
26243
|
type: "invalid",
|
|
26138
26244
|
message: strings.notNoneOfTheAbove
|
|
@@ -26140,9 +26246,7 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
26140
26246
|
}
|
|
26141
26247
|
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
26142
26248
|
let isCorrect;
|
|
26143
|
-
|
|
26144
|
-
// instead of the userInput?
|
|
26145
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
26249
|
+
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
26146
26250
|
isCorrect = rubric.choices.every((choice, j) => {
|
|
26147
26251
|
return i === j || !choice.correct;
|
|
26148
26252
|
});
|
|
@@ -26174,31 +26278,19 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26174
26278
|
showSolutions: "none"
|
|
26175
26279
|
};
|
|
26176
26280
|
static getUserInputFromProps(props) {
|
|
26281
|
+
let unshuffle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
26177
26282
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
26178
26283
|
// future timeline implementers: this used to be {value: i} before
|
|
26179
26284
|
// multiple select was added)
|
|
26180
26285
|
if (props.choiceStates) {
|
|
26181
|
-
let noneOfTheAboveIndex = null;
|
|
26182
|
-
let noneOfTheAboveSelected = false;
|
|
26183
26286
|
const choiceStates = props.choiceStates;
|
|
26184
26287
|
const choicesSelected = choiceStates.map(() => false);
|
|
26185
|
-
const numCorrect = props.numCorrect;
|
|
26186
26288
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26187
|
-
const index = props.choices[i].originalIndex;
|
|
26289
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26188
26290
|
choicesSelected[index] = choiceStates[i].selected;
|
|
26189
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26190
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26191
|
-
noneOfTheAboveIndex = index;
|
|
26192
|
-
if (choicesSelected[i]) {
|
|
26193
|
-
noneOfTheAboveSelected = true;
|
|
26194
|
-
}
|
|
26195
|
-
}
|
|
26196
26291
|
}
|
|
26197
26292
|
return {
|
|
26198
|
-
choicesSelected
|
|
26199
|
-
numCorrect,
|
|
26200
|
-
noneOfTheAboveIndex,
|
|
26201
|
-
noneOfTheAboveSelected
|
|
26293
|
+
choicesSelected
|
|
26202
26294
|
};
|
|
26203
26295
|
// Support legacy choiceState implementation
|
|
26204
26296
|
}
|
|
@@ -26207,27 +26299,14 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26207
26299
|
values
|
|
26208
26300
|
} = props;
|
|
26209
26301
|
if (values) {
|
|
26210
|
-
let noneOfTheAboveIndex = null;
|
|
26211
|
-
let noneOfTheAboveSelected = false;
|
|
26212
26302
|
const choicesSelected = [...values];
|
|
26213
|
-
const numCorrect = props.numCorrect;
|
|
26214
26303
|
const valuesLength = values.length;
|
|
26215
26304
|
for (let i = 0; i < valuesLength; i++) {
|
|
26216
|
-
const index = props.choices[i].originalIndex;
|
|
26305
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26217
26306
|
choicesSelected[index] = values[i];
|
|
26218
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26219
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26220
|
-
noneOfTheAboveIndex = index;
|
|
26221
|
-
if (choicesSelected[i]) {
|
|
26222
|
-
noneOfTheAboveSelected = true;
|
|
26223
|
-
}
|
|
26224
|
-
}
|
|
26225
26307
|
}
|
|
26226
26308
|
return {
|
|
26227
|
-
choicesSelected
|
|
26228
|
-
noneOfTheAboveIndex,
|
|
26229
|
-
noneOfTheAboveSelected,
|
|
26230
|
-
numCorrect
|
|
26309
|
+
choicesSelected
|
|
26231
26310
|
};
|
|
26232
26311
|
}
|
|
26233
26312
|
// Nothing checked
|
|
@@ -26355,6 +26434,10 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26355
26434
|
getUserInput() {
|
|
26356
26435
|
return Radio$1.getUserInputFromProps(this.props);
|
|
26357
26436
|
}
|
|
26437
|
+
getPromptJSON() {
|
|
26438
|
+
const userInput = Radio$1.getUserInputFromProps(this.props, false);
|
|
26439
|
+
return getPromptJSON$q(this.props, userInput);
|
|
26440
|
+
}
|
|
26358
26441
|
|
|
26359
26442
|
/**
|
|
26360
26443
|
* Turn on rationale display for the currently selected choices. Note that
|
|
@@ -26367,7 +26450,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
26367
26450
|
choiceStates
|
|
26368
26451
|
} = this.props;
|
|
26369
26452
|
if (choiceStates) {
|
|
26370
|
-
const score =
|
|
26453
|
+
const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
|
|
26371
26454
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26372
26455
|
const newStates = choiceStates.map(state => ({
|
|
26373
26456
|
...state,
|
|
@@ -26649,13 +26732,26 @@ var Radio = {
|
|
|
26649
26732
|
},
|
|
26650
26733
|
propUpgrades: propUpgrades$1,
|
|
26651
26734
|
isLintable: true,
|
|
26652
|
-
|
|
26735
|
+
scorer: scoreRadio
|
|
26653
26736
|
};
|
|
26654
26737
|
|
|
26655
26738
|
/* eslint-disable import/no-named-as-default */
|
|
26656
26739
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
26657
26740
|
|
|
26658
|
-
|
|
26741
|
+
const getPromptJSON$n = (renderProps, userInput) => {
|
|
26742
|
+
return {
|
|
26743
|
+
type: "categorizer",
|
|
26744
|
+
options: {
|
|
26745
|
+
items: renderProps.items,
|
|
26746
|
+
categories: renderProps.categories
|
|
26747
|
+
},
|
|
26748
|
+
userInput: {
|
|
26749
|
+
itemToCategoryMapping: userInput.values
|
|
26750
|
+
}
|
|
26751
|
+
};
|
|
26752
|
+
};
|
|
26753
|
+
|
|
26754
|
+
function scoreCategorizer(userInput, rubric, strings) {
|
|
26659
26755
|
let completed = true;
|
|
26660
26756
|
let allCorrect = true;
|
|
26661
26757
|
rubric.values.forEach((value, i) => {
|
|
@@ -26710,6 +26806,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
26710
26806
|
getUserInput() {
|
|
26711
26807
|
return Categorizer.getUserInputFromProps(this.props);
|
|
26712
26808
|
}
|
|
26809
|
+
getPromptJSON() {
|
|
26810
|
+
return getPromptJSON$n(this.props, this.getUserInput());
|
|
26811
|
+
}
|
|
26713
26812
|
onChange(itemNum, catNum) {
|
|
26714
26813
|
const values = [...this.props.values];
|
|
26715
26814
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -26856,7 +26955,7 @@ var Categorizer$1 = {
|
|
|
26856
26955
|
return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
26857
26956
|
},
|
|
26858
26957
|
isLintable: true,
|
|
26859
|
-
|
|
26958
|
+
scorer: scoreCategorizer
|
|
26860
26959
|
};
|
|
26861
26960
|
|
|
26862
26961
|
/**
|
|
@@ -26906,7 +27005,20 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
26906
27005
|
*/
|
|
26907
27006
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
26908
27007
|
|
|
26909
|
-
function
|
|
27008
|
+
const getUnsupportedPromptJSON = function (widgetType) {
|
|
27009
|
+
let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
|
|
27010
|
+
return {
|
|
27011
|
+
type: widgetType,
|
|
27012
|
+
isSupported: false,
|
|
27013
|
+
message
|
|
27014
|
+
};
|
|
27015
|
+
};
|
|
27016
|
+
|
|
27017
|
+
const getPromptJSON$m = () => {
|
|
27018
|
+
return getUnsupportedPromptJSON("cs-program");
|
|
27019
|
+
};
|
|
27020
|
+
|
|
27021
|
+
function scoreCSProgram(state) {
|
|
26910
27022
|
// The iframe can tell us whether it's correct or incorrect,
|
|
26911
27023
|
// and pass an optional message
|
|
26912
27024
|
if (state.status === "correct") {
|
|
@@ -27002,6 +27114,9 @@ class CSProgram extends React__namespace.Component {
|
|
|
27002
27114
|
message: this.props.message
|
|
27003
27115
|
};
|
|
27004
27116
|
}
|
|
27117
|
+
getPromptJSON() {
|
|
27118
|
+
return getPromptJSON$m();
|
|
27119
|
+
}
|
|
27005
27120
|
render() {
|
|
27006
27121
|
if (!this.props.programID) {
|
|
27007
27122
|
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
@@ -27077,7 +27192,15 @@ var CSProgram$1 = {
|
|
|
27077
27192
|
supportedAlignments: ["block", "full-width"],
|
|
27078
27193
|
widget: CSProgram,
|
|
27079
27194
|
hidden: true,
|
|
27080
|
-
|
|
27195
|
+
scorer: scoreCSProgram
|
|
27196
|
+
};
|
|
27197
|
+
|
|
27198
|
+
const getPromptJSON$l = renderProps => {
|
|
27199
|
+
return {
|
|
27200
|
+
type: "definition",
|
|
27201
|
+
definition: renderProps.definition,
|
|
27202
|
+
togglePrompt: renderProps.togglePrompt
|
|
27203
|
+
};
|
|
27081
27204
|
};
|
|
27082
27205
|
|
|
27083
27206
|
class Definition extends React__namespace.Component {
|
|
@@ -27090,6 +27213,9 @@ class Definition extends React__namespace.Component {
|
|
|
27090
27213
|
// this just helps with TS weak typing when a Widget
|
|
27091
27214
|
// doesn't implement any Widget methods
|
|
27092
27215
|
isWidget = true;
|
|
27216
|
+
getPromptJSON() {
|
|
27217
|
+
return getPromptJSON$l(this.props);
|
|
27218
|
+
}
|
|
27093
27219
|
render() {
|
|
27094
27220
|
return /*#__PURE__*/React__namespace.createElement(DefinitionConsumer, null, _ref => {
|
|
27095
27221
|
let {
|
|
@@ -27146,8 +27272,8 @@ var Definition$1 = {
|
|
|
27146
27272
|
defaultAlignment: "inline",
|
|
27147
27273
|
widget: Definition,
|
|
27148
27274
|
transform: x => x,
|
|
27149
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27150
|
-
|
|
27275
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27276
|
+
scorer: () => scoreNoop()
|
|
27151
27277
|
};
|
|
27152
27278
|
|
|
27153
27279
|
// The props are type `any` on purpose so that this can receive props
|
|
@@ -27175,11 +27301,23 @@ var DeprecatedStandin$1 = {
|
|
|
27175
27301
|
displayName: "Deprecated Standin",
|
|
27176
27302
|
widget: DeprecatedStandin,
|
|
27177
27303
|
hidden: true,
|
|
27178
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27179
|
-
|
|
27304
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27305
|
+
scorer: () => scoreNoop(1)
|
|
27306
|
+
};
|
|
27307
|
+
|
|
27308
|
+
const getPromptJSON$k = (renderProps, userInput) => {
|
|
27309
|
+
return {
|
|
27310
|
+
type: "dropdown",
|
|
27311
|
+
options: {
|
|
27312
|
+
items: renderProps.choices
|
|
27313
|
+
},
|
|
27314
|
+
userInput: {
|
|
27315
|
+
selectedIndex: userInput.value - 1 // Offset to account for placeholder
|
|
27316
|
+
}
|
|
27317
|
+
};
|
|
27180
27318
|
};
|
|
27181
27319
|
|
|
27182
|
-
function
|
|
27320
|
+
function scoreDropdown(userInput, rubric) {
|
|
27183
27321
|
const selected = userInput.value;
|
|
27184
27322
|
if (selected === 0) {
|
|
27185
27323
|
return {
|
|
@@ -27225,6 +27363,9 @@ class Dropdown extends React__namespace.Component {
|
|
|
27225
27363
|
value: this.props.selected
|
|
27226
27364
|
};
|
|
27227
27365
|
}
|
|
27366
|
+
getPromptJSON() {
|
|
27367
|
+
return getPromptJSON$k(this.props, this.getUserInput());
|
|
27368
|
+
}
|
|
27228
27369
|
render() {
|
|
27229
27370
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
27230
27371
|
key: "placeholder",
|
|
@@ -27266,7 +27407,15 @@ var Dropdown$1 = {
|
|
|
27266
27407
|
accessible: true,
|
|
27267
27408
|
widget: Dropdown,
|
|
27268
27409
|
transform: optionsTransform,
|
|
27269
|
-
|
|
27410
|
+
scorer: scoreDropdown
|
|
27411
|
+
};
|
|
27412
|
+
|
|
27413
|
+
const getPromptJSON$j = renderProps => {
|
|
27414
|
+
return {
|
|
27415
|
+
type: "explanation",
|
|
27416
|
+
showPrompt: renderProps.showPrompt,
|
|
27417
|
+
explanation: renderProps.explanation
|
|
27418
|
+
};
|
|
27270
27419
|
};
|
|
27271
27420
|
|
|
27272
27421
|
// transform = _.identity
|
|
@@ -27315,6 +27464,9 @@ class Explanation extends React__namespace.Component {
|
|
|
27315
27464
|
});
|
|
27316
27465
|
this.props.trackInteraction();
|
|
27317
27466
|
};
|
|
27467
|
+
getPromptJSON() {
|
|
27468
|
+
return getPromptJSON$j(this.props);
|
|
27469
|
+
}
|
|
27318
27470
|
render() {
|
|
27319
27471
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
27320
27472
|
const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
|
|
@@ -27421,8 +27573,26 @@ var Explanation$1 = {
|
|
|
27421
27573
|
widget: Explanation,
|
|
27422
27574
|
transform: ___default["default"].identity,
|
|
27423
27575
|
isLintable: true,
|
|
27424
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27425
|
-
|
|
27576
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27577
|
+
scorer: () => scoreNoop()
|
|
27578
|
+
};
|
|
27579
|
+
|
|
27580
|
+
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
27581
|
+
if (!rendererJSON) {
|
|
27582
|
+
return {
|
|
27583
|
+
type: "graded-group",
|
|
27584
|
+
title,
|
|
27585
|
+
content: "",
|
|
27586
|
+
widgets: {},
|
|
27587
|
+
hint: hintRendererJSON
|
|
27588
|
+
};
|
|
27589
|
+
}
|
|
27590
|
+
return {
|
|
27591
|
+
...rendererJSON,
|
|
27592
|
+
title,
|
|
27593
|
+
type: "graded-group",
|
|
27594
|
+
hint: hintRendererJSON
|
|
27595
|
+
};
|
|
27426
27596
|
};
|
|
27427
27597
|
|
|
27428
27598
|
class GradedGroupAnswerBar extends React__namespace.Component {
|
|
@@ -27566,6 +27736,8 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27566
27736
|
message: "",
|
|
27567
27737
|
answerBarState: "HIDDEN"
|
|
27568
27738
|
};
|
|
27739
|
+
rendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27740
|
+
hintRendererRef = /*#__PURE__*/React__namespace.createRef();
|
|
27569
27741
|
shouldComponentUpdate(nextProps, nextState) {
|
|
27570
27742
|
return nextProps !== this.props || nextState !== this.state;
|
|
27571
27743
|
}
|
|
@@ -27591,13 +27763,9 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27591
27763
|
status: GRADING_STATUSES.ungraded,
|
|
27592
27764
|
message: ""
|
|
27593
27765
|
});
|
|
27594
|
-
|
|
27595
|
-
// eslint-disable-next-line react/no-string-refs
|
|
27596
|
-
if (this.refs.renderer) {
|
|
27766
|
+
if (this.rendererRef.current) {
|
|
27597
27767
|
this.change("widgets", this.props.widgets);
|
|
27598
|
-
|
|
27599
|
-
// @ts-expect-error - TS2339 - Property 'emptyWidgets' does not exist on type 'ReactInstance'.
|
|
27600
|
-
const emptyWidgets = this.refs.renderer.emptyWidgets();
|
|
27768
|
+
const emptyWidgets = this.rendererRef.current.emptyWidgets();
|
|
27601
27769
|
const answerable = emptyWidgets.length === 0;
|
|
27602
27770
|
const answerBarState = this.state.answerBarState;
|
|
27603
27771
|
this.setState({
|
|
@@ -27606,12 +27774,10 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27606
27774
|
}
|
|
27607
27775
|
};
|
|
27608
27776
|
_checkAnswer = () => {
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
|
|
27612
|
-
|
|
27613
|
-
// @ts-expect-error - TS2339 - Property 'score' does not exist on type 'ReactInstance'.
|
|
27614
|
-
const score = this.refs.renderer.score();
|
|
27777
|
+
this.rendererRef.current?.showRationalesForCurrentlySelectedChoices();
|
|
27778
|
+
const score = this.rendererRef.current?.score() || {
|
|
27779
|
+
type: "invalid"
|
|
27780
|
+
};
|
|
27615
27781
|
const {
|
|
27616
27782
|
INVALID_MESSAGE_PREFIX,
|
|
27617
27783
|
DEFAULT_INVALID_MESSAGE_1,
|
|
@@ -27632,29 +27798,28 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27632
27798
|
|
|
27633
27799
|
// Mobile API
|
|
27634
27800
|
getInputPaths = () => {
|
|
27635
|
-
|
|
27636
|
-
// @ts-expect-error - TS2339 - Property 'getInputPaths' does not exist on type 'ReactInstance'.
|
|
27637
|
-
return this.refs.renderer.getInputPaths();
|
|
27801
|
+
return this.rendererRef.current?.getInputPaths() || [];
|
|
27638
27802
|
};
|
|
27803
|
+
getPromptJSON() {
|
|
27804
|
+
// If the hint isn't expanded, we can't get the prompt JSON from the rendered widgets.
|
|
27805
|
+
// We'll just pass in the hint content as a string instead.
|
|
27806
|
+
const hint = this.hintRendererRef.current?.getPromptJSON() || {
|
|
27807
|
+
content: this.props.hint?.content || "",
|
|
27808
|
+
widgets: {}
|
|
27809
|
+
};
|
|
27810
|
+
return getPromptJSON$i(this.props.title, this.rendererRef.current?.getPromptJSON(), hint);
|
|
27811
|
+
}
|
|
27639
27812
|
setInputValue = (path, newValue, cb) => {
|
|
27640
|
-
|
|
27641
|
-
// @ts-expect-error - TS2339 - Property 'setInputValue' does not exist on type 'ReactInstance'.
|
|
27642
|
-
return this.refs.renderer.setInputValue(path, newValue, cb);
|
|
27813
|
+
return this.rendererRef.current?.setInputValue(path, newValue, cb);
|
|
27643
27814
|
};
|
|
27644
27815
|
focus = () => {
|
|
27645
|
-
|
|
27646
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
27647
|
-
return this.refs.renderer.focus();
|
|
27816
|
+
return !!this.rendererRef.current?.focus();
|
|
27648
27817
|
};
|
|
27649
27818
|
focusInputPath = path => {
|
|
27650
|
-
|
|
27651
|
-
// @ts-expect-error - TS2339 - Property 'focusPath' does not exist on type 'ReactInstance'.
|
|
27652
|
-
this.refs.renderer.focusPath(path);
|
|
27819
|
+
this.rendererRef.current?.focusPath(path);
|
|
27653
27820
|
};
|
|
27654
27821
|
blurInputPath = path => {
|
|
27655
|
-
|
|
27656
|
-
// @ts-expect-error - TS2339 - Property 'blurPath' does not exist on type 'ReactInstance'.
|
|
27657
|
-
this.refs.renderer.blurPath(path);
|
|
27822
|
+
this.rendererRef.current?.blurPath(path);
|
|
27658
27823
|
};
|
|
27659
27824
|
render() {
|
|
27660
27825
|
const apiOptions = ___default["default"].extend({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
@@ -27712,8 +27877,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27712
27877
|
}, !!this.props.title && /*#__PURE__*/React__namespace.createElement("div", {
|
|
27713
27878
|
className: aphrodite.css(styles$j.title)
|
|
27714
27879
|
}, this.props.title), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props, {
|
|
27715
|
-
|
|
27716
|
-
ref: "renderer",
|
|
27880
|
+
ref: this.rendererRef,
|
|
27717
27881
|
apiOptions: {
|
|
27718
27882
|
...apiOptions,
|
|
27719
27883
|
readOnly
|
|
@@ -27756,8 +27920,7 @@ class GradedGroup extends React__namespace.Component {
|
|
|
27756
27920
|
});
|
|
27757
27921
|
}
|
|
27758
27922
|
}, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
|
|
27759
|
-
|
|
27760
|
-
ref: "hints-renderer",
|
|
27923
|
+
ref: this.hintRendererRef,
|
|
27761
27924
|
apiOptions: apiOptions,
|
|
27762
27925
|
linterContext: this.props.linterContext,
|
|
27763
27926
|
strings: this.context.strings
|
|
@@ -27849,6 +28012,16 @@ var GradedGroup$1 = {
|
|
|
27849
28012
|
isLintable: true
|
|
27850
28013
|
};
|
|
27851
28014
|
|
|
28015
|
+
const getPromptJSON$h = (renderProps, activeGroupJSON) => {
|
|
28016
|
+
return {
|
|
28017
|
+
type: "graded-group-set",
|
|
28018
|
+
options: {
|
|
28019
|
+
groupCount: renderProps.gradedGroups.length,
|
|
28020
|
+
currentGroup: activeGroupJSON
|
|
28021
|
+
}
|
|
28022
|
+
};
|
|
28023
|
+
};
|
|
28024
|
+
|
|
27852
28025
|
class Indicators extends React__namespace.Component {
|
|
27853
28026
|
static contextType = PerseusI18nContext;
|
|
27854
28027
|
handleKeyDown = (e, i) => {
|
|
@@ -27927,6 +28100,10 @@ class GradedGroupSet extends React__namespace.Component {
|
|
|
27927
28100
|
getInputPaths = () => {
|
|
27928
28101
|
return this._childGroup.getInputPaths();
|
|
27929
28102
|
};
|
|
28103
|
+
getPromptJSON() {
|
|
28104
|
+
const activeGroupPromptJSON = this._childGroup.getPromptJSON();
|
|
28105
|
+
return getPromptJSON$h(this.props, activeGroupPromptJSON);
|
|
28106
|
+
}
|
|
27930
28107
|
setInputValue = (path, newValue, cb) => {
|
|
27931
28108
|
return this._childGroup.setInputValue(path, newValue, cb);
|
|
27932
28109
|
};
|
|
@@ -28180,6 +28357,33 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
28180
28357
|
}
|
|
28181
28358
|
});
|
|
28182
28359
|
|
|
28360
|
+
const getPromptJSON$g = (renderProps, userInput) => {
|
|
28361
|
+
const {
|
|
28362
|
+
type,
|
|
28363
|
+
coords
|
|
28364
|
+
} = userInput;
|
|
28365
|
+
const input = {
|
|
28366
|
+
type,
|
|
28367
|
+
coords
|
|
28368
|
+
};
|
|
28369
|
+
if (userInput.type === "logarithm" || userInput.type === "exponential") {
|
|
28370
|
+
input["asymptote"] = userInput.asymptote;
|
|
28371
|
+
}
|
|
28372
|
+
return {
|
|
28373
|
+
type: "grapher",
|
|
28374
|
+
options: {
|
|
28375
|
+
availableTypes: renderProps.availableTypes,
|
|
28376
|
+
range: renderProps.graph.range,
|
|
28377
|
+
labels: renderProps.graph.labels,
|
|
28378
|
+
tickStep: renderProps.graph.step,
|
|
28379
|
+
gridStep: renderProps.graph.gridStep,
|
|
28380
|
+
snapStep: renderProps.graph.snapStep,
|
|
28381
|
+
backgroundImageUrl: renderProps.graph.backgroundImage.url
|
|
28382
|
+
},
|
|
28383
|
+
userInput: input
|
|
28384
|
+
};
|
|
28385
|
+
};
|
|
28386
|
+
|
|
28183
28387
|
/* eslint-disable one-var */
|
|
28184
28388
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
28185
28389
|
const Plot$1 = Graphie.Plot;
|
|
@@ -28703,7 +28907,7 @@ function getCoefficientsByType(data) {
|
|
|
28703
28907
|
throw new perseusCore.PerseusError("Invalid grapher type", perseusCore.Errors.InvalidInput);
|
|
28704
28908
|
}
|
|
28705
28909
|
}
|
|
28706
|
-
function
|
|
28910
|
+
function scoreGrapher(userInput, rubric) {
|
|
28707
28911
|
if (userInput.type !== rubric.correct.type) {
|
|
28708
28912
|
return {
|
|
28709
28913
|
type: "points",
|
|
@@ -29054,6 +29258,9 @@ class Grapher extends React__namespace.Component {
|
|
|
29054
29258
|
getUserInput() {
|
|
29055
29259
|
return Grapher.getUserInputFromProps(this.props);
|
|
29056
29260
|
}
|
|
29261
|
+
getPromptJSON() {
|
|
29262
|
+
return getPromptJSON$g(this.props, this.getUserInput());
|
|
29263
|
+
}
|
|
29057
29264
|
render() {
|
|
29058
29265
|
const type = this.props.plot.type;
|
|
29059
29266
|
const coords = this.props.plot.coords;
|
|
@@ -29143,7 +29350,21 @@ var Grapher$1 = {
|
|
|
29143
29350
|
widget: Grapher,
|
|
29144
29351
|
transform: propTransform$2,
|
|
29145
29352
|
staticTransform: staticTransform$4,
|
|
29146
|
-
|
|
29353
|
+
scorer: scoreGrapher
|
|
29354
|
+
};
|
|
29355
|
+
|
|
29356
|
+
const getPromptJSON$f = rendererJSON => {
|
|
29357
|
+
if (!rendererJSON) {
|
|
29358
|
+
return {
|
|
29359
|
+
type: "group",
|
|
29360
|
+
content: "",
|
|
29361
|
+
widgets: {}
|
|
29362
|
+
};
|
|
29363
|
+
}
|
|
29364
|
+
return {
|
|
29365
|
+
...rendererJSON,
|
|
29366
|
+
type: "group"
|
|
29367
|
+
};
|
|
29147
29368
|
};
|
|
29148
29369
|
|
|
29149
29370
|
// exports has no 'transform'
|
|
@@ -29182,6 +29403,9 @@ class Group extends React__namespace.Component {
|
|
|
29182
29403
|
getUserInput() {
|
|
29183
29404
|
return this.rendererRef?.getUserInput();
|
|
29184
29405
|
}
|
|
29406
|
+
getPromptJSON() {
|
|
29407
|
+
return getPromptJSON$f(this.rendererRef?.getPromptJSON());
|
|
29408
|
+
}
|
|
29185
29409
|
getSerializedState = () => {
|
|
29186
29410
|
return this.rendererRef?.getSerializedState();
|
|
29187
29411
|
};
|
|
@@ -29295,7 +29519,11 @@ var Group$1 = {
|
|
|
29295
29519
|
isLintable: true
|
|
29296
29520
|
};
|
|
29297
29521
|
|
|
29298
|
-
|
|
29522
|
+
const getPromptJSON$e = () => {
|
|
29523
|
+
return getUnsupportedPromptJSON("iframe");
|
|
29524
|
+
};
|
|
29525
|
+
|
|
29526
|
+
function scoreIframe(userInput) {
|
|
29299
29527
|
// The iframe can tell us whether it's correct or incorrect,
|
|
29300
29528
|
// and pass an optional message
|
|
29301
29529
|
if (userInput.status === "correct") {
|
|
@@ -29352,6 +29580,9 @@ class Iframe extends React__namespace.Component {
|
|
|
29352
29580
|
message: this.props.message
|
|
29353
29581
|
};
|
|
29354
29582
|
}
|
|
29583
|
+
getPromptJSON() {
|
|
29584
|
+
return getPromptJSON$e();
|
|
29585
|
+
}
|
|
29355
29586
|
handleMessageEvent = e => {
|
|
29356
29587
|
// We receive data from the iframe that contains {passed: true/false}
|
|
29357
29588
|
// and use that to set the status
|
|
@@ -29449,7 +29680,19 @@ var Iframe$1 = {
|
|
|
29449
29680
|
widget: Iframe,
|
|
29450
29681
|
// Let's not expose it to all content creators yet
|
|
29451
29682
|
hidden: true,
|
|
29452
|
-
|
|
29683
|
+
scorer: scoreIframe
|
|
29684
|
+
};
|
|
29685
|
+
|
|
29686
|
+
const getPromptJSON$d = renderProps => {
|
|
29687
|
+
return {
|
|
29688
|
+
type: "image",
|
|
29689
|
+
options: {
|
|
29690
|
+
altText: renderProps.alt,
|
|
29691
|
+
title: renderProps.title,
|
|
29692
|
+
caption: renderProps.caption,
|
|
29693
|
+
imageUrl: renderProps.backgroundImage.url
|
|
29694
|
+
}
|
|
29695
|
+
};
|
|
29453
29696
|
};
|
|
29454
29697
|
|
|
29455
29698
|
const defaultBoxSize = 400;
|
|
@@ -29490,6 +29733,9 @@ class ImageWidget extends React__namespace.Component {
|
|
|
29490
29733
|
return change.apply(_this, args);
|
|
29491
29734
|
};
|
|
29492
29735
|
})();
|
|
29736
|
+
getPromptJSON() {
|
|
29737
|
+
return getPromptJSON$d(this.props);
|
|
29738
|
+
}
|
|
29493
29739
|
render() {
|
|
29494
29740
|
let image;
|
|
29495
29741
|
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
@@ -29636,8 +29882,12 @@ var Image$1 = {
|
|
|
29636
29882
|
displayName: "Image",
|
|
29637
29883
|
widget: ImageWidget,
|
|
29638
29884
|
isLintable: true,
|
|
29639
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29640
|
-
|
|
29885
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
29886
|
+
scorer: () => scoreNoop()
|
|
29887
|
+
};
|
|
29888
|
+
|
|
29889
|
+
const getPromptJSON$c = () => {
|
|
29890
|
+
return getUnsupportedPromptJSON("interaction");
|
|
29641
29891
|
};
|
|
29642
29892
|
|
|
29643
29893
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -29826,6 +30076,9 @@ class Interaction extends React__namespace.Component {
|
|
|
29826
30076
|
return change.apply(_this, args);
|
|
29827
30077
|
};
|
|
29828
30078
|
})();
|
|
30079
|
+
getPromptJSON() {
|
|
30080
|
+
return getPromptJSON$c();
|
|
30081
|
+
}
|
|
29829
30082
|
render() {
|
|
29830
30083
|
const range = this.props.graph.range;
|
|
29831
30084
|
let labels = this.props.graph.labels;
|
|
@@ -30234,8 +30487,8 @@ var Interactive = {
|
|
|
30234
30487
|
widget: Interaction,
|
|
30235
30488
|
transform: ___default["default"].identity,
|
|
30236
30489
|
hidden: true,
|
|
30237
|
-
// TODO: things that aren't interactive shouldn't need
|
|
30238
|
-
|
|
30490
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
30491
|
+
scorer: () => scoreNoop()
|
|
30239
30492
|
};
|
|
30240
30493
|
|
|
30241
30494
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -30539,6 +30792,137 @@ class Graph extends React__namespace.Component {
|
|
|
30539
30792
|
}
|
|
30540
30793
|
}
|
|
30541
30794
|
|
|
30795
|
+
const getPromptJSON$b = (props, userInput) => {
|
|
30796
|
+
return {
|
|
30797
|
+
type: "interactive-graph",
|
|
30798
|
+
options: {
|
|
30799
|
+
graph: getGraphOptionsForProps(props),
|
|
30800
|
+
backgroundImageUrl: props.backgroundImage?.url,
|
|
30801
|
+
range: props.range,
|
|
30802
|
+
labels: props.labels
|
|
30803
|
+
},
|
|
30804
|
+
userInput: getUserInput(userInput)
|
|
30805
|
+
};
|
|
30806
|
+
};
|
|
30807
|
+
const getGraphOptionsForProps = props => {
|
|
30808
|
+
const type = props.graph.type;
|
|
30809
|
+
switch (type) {
|
|
30810
|
+
case "angle":
|
|
30811
|
+
return {
|
|
30812
|
+
type: props.graph.type,
|
|
30813
|
+
angleOffsetDegrees: props.graph.angleOffsetDeg,
|
|
30814
|
+
startCoords: props.graph.startCoords
|
|
30815
|
+
};
|
|
30816
|
+
case "circle":
|
|
30817
|
+
return {
|
|
30818
|
+
type: props.graph.type,
|
|
30819
|
+
startParams: {
|
|
30820
|
+
center: props.graph.startCoords?.center,
|
|
30821
|
+
radius: props.graph.startCoords?.radius
|
|
30822
|
+
}
|
|
30823
|
+
};
|
|
30824
|
+
case "linear":
|
|
30825
|
+
return {
|
|
30826
|
+
type: props.graph.type,
|
|
30827
|
+
startCoords: props.graph.startCoords
|
|
30828
|
+
};
|
|
30829
|
+
case "linear-system":
|
|
30830
|
+
return {
|
|
30831
|
+
type: props.graph.type,
|
|
30832
|
+
startCoords: props.graph.startCoords
|
|
30833
|
+
};
|
|
30834
|
+
case "point":
|
|
30835
|
+
return {
|
|
30836
|
+
type: props.graph.type,
|
|
30837
|
+
numPoints: props.graph.numPoints,
|
|
30838
|
+
startCoords: props.graph.startCoords
|
|
30839
|
+
};
|
|
30840
|
+
case "polygon":
|
|
30841
|
+
return {
|
|
30842
|
+
type: props.graph.type,
|
|
30843
|
+
match: props.graph.match,
|
|
30844
|
+
numSides: props.graph.numSides,
|
|
30845
|
+
startCoords: props.graph.startCoords
|
|
30846
|
+
};
|
|
30847
|
+
case "quadratic":
|
|
30848
|
+
return {
|
|
30849
|
+
type: props.graph.type,
|
|
30850
|
+
startCoords: props.graph.startCoords
|
|
30851
|
+
};
|
|
30852
|
+
case "ray":
|
|
30853
|
+
return {
|
|
30854
|
+
type: props.graph.type,
|
|
30855
|
+
startCoords: props.graph.startCoords
|
|
30856
|
+
};
|
|
30857
|
+
case "segment":
|
|
30858
|
+
return {
|
|
30859
|
+
type: props.graph.type,
|
|
30860
|
+
numSegments: props.graph.numSegments,
|
|
30861
|
+
startCoords: props.graph.startCoords
|
|
30862
|
+
};
|
|
30863
|
+
case "sinusoid":
|
|
30864
|
+
return {
|
|
30865
|
+
type: props.graph.type,
|
|
30866
|
+
startCoords: props.graph.startCoords
|
|
30867
|
+
};
|
|
30868
|
+
case "none":
|
|
30869
|
+
return {};
|
|
30870
|
+
default:
|
|
30871
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30872
|
+
}
|
|
30873
|
+
};
|
|
30874
|
+
const getUserInput = userInput => {
|
|
30875
|
+
const type = userInput.type;
|
|
30876
|
+
switch (type) {
|
|
30877
|
+
case "angle":
|
|
30878
|
+
return {
|
|
30879
|
+
coords: userInput.coords,
|
|
30880
|
+
angleOffsetDegrees: userInput.angleOffsetDeg
|
|
30881
|
+
};
|
|
30882
|
+
case "circle":
|
|
30883
|
+
return {
|
|
30884
|
+
center: userInput.center,
|
|
30885
|
+
radius: userInput.radius
|
|
30886
|
+
};
|
|
30887
|
+
case "linear":
|
|
30888
|
+
return {
|
|
30889
|
+
coords: userInput.coords
|
|
30890
|
+
};
|
|
30891
|
+
case "linear-system":
|
|
30892
|
+
return {
|
|
30893
|
+
coords: userInput.coords
|
|
30894
|
+
};
|
|
30895
|
+
case "point":
|
|
30896
|
+
return {
|
|
30897
|
+
coords: userInput.coords
|
|
30898
|
+
};
|
|
30899
|
+
case "polygon":
|
|
30900
|
+
return {
|
|
30901
|
+
coords: userInput.coords
|
|
30902
|
+
};
|
|
30903
|
+
case "quadratic":
|
|
30904
|
+
return {
|
|
30905
|
+
coords: userInput.coords
|
|
30906
|
+
};
|
|
30907
|
+
case "ray":
|
|
30908
|
+
return {
|
|
30909
|
+
coords: userInput.coords
|
|
30910
|
+
};
|
|
30911
|
+
case "segment":
|
|
30912
|
+
return {
|
|
30913
|
+
coords: userInput.coords
|
|
30914
|
+
};
|
|
30915
|
+
case "sinusoid":
|
|
30916
|
+
return {
|
|
30917
|
+
coords: userInput.coords
|
|
30918
|
+
};
|
|
30919
|
+
case "none":
|
|
30920
|
+
return {};
|
|
30921
|
+
default:
|
|
30922
|
+
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
30923
|
+
}
|
|
30924
|
+
};
|
|
30925
|
+
|
|
30542
30926
|
function pathBuilder() {
|
|
30543
30927
|
return new PathBuilder();
|
|
30544
30928
|
}
|
|
@@ -31790,6 +32174,10 @@ const LegacyGrid = _ref => {
|
|
|
31790
32174
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
31791
32175
|
* area.
|
|
31792
32176
|
*/
|
|
32177
|
+
/**
|
|
32178
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
32179
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
32180
|
+
*/
|
|
31793
32181
|
/**
|
|
31794
32182
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
31795
32183
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
@@ -31886,6 +32274,10 @@ const ItemExtras = [
|
|
|
31886
32274
|
// A background image applied to various widgets.
|
|
31887
32275
|
|
|
31888
32276
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32277
|
+
|
|
32278
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
32279
|
+
|
|
32280
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
31889
32281
|
const lockedFigureColors = {
|
|
31890
32282
|
blue: "#3D7586",
|
|
31891
32283
|
green: "#447A53",
|
|
@@ -31953,16 +32345,57 @@ function isUnlimitedGraphState(state) {
|
|
|
31953
32345
|
* and the text outside of the $ blocks will be non-TeX text.
|
|
31954
32346
|
*/
|
|
31955
32347
|
function replaceOutsideTeX(mathString) {
|
|
31956
|
-
|
|
32348
|
+
// All the information we need is in the first section,
|
|
32349
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
32350
|
+
const firstSection = SimpleMarkdown__namespace.parse(mathString)[0];
|
|
32351
|
+
|
|
32352
|
+
// If it's blockMath, the outer level has the full math content.
|
|
32353
|
+
if (firstSection.type === "blockMath") {
|
|
32354
|
+
return firstSection.content;
|
|
32355
|
+
}
|
|
32356
|
+
|
|
32357
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
32358
|
+
// to look for individual math blocks.
|
|
32359
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31957
32360
|
let result = "";
|
|
31958
|
-
const
|
|
31959
|
-
|
|
31960
|
-
const part = splitString[i];
|
|
31961
|
-
result += currentlyTeX ? part : `\\text{${part}}`;
|
|
31962
|
-
currentlyTeX = !currentlyTeX;
|
|
32361
|
+
for (const piece of condensedNodes) {
|
|
32362
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
31963
32363
|
}
|
|
31964
32364
|
return result;
|
|
31965
32365
|
}
|
|
32366
|
+
// Helper function for replaceOutsideTeX()
|
|
32367
|
+
// Condense adjacent text nodes into a single text node
|
|
32368
|
+
function condenseTextNodes(nodes) {
|
|
32369
|
+
const result = [];
|
|
32370
|
+
let currentText = "";
|
|
32371
|
+
for (const node of nodes) {
|
|
32372
|
+
if (node.type === "math") {
|
|
32373
|
+
if (currentText) {
|
|
32374
|
+
result.push({
|
|
32375
|
+
type: "text",
|
|
32376
|
+
content: currentText
|
|
32377
|
+
});
|
|
32378
|
+
currentText = "";
|
|
32379
|
+
}
|
|
32380
|
+
result.push(node);
|
|
32381
|
+
} else {
|
|
32382
|
+
currentText += node.content;
|
|
32383
|
+
}
|
|
32384
|
+
}
|
|
32385
|
+
if (currentText) {
|
|
32386
|
+
result.push({
|
|
32387
|
+
type: "text",
|
|
32388
|
+
content: currentText
|
|
32389
|
+
});
|
|
32390
|
+
}
|
|
32391
|
+
return result;
|
|
32392
|
+
}
|
|
32393
|
+
|
|
32394
|
+
// Helper function for replaceOutsideTeX()
|
|
32395
|
+
function escapeSpecialChars(str) {
|
|
32396
|
+
// Escape $, \, {, and } characters
|
|
32397
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32398
|
+
}
|
|
31966
32399
|
|
|
31967
32400
|
function LockedLabel(props) {
|
|
31968
32401
|
const {
|
|
@@ -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
|
|
40548
|
+
};
|
|
40549
|
+
|
|
40550
|
+
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
40551
|
+
return {
|
|
40552
|
+
type: "matrix",
|
|
40553
|
+
options: {
|
|
40554
|
+
height: renderProps.matrixBoardSize[0],
|
|
40555
|
+
width: renderProps.matrixBoardSize[1]
|
|
40556
|
+
},
|
|
40557
|
+
userInput: {
|
|
40558
|
+
answerRows: userInput.answers
|
|
40559
|
+
}
|
|
40560
|
+
};
|
|
40064
40561
|
};
|
|
40065
40562
|
|
|
40066
|
-
function
|
|
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()
|
|
41543
42050
|
};
|
|
41544
42051
|
|
|
41545
|
-
|
|
42052
|
+
const getPromptJSON$6 = (renderProps, userInput) => {
|
|
42053
|
+
return {
|
|
42054
|
+
type: "number-line",
|
|
42055
|
+
options: {
|
|
42056
|
+
range: renderProps.range,
|
|
42057
|
+
numDivisions: renderProps.numDivisions,
|
|
42058
|
+
snapDivisions: renderProps.snapDivisions
|
|
42059
|
+
},
|
|
42060
|
+
userInput: {
|
|
42061
|
+
numLinePosition: userInput.numLinePosition,
|
|
42062
|
+
numDivisions: userInput.numDivisions
|
|
42063
|
+
}
|
|
42064
|
+
};
|
|
42065
|
+
};
|
|
42066
|
+
|
|
42067
|
+
function scoreNumberLine(state, rubric) {
|
|
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,10 +44509,10 @@ var Sorter$1 = {
|
|
|
43935
44509
|
displayName: "Sorter",
|
|
43936
44510
|
widget: Sorter,
|
|
43937
44511
|
isLintable: true,
|
|
43938
|
-
|
|
44512
|
+
scorer: scoreSorter
|
|
43939
44513
|
};
|
|
43940
44514
|
|
|
43941
|
-
function
|
|
44515
|
+
function scoreTable(state, rubric, strings) {
|
|
43942
44516
|
const filterNonEmpty = function (table) {
|
|
43943
44517
|
return ___default["default"].filter(table, function (row) {
|
|
43944
44518
|
// Check if row has a cell that is nonempty
|
|
@@ -44212,7 +44786,11 @@ var Table$1 = {
|
|
|
44212
44786
|
transform: propTransform,
|
|
44213
44787
|
hidden: true,
|
|
44214
44788
|
isLintable: true,
|
|
44215
|
-
|
|
44789
|
+
scorer: scoreTable
|
|
44790
|
+
};
|
|
44791
|
+
|
|
44792
|
+
const getPromptJSON = () => {
|
|
44793
|
+
return getUnsupportedPromptJSON("video");
|
|
44216
44794
|
};
|
|
44217
44795
|
|
|
44218
44796
|
/**
|
|
@@ -44311,6 +44889,9 @@ class Video extends React__namespace.Component {
|
|
|
44311
44889
|
return change.apply(_this, args);
|
|
44312
44890
|
};
|
|
44313
44891
|
})();
|
|
44892
|
+
getPromptJSON() {
|
|
44893
|
+
return getPromptJSON();
|
|
44894
|
+
}
|
|
44314
44895
|
render() {
|
|
44315
44896
|
const {
|
|
44316
44897
|
InitialRequestUrl
|
|
@@ -44370,8 +44951,8 @@ var Video$1 = {
|
|
|
44370
44951
|
defaultAlignment: "block",
|
|
44371
44952
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44372
44953
|
widget: Video,
|
|
44373
|
-
// TODO: things that aren't interactive shouldn't need
|
|
44374
|
-
|
|
44954
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44955
|
+
scorer: () => scoreNoop()
|
|
44375
44956
|
};
|
|
44376
44957
|
|
|
44377
44958
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -44390,7 +44971,7 @@ const init = function () {
|
|
|
44390
44971
|
|
|
44391
44972
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44392
44973
|
const libName = "@khanacademy/perseus";
|
|
44393
|
-
const libVersion = "41.
|
|
44974
|
+
const libVersion = "41.3.0";
|
|
44394
44975
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44395
44976
|
|
|
44396
44977
|
const apiVersion = {
|
|
@@ -45126,6 +45707,9 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
45126
45707
|
getNumHints() {
|
|
45127
45708
|
return this.props.item.hints.length;
|
|
45128
45709
|
}
|
|
45710
|
+
getPromptJSON() {
|
|
45711
|
+
return this.questionRenderer.getPromptJSON();
|
|
45712
|
+
}
|
|
45129
45713
|
|
|
45130
45714
|
/**
|
|
45131
45715
|
* Grades the item.
|
|
@@ -45783,7 +46367,7 @@ function treeToItem(node) {
|
|
|
45783
46367
|
};
|
|
45784
46368
|
}
|
|
45785
46369
|
|
|
45786
|
-
var isObject = function (obj) {
|
|
46370
|
+
var isObject$1 = function (obj) {
|
|
45787
46371
|
return obj === Object(obj);
|
|
45788
46372
|
};
|
|
45789
46373
|
var merge = function () {
|
|
@@ -45799,7 +46383,7 @@ var merge = function () {
|
|
|
45799
46383
|
return obj;
|
|
45800
46384
|
};
|
|
45801
46385
|
var clone = function (obj) {
|
|
45802
|
-
if (!isObject(obj)) {
|
|
46386
|
+
if (!isObject$1(obj)) {
|
|
45803
46387
|
return obj;
|
|
45804
46388
|
}
|
|
45805
46389
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45815,7 +46399,7 @@ var clone = function (obj) {
|
|
|
45815
46399
|
var dispatch = function (x) {
|
|
45816
46400
|
if (Array.isArray(x)) {
|
|
45817
46401
|
return arr;
|
|
45818
|
-
} else if (isObject(x)) {
|
|
46402
|
+
} else if (isObject$1(x)) {
|
|
45819
46403
|
return obj;
|
|
45820
46404
|
} else if (typeof x === "string") {
|
|
45821
46405
|
return str;
|
|
@@ -47739,6 +48323,697 @@ function buildTestData(testObject) {
|
|
|
47739
48323
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47740
48324
|
}
|
|
47741
48325
|
|
|
48326
|
+
function success(value) {
|
|
48327
|
+
return {
|
|
48328
|
+
type: "success",
|
|
48329
|
+
value
|
|
48330
|
+
};
|
|
48331
|
+
}
|
|
48332
|
+
function failure(detail) {
|
|
48333
|
+
return {
|
|
48334
|
+
type: "failure",
|
|
48335
|
+
detail
|
|
48336
|
+
};
|
|
48337
|
+
}
|
|
48338
|
+
function isFailure(result) {
|
|
48339
|
+
return result.type === "failure";
|
|
48340
|
+
}
|
|
48341
|
+
function isSuccess(result) {
|
|
48342
|
+
return result.type === "success";
|
|
48343
|
+
}
|
|
48344
|
+
|
|
48345
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
48346
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
48347
|
+
function all(results) {
|
|
48348
|
+
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
48349
|
+
const values = [];
|
|
48350
|
+
const failureDetails = [];
|
|
48351
|
+
for (const result of results) {
|
|
48352
|
+
if (result.type === "success") {
|
|
48353
|
+
values.push(result.value);
|
|
48354
|
+
} else {
|
|
48355
|
+
failureDetails.push(result.detail);
|
|
48356
|
+
}
|
|
48357
|
+
}
|
|
48358
|
+
if (failureDetails.length > 0) {
|
|
48359
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
48360
|
+
}
|
|
48361
|
+
return success(values);
|
|
48362
|
+
}
|
|
48363
|
+
|
|
48364
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48365
|
+
|
|
48366
|
+
function array(elementParser) {
|
|
48367
|
+
return (rawValue, ctx) => {
|
|
48368
|
+
if (!Array.isArray(rawValue)) {
|
|
48369
|
+
return ctx.failure("array", rawValue);
|
|
48370
|
+
}
|
|
48371
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
48372
|
+
return all(elementResults, concat);
|
|
48373
|
+
};
|
|
48374
|
+
}
|
|
48375
|
+
function concat(a, b) {
|
|
48376
|
+
return [...a, ...b];
|
|
48377
|
+
}
|
|
48378
|
+
|
|
48379
|
+
function boolean(rawValue, ctx) {
|
|
48380
|
+
if (typeof rawValue === "boolean") {
|
|
48381
|
+
return ctx.success(rawValue);
|
|
48382
|
+
}
|
|
48383
|
+
return ctx.failure("boolean", rawValue);
|
|
48384
|
+
}
|
|
48385
|
+
|
|
48386
|
+
function constant(acceptedValue) {
|
|
48387
|
+
return (rawValue, ctx) => {
|
|
48388
|
+
if (rawValue !== acceptedValue) {
|
|
48389
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
48390
|
+
}
|
|
48391
|
+
return ctx.success(acceptedValue);
|
|
48392
|
+
};
|
|
48393
|
+
}
|
|
48394
|
+
|
|
48395
|
+
function enumeration() {
|
|
48396
|
+
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
48397
|
+
acceptedValues[_key] = arguments[_key];
|
|
48398
|
+
}
|
|
48399
|
+
return (rawValue, ctx) => {
|
|
48400
|
+
if (typeof rawValue === "string") {
|
|
48401
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
48402
|
+
if (index > -1) {
|
|
48403
|
+
return ctx.success(acceptedValues[index]);
|
|
48404
|
+
}
|
|
48405
|
+
}
|
|
48406
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
48407
|
+
return ctx.failure(expected, rawValue);
|
|
48408
|
+
};
|
|
48409
|
+
}
|
|
48410
|
+
|
|
48411
|
+
function isObject(x) {
|
|
48412
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
48413
|
+
}
|
|
48414
|
+
|
|
48415
|
+
function nullable(parseValue) {
|
|
48416
|
+
return (rawValue, ctx) => {
|
|
48417
|
+
if (rawValue === null) {
|
|
48418
|
+
return ctx.success(rawValue);
|
|
48419
|
+
}
|
|
48420
|
+
return parseValue(rawValue, ctx);
|
|
48421
|
+
};
|
|
48422
|
+
}
|
|
48423
|
+
|
|
48424
|
+
const number = (rawValue, ctx) => {
|
|
48425
|
+
if (typeof rawValue === "number") {
|
|
48426
|
+
return ctx.success(rawValue);
|
|
48427
|
+
}
|
|
48428
|
+
return ctx.failure("number", rawValue);
|
|
48429
|
+
};
|
|
48430
|
+
|
|
48431
|
+
function object(schema) {
|
|
48432
|
+
return (rawValue, ctx) => {
|
|
48433
|
+
if (!isObject(rawValue)) {
|
|
48434
|
+
return ctx.failure("object", rawValue);
|
|
48435
|
+
}
|
|
48436
|
+
const ret = {
|
|
48437
|
+
...rawValue
|
|
48438
|
+
};
|
|
48439
|
+
const mismatches = [];
|
|
48440
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
48441
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
48442
|
+
if (isSuccess(result)) {
|
|
48443
|
+
ret[prop] = result.value;
|
|
48444
|
+
} else {
|
|
48445
|
+
mismatches.push(...result.detail);
|
|
48446
|
+
}
|
|
48447
|
+
}
|
|
48448
|
+
if (mismatches.length > 0) {
|
|
48449
|
+
return failure(mismatches);
|
|
48450
|
+
}
|
|
48451
|
+
return ctx.success(ret);
|
|
48452
|
+
};
|
|
48453
|
+
}
|
|
48454
|
+
|
|
48455
|
+
function optional(parseValue) {
|
|
48456
|
+
return (rawValue, ctx) => {
|
|
48457
|
+
if (rawValue === undefined) {
|
|
48458
|
+
return ctx.success(rawValue);
|
|
48459
|
+
}
|
|
48460
|
+
return parseValue(rawValue, ctx);
|
|
48461
|
+
};
|
|
48462
|
+
}
|
|
48463
|
+
|
|
48464
|
+
function pair(parseA, parseB) {
|
|
48465
|
+
return (rawValue, ctx) => {
|
|
48466
|
+
if (!Array.isArray(rawValue)) {
|
|
48467
|
+
return ctx.failure("array", rawValue);
|
|
48468
|
+
}
|
|
48469
|
+
if (rawValue.length !== 2) {
|
|
48470
|
+
return ctx.failure("array of length 2", rawValue);
|
|
48471
|
+
}
|
|
48472
|
+
const [rawA, rawB] = rawValue;
|
|
48473
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
48474
|
+
if (isFailure(resultA)) {
|
|
48475
|
+
return resultA;
|
|
48476
|
+
}
|
|
48477
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
48478
|
+
if (isFailure(resultB)) {
|
|
48479
|
+
return resultB;
|
|
48480
|
+
}
|
|
48481
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
48482
|
+
};
|
|
48483
|
+
}
|
|
48484
|
+
|
|
48485
|
+
function record(parseKey, parseValue) {
|
|
48486
|
+
return (rawValue, ctx) => {
|
|
48487
|
+
if (!isObject(rawValue)) {
|
|
48488
|
+
return ctx.failure("object", rawValue);
|
|
48489
|
+
}
|
|
48490
|
+
const result = {};
|
|
48491
|
+
const mismatches = [];
|
|
48492
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
48493
|
+
const entryCtx = ctx.forSubtree(key);
|
|
48494
|
+
const keyResult = parseKey(key, entryCtx);
|
|
48495
|
+
if (isFailure(keyResult)) {
|
|
48496
|
+
mismatches.push(...keyResult.detail);
|
|
48497
|
+
}
|
|
48498
|
+
const valueResult = parseValue(value, entryCtx);
|
|
48499
|
+
if (isFailure(valueResult)) {
|
|
48500
|
+
mismatches.push(...valueResult.detail);
|
|
48501
|
+
}
|
|
48502
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
48503
|
+
result[keyResult.value] = valueResult.value;
|
|
48504
|
+
}
|
|
48505
|
+
}
|
|
48506
|
+
if (mismatches.length > 0) {
|
|
48507
|
+
return failure(mismatches);
|
|
48508
|
+
}
|
|
48509
|
+
return ctx.success(result);
|
|
48510
|
+
};
|
|
48511
|
+
}
|
|
48512
|
+
|
|
48513
|
+
const string = (rawValue, ctx) => {
|
|
48514
|
+
if (typeof rawValue === "string") {
|
|
48515
|
+
return ctx.success(rawValue);
|
|
48516
|
+
}
|
|
48517
|
+
return ctx.failure("string", rawValue);
|
|
48518
|
+
};
|
|
48519
|
+
|
|
48520
|
+
function trio(parseA, parseB, parseC) {
|
|
48521
|
+
return (rawValue, ctx) => {
|
|
48522
|
+
if (!Array.isArray(rawValue)) {
|
|
48523
|
+
return ctx.failure("array", rawValue);
|
|
48524
|
+
}
|
|
48525
|
+
if (rawValue.length !== 3) {
|
|
48526
|
+
return ctx.failure("array of length 3", rawValue);
|
|
48527
|
+
}
|
|
48528
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
48529
|
+
if (isFailure(resultA)) {
|
|
48530
|
+
return resultA;
|
|
48531
|
+
}
|
|
48532
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
48533
|
+
if (isFailure(resultB)) {
|
|
48534
|
+
return resultB;
|
|
48535
|
+
}
|
|
48536
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
48537
|
+
if (isFailure(resultC)) {
|
|
48538
|
+
return resultC;
|
|
48539
|
+
}
|
|
48540
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
48541
|
+
};
|
|
48542
|
+
}
|
|
48543
|
+
|
|
48544
|
+
function union(parseBranch) {
|
|
48545
|
+
return new UnionBuilder(parseBranch);
|
|
48546
|
+
}
|
|
48547
|
+
class UnionBuilder {
|
|
48548
|
+
constructor(parser) {
|
|
48549
|
+
this.parser = parser;
|
|
48550
|
+
}
|
|
48551
|
+
or(newBranch) {
|
|
48552
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
48553
|
+
}
|
|
48554
|
+
}
|
|
48555
|
+
function either(parseA, parseB) {
|
|
48556
|
+
return (rawValue, ctx) => {
|
|
48557
|
+
const resultA = parseA(rawValue, ctx);
|
|
48558
|
+
if (isSuccess(resultA)) {
|
|
48559
|
+
return resultA;
|
|
48560
|
+
}
|
|
48561
|
+
return parseB(rawValue, ctx);
|
|
48562
|
+
};
|
|
48563
|
+
}
|
|
48564
|
+
|
|
48565
|
+
function parseWidget(parseType, parseOptions) {
|
|
48566
|
+
return object({
|
|
48567
|
+
type: parseType,
|
|
48568
|
+
static: optional(boolean),
|
|
48569
|
+
graded: optional(boolean),
|
|
48570
|
+
alignment: optional(string),
|
|
48571
|
+
options: parseOptions,
|
|
48572
|
+
key: optional(number),
|
|
48573
|
+
version: object({
|
|
48574
|
+
major: number,
|
|
48575
|
+
minor: number
|
|
48576
|
+
})
|
|
48577
|
+
});
|
|
48578
|
+
}
|
|
48579
|
+
|
|
48580
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
48581
|
+
items: array(string),
|
|
48582
|
+
categories: array(string),
|
|
48583
|
+
randomizeItems: boolean,
|
|
48584
|
+
static: boolean,
|
|
48585
|
+
values: array(number),
|
|
48586
|
+
highlightLint: optional(boolean),
|
|
48587
|
+
linterContext: optional(object({
|
|
48588
|
+
contentType: string,
|
|
48589
|
+
paths: array(string),
|
|
48590
|
+
stack: array(string)
|
|
48591
|
+
}))
|
|
48592
|
+
}));
|
|
48593
|
+
|
|
48594
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
48595
|
+
programID: string,
|
|
48596
|
+
programType: any,
|
|
48597
|
+
settings: array(object({
|
|
48598
|
+
name: string,
|
|
48599
|
+
value: string
|
|
48600
|
+
})),
|
|
48601
|
+
showEditor: boolean,
|
|
48602
|
+
showButtons: boolean,
|
|
48603
|
+
width: number,
|
|
48604
|
+
height: number,
|
|
48605
|
+
static: boolean
|
|
48606
|
+
}));
|
|
48607
|
+
|
|
48608
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48609
|
+
togglePrompt: string,
|
|
48610
|
+
definition: string,
|
|
48611
|
+
static: boolean
|
|
48612
|
+
}));
|
|
48613
|
+
|
|
48614
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48615
|
+
placeholder: string,
|
|
48616
|
+
static: boolean,
|
|
48617
|
+
choices: array(object({
|
|
48618
|
+
content: string,
|
|
48619
|
+
correct: boolean
|
|
48620
|
+
}))
|
|
48621
|
+
}));
|
|
48622
|
+
|
|
48623
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
48624
|
+
showPrompt: string,
|
|
48625
|
+
hidePrompt: string,
|
|
48626
|
+
explanation: string,
|
|
48627
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
48628
|
+
// referenced before it is defined. There is an import cycle between
|
|
48629
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
48630
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48631
|
+
static: boolean
|
|
48632
|
+
}));
|
|
48633
|
+
|
|
48634
|
+
const parseAnswerForm = object({
|
|
48635
|
+
value: string,
|
|
48636
|
+
form: boolean,
|
|
48637
|
+
simplify: boolean,
|
|
48638
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48639
|
+
key: optional(string)
|
|
48640
|
+
});
|
|
48641
|
+
const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
48642
|
+
answerForms: array(parseAnswerForm),
|
|
48643
|
+
functions: array(string),
|
|
48644
|
+
times: boolean,
|
|
48645
|
+
visibleLabel: optional(string),
|
|
48646
|
+
ariaLabel: optional(string),
|
|
48647
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48648
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48649
|
+
}));
|
|
48650
|
+
|
|
48651
|
+
const parsePerseusImageBackground = object({
|
|
48652
|
+
url: optional(nullable(string)),
|
|
48653
|
+
width: optional(number),
|
|
48654
|
+
height: optional(number),
|
|
48655
|
+
top: optional(number),
|
|
48656
|
+
left: optional(number),
|
|
48657
|
+
bottom: optional(number),
|
|
48658
|
+
// TODO(benchristel): convert scale to a number
|
|
48659
|
+
scale: optional(union(number).or(string).parser)
|
|
48660
|
+
});
|
|
48661
|
+
|
|
48662
|
+
// Used to represent 2-D points and ranges
|
|
48663
|
+
const pairOfNumbers = pair(number, number);
|
|
48664
|
+
const parsePerseusGraphTypeAngle = object({
|
|
48665
|
+
type: constant("angle"),
|
|
48666
|
+
showAngles: optional(boolean),
|
|
48667
|
+
allowReflexAngles: optional(boolean),
|
|
48668
|
+
angleOffsetDeg: optional(number),
|
|
48669
|
+
snapDegrees: optional(number),
|
|
48670
|
+
match: optional(constant("congruent")),
|
|
48671
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48672
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
48673
|
+
});
|
|
48674
|
+
const parsePerseusGraphTypeCircle = object({
|
|
48675
|
+
type: constant("circle"),
|
|
48676
|
+
center: optional(pairOfNumbers),
|
|
48677
|
+
radius: optional(number),
|
|
48678
|
+
startCoords: optional(object({
|
|
48679
|
+
center: pairOfNumbers,
|
|
48680
|
+
radius: number
|
|
48681
|
+
})),
|
|
48682
|
+
// TODO: remove coord? it's legacy.
|
|
48683
|
+
coord: optional(pairOfNumbers)
|
|
48684
|
+
});
|
|
48685
|
+
const parsePerseusGraphTypeLinear = object({
|
|
48686
|
+
type: constant("linear"),
|
|
48687
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48688
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48689
|
+
// TODO: remove coord? it's legacy.
|
|
48690
|
+
coord: optional(pairOfNumbers)
|
|
48691
|
+
});
|
|
48692
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
48693
|
+
type: constant("linear-system"),
|
|
48694
|
+
// TODO(benchristel): default coords to empty array?
|
|
48695
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48696
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48697
|
+
// TODO: remove coord? it's legacy.
|
|
48698
|
+
coord: optional(pairOfNumbers)
|
|
48699
|
+
});
|
|
48700
|
+
const parsePerseusGraphTypeNone = object({
|
|
48701
|
+
type: constant("none")
|
|
48702
|
+
});
|
|
48703
|
+
const parsePerseusGraphTypePoint = object({
|
|
48704
|
+
type: constant("point"),
|
|
48705
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
48706
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48707
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48708
|
+
// TODO: remove coord? it's legacy.
|
|
48709
|
+
coord: optional(pairOfNumbers)
|
|
48710
|
+
});
|
|
48711
|
+
const parsePerseusGraphTypePolygon = object({
|
|
48712
|
+
type: constant("polygon"),
|
|
48713
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
48714
|
+
showAngles: optional(boolean),
|
|
48715
|
+
showSides: optional(boolean),
|
|
48716
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
48717
|
+
match: optional(enumeration("similar", "congruent", "approx")),
|
|
48718
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48719
|
+
// TODO: remove coord? it's legacy.
|
|
48720
|
+
coord: optional(pairOfNumbers)
|
|
48721
|
+
});
|
|
48722
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
48723
|
+
type: constant("quadratic"),
|
|
48724
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
48725
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48726
|
+
// TODO: remove coord? it's legacy.
|
|
48727
|
+
coord: optional(pairOfNumbers)
|
|
48728
|
+
});
|
|
48729
|
+
const parsePerseusGraphTypeRay = object({
|
|
48730
|
+
type: constant("ray"),
|
|
48731
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48732
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48733
|
+
// TODO: remove coord? it's legacy.
|
|
48734
|
+
coord: optional(pairOfNumbers)
|
|
48735
|
+
});
|
|
48736
|
+
const parsePerseusGraphTypeSegment = object({
|
|
48737
|
+
type: constant("segment"),
|
|
48738
|
+
// TODO(benchristel): default numSegments?
|
|
48739
|
+
numSegments: optional(number),
|
|
48740
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48741
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48742
|
+
// TODO: remove coord? it's legacy.
|
|
48743
|
+
coord: optional(pairOfNumbers)
|
|
48744
|
+
});
|
|
48745
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
48746
|
+
type: constant("sinusoid"),
|
|
48747
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48748
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48749
|
+
// TODO: remove coord? it's legacy.
|
|
48750
|
+
coord: optional(pairOfNumbers)
|
|
48751
|
+
});
|
|
48752
|
+
const parsePerseusGraphType = union(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeAngle).or(parsePerseusGraphTypeCircle).or(parsePerseusGraphTypeLinear).or(parsePerseusGraphTypeLinearSystem).or(parsePerseusGraphTypeNone).or(parsePerseusGraphTypePoint).or(parsePerseusGraphTypePolygon).or(parsePerseusGraphTypeQuadratic).or(parsePerseusGraphTypeRay).or(parsePerseusGraphTypeSegment).or(parsePerseusGraphTypeSinusoid).parser;
|
|
48753
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48754
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48755
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48756
|
+
const parseLockedLabelType = object({
|
|
48757
|
+
type: constant("label"),
|
|
48758
|
+
coord: pairOfNumbers,
|
|
48759
|
+
text: string,
|
|
48760
|
+
color: parseLockedFigureColor,
|
|
48761
|
+
size: enumeration("small", "medium", "large")
|
|
48762
|
+
});
|
|
48763
|
+
const parseLockedPointType = object({
|
|
48764
|
+
type: constant("point"),
|
|
48765
|
+
coord: pairOfNumbers,
|
|
48766
|
+
color: parseLockedFigureColor,
|
|
48767
|
+
filled: boolean,
|
|
48768
|
+
// TODO(benchristel): default labels to empty array?
|
|
48769
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48770
|
+
ariaLabel: optional(string)
|
|
48771
|
+
});
|
|
48772
|
+
const parseLockedLineType = object({
|
|
48773
|
+
type: constant("line"),
|
|
48774
|
+
kind: enumeration("line", "ray", "segment"),
|
|
48775
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48776
|
+
color: parseLockedFigureColor,
|
|
48777
|
+
lineStyle: parseLockedLineStyle,
|
|
48778
|
+
showPoint1: boolean,
|
|
48779
|
+
showPoint2: boolean,
|
|
48780
|
+
// TODO(benchristel): default labels to empty array?
|
|
48781
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48782
|
+
ariaLabel: optional(string)
|
|
48783
|
+
});
|
|
48784
|
+
const parseLockedVectorType = object({
|
|
48785
|
+
type: constant("vector"),
|
|
48786
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
48787
|
+
color: parseLockedFigureColor,
|
|
48788
|
+
// TODO(benchristel): default labels to empty array?
|
|
48789
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48790
|
+
ariaLabel: optional(string)
|
|
48791
|
+
});
|
|
48792
|
+
const parseLockedEllipseType = object({
|
|
48793
|
+
type: constant("ellipse"),
|
|
48794
|
+
center: pairOfNumbers,
|
|
48795
|
+
radius: pairOfNumbers,
|
|
48796
|
+
angle: number,
|
|
48797
|
+
color: parseLockedFigureColor,
|
|
48798
|
+
fillStyle: parseLockedFigureFillType,
|
|
48799
|
+
strokeStyle: parseLockedLineStyle,
|
|
48800
|
+
// TODO(benchristel): default labels to empty array?
|
|
48801
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48802
|
+
ariaLabel: optional(string)
|
|
48803
|
+
});
|
|
48804
|
+
const parseLockedPolygonType = object({
|
|
48805
|
+
type: constant("polygon"),
|
|
48806
|
+
points: array(pairOfNumbers),
|
|
48807
|
+
color: parseLockedFigureColor,
|
|
48808
|
+
showVertices: boolean,
|
|
48809
|
+
fillStyle: parseLockedFigureFillType,
|
|
48810
|
+
strokeStyle: parseLockedLineStyle,
|
|
48811
|
+
// TODO(benchristel): default labels to empty array?
|
|
48812
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48813
|
+
ariaLabel: optional(string)
|
|
48814
|
+
});
|
|
48815
|
+
const parseLockedFunctionType = object({
|
|
48816
|
+
type: constant("function"),
|
|
48817
|
+
color: parseLockedFigureColor,
|
|
48818
|
+
strokeStyle: parseLockedLineStyle,
|
|
48819
|
+
equation: string,
|
|
48820
|
+
directionalAxis: enumeration("x", "y"),
|
|
48821
|
+
domain: optional(pairOfNumbers),
|
|
48822
|
+
// TODO(benchristel): default labels to empty array?
|
|
48823
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48824
|
+
ariaLabel: optional(string)
|
|
48825
|
+
});
|
|
48826
|
+
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
48827
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48828
|
+
step: pairOfNumbers,
|
|
48829
|
+
gridStep: pairOfNumbers,
|
|
48830
|
+
snapStep: pairOfNumbers,
|
|
48831
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
48832
|
+
markings: enumeration("graph", "grid", "none"),
|
|
48833
|
+
labels: array(string),
|
|
48834
|
+
showProtractor: boolean,
|
|
48835
|
+
showRuler: optional(boolean),
|
|
48836
|
+
showTooltips: optional(boolean),
|
|
48837
|
+
rulerLabel: optional(string),
|
|
48838
|
+
rulerTicks: optional(number),
|
|
48839
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
48840
|
+
graph: parsePerseusGraphType,
|
|
48841
|
+
correct: parsePerseusGraphType,
|
|
48842
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
48843
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
48844
|
+
fullGraphLabel: optional(string),
|
|
48845
|
+
fullGraphAriaDescription: optional(string)
|
|
48846
|
+
}));
|
|
48847
|
+
|
|
48848
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
48849
|
+
if (!isObject(rawValue)) {
|
|
48850
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
48851
|
+
}
|
|
48852
|
+
const widgetsMap = {};
|
|
48853
|
+
for (const key of Object.keys(rawValue)) {
|
|
48854
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
48855
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
48856
|
+
// against the widget type.
|
|
48857
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
48858
|
+
if (isFailure(entryResult)) {
|
|
48859
|
+
return entryResult;
|
|
48860
|
+
}
|
|
48861
|
+
}
|
|
48862
|
+
return ctx.success(widgetsMap);
|
|
48863
|
+
};
|
|
48864
|
+
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
48865
|
+
let [key, widget] = _ref;
|
|
48866
|
+
const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
|
|
48867
|
+
if (isFailure(keyComponentsResult)) {
|
|
48868
|
+
return keyComponentsResult;
|
|
48869
|
+
}
|
|
48870
|
+
const [type, id] = keyComponentsResult.value;
|
|
48871
|
+
function parseAndAssign(key, parse) {
|
|
48872
|
+
const widgetResult = parse(widget, ctx);
|
|
48873
|
+
if (isFailure(widgetResult)) {
|
|
48874
|
+
return widgetResult;
|
|
48875
|
+
}
|
|
48876
|
+
widgetMap[key] = widgetResult.value;
|
|
48877
|
+
return ctx.success(undefined);
|
|
48878
|
+
}
|
|
48879
|
+
switch (type) {
|
|
48880
|
+
case "categorizer":
|
|
48881
|
+
return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
|
|
48882
|
+
case "cs-program":
|
|
48883
|
+
return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
|
|
48884
|
+
case "definition":
|
|
48885
|
+
return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
|
|
48886
|
+
case "dropdown":
|
|
48887
|
+
return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
|
|
48888
|
+
case "explanation":
|
|
48889
|
+
return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
|
|
48890
|
+
case "expression":
|
|
48891
|
+
return parseAndAssign(`expression ${id}`, parseExpressionWidget);
|
|
48892
|
+
case "grapher":
|
|
48893
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48894
|
+
return parseAndAssign(`grapher ${id}`, any);
|
|
48895
|
+
case "group":
|
|
48896
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48897
|
+
return parseAndAssign(`group ${id}`, any);
|
|
48898
|
+
case "graded-group":
|
|
48899
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48900
|
+
return parseAndAssign(`graded-group ${id}`, any);
|
|
48901
|
+
case "graded-group-set":
|
|
48902
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48903
|
+
return parseAndAssign(`graded-group-set ${id}`, any);
|
|
48904
|
+
case "iframe":
|
|
48905
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48906
|
+
return parseAndAssign(`iframe ${id}`, any);
|
|
48907
|
+
case "image":
|
|
48908
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48909
|
+
return parseAndAssign(`image ${id}`, any);
|
|
48910
|
+
case "input-number":
|
|
48911
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48912
|
+
return parseAndAssign(`input-number ${id}`, any);
|
|
48913
|
+
case "interaction":
|
|
48914
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48915
|
+
return parseAndAssign(`interaction ${id}`, any);
|
|
48916
|
+
case "interactive-graph":
|
|
48917
|
+
return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
|
|
48918
|
+
case "label-image":
|
|
48919
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48920
|
+
return parseAndAssign(`label-image ${id}`, any);
|
|
48921
|
+
case "matcher":
|
|
48922
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48923
|
+
return parseAndAssign(`matcher ${id}`, any);
|
|
48924
|
+
case "matrix":
|
|
48925
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48926
|
+
return parseAndAssign(`matrix ${id}`, any);
|
|
48927
|
+
case "measurer":
|
|
48928
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48929
|
+
return parseAndAssign(`measurer ${id}`, any);
|
|
48930
|
+
case "molecule-renderer":
|
|
48931
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48932
|
+
return parseAndAssign(`molecule-renderer ${id}`, any);
|
|
48933
|
+
case "number-line":
|
|
48934
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48935
|
+
return parseAndAssign(`number-line ${id}`, any);
|
|
48936
|
+
case "numeric-input":
|
|
48937
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48938
|
+
return parseAndAssign(`numeric-input ${id}`, any);
|
|
48939
|
+
case "orderer":
|
|
48940
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48941
|
+
return parseAndAssign(`orderer ${id}`, any);
|
|
48942
|
+
case "passage":
|
|
48943
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48944
|
+
return parseAndAssign(`passage ${id}`, any);
|
|
48945
|
+
case "passage-ref":
|
|
48946
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48947
|
+
return parseAndAssign(`passage-ref ${id}`, any);
|
|
48948
|
+
case "passage-ref-target":
|
|
48949
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48950
|
+
return parseAndAssign(`passage-ref-target ${id}`, any);
|
|
48951
|
+
case "phet-simulation":
|
|
48952
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48953
|
+
return parseAndAssign(`phet-simulation ${id}`, any);
|
|
48954
|
+
case "plotter":
|
|
48955
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48956
|
+
return parseAndAssign(`plotter ${id}`, any);
|
|
48957
|
+
case "python-program":
|
|
48958
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48959
|
+
return parseAndAssign(`python-program ${id}`, any);
|
|
48960
|
+
case "radio":
|
|
48961
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48962
|
+
return parseAndAssign(`radio ${id}`, any);
|
|
48963
|
+
case "sorter":
|
|
48964
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48965
|
+
return parseAndAssign(`sorter ${id}`, any);
|
|
48966
|
+
case "table":
|
|
48967
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48968
|
+
return parseAndAssign(`table ${id}`, any);
|
|
48969
|
+
case "video":
|
|
48970
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48971
|
+
return parseAndAssign(`video ${id}`, any);
|
|
48972
|
+
default:
|
|
48973
|
+
return ctx.failure("a valid widget type", type);
|
|
48974
|
+
}
|
|
48975
|
+
};
|
|
48976
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48977
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48978
|
+
return ctx.failure("numeric string", rawValue);
|
|
48979
|
+
}
|
|
48980
|
+
return ctx.success(+rawValue);
|
|
48981
|
+
};
|
|
48982
|
+
const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
48983
|
+
|
|
48984
|
+
const parseHint = object({
|
|
48985
|
+
replace: optional(boolean),
|
|
48986
|
+
content: string,
|
|
48987
|
+
widgets: parseWidgetsMap,
|
|
48988
|
+
metadata: optional(array(string)),
|
|
48989
|
+
images: record(string, object({
|
|
48990
|
+
width: number,
|
|
48991
|
+
height: number
|
|
48992
|
+
}))
|
|
48993
|
+
});
|
|
48994
|
+
|
|
48995
|
+
const parsePerseusRenderer = object({
|
|
48996
|
+
content: string,
|
|
48997
|
+
widgets: parseWidgetsMap,
|
|
48998
|
+
metadata: optional(array(string)),
|
|
48999
|
+
images: record(string, object({
|
|
49000
|
+
width: number,
|
|
49001
|
+
height: number
|
|
49002
|
+
}))
|
|
49003
|
+
});
|
|
49004
|
+
|
|
49005
|
+
object({
|
|
49006
|
+
question: parsePerseusRenderer,
|
|
49007
|
+
hints: array(parseHint),
|
|
49008
|
+
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
49009
|
+
itemDataVersion: object({
|
|
49010
|
+
major: number,
|
|
49011
|
+
minor: number
|
|
49012
|
+
}),
|
|
49013
|
+
// Deprecated field
|
|
49014
|
+
answer: any
|
|
49015
|
+
});
|
|
49016
|
+
|
|
47742
49017
|
/**
|
|
47743
49018
|
* Helper to parse PerseusItem JSON
|
|
47744
49019
|
* Why not just use JSON.parse? We want:
|