@khanacademy/perseus 41.2.0 → 41.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-widgets.d.ts +1 -1
- package/dist/article-renderer.d.ts +2 -2
- package/dist/basic-widgets.d.ts +1 -1
- package/dist/es/index.js +1643 -354
- package/dist/es/index.js.map +1 -1
- package/dist/extra-widgets.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1562 -272
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +3 -3
- package/dist/perseus-types.d.ts +7 -2
- package/dist/prompt-types.d.ts +33 -0
- package/dist/renderer.d.ts +8 -2
- package/dist/server-item-renderer.d.ts +6 -4
- package/dist/types.d.ts +4 -2
- package/dist/util/answer-types.d.ts +45 -2
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +15 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/util/{parse-perseus-json.d.ts → parse-perseus-json/index.d.ts} +7 -1
- package/dist/util/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse.d.ts +3 -0
- package/dist/util/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/util/parse-perseus-json/result.d.ts +16 -0
- package/dist/validation.types.d.ts +4 -5
- package/dist/widget-ai-utils/categorizer/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/cs-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/definition/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/dropdown/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/explanation/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/expression/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/graded-group/prompt-utils.d.ts +7 -0
- package/dist/widget-ai-utils/graded-group-set/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/grapher/prompt-utils.d.ts +18 -0
- package/dist/widget-ai-utils/group/prompt-utils.d.ts +5 -0
- package/dist/widget-ai-utils/iframe/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/image/prompt-utils.d.ts +12 -0
- package/dist/widget-ai-utils/input-number/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/interaction/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/interactive-graph/prompt-utils.d.ts +94 -0
- package/dist/widget-ai-utils/label-image/prompt-utils.d.ts +24 -0
- package/dist/widget-ai-utils/matcher/prompt-utils.d.ts +17 -0
- package/dist/widget-ai-utils/matrix/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/measurer/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/number-line/prompt-utils.d.ts +16 -0
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/orderer/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/passage/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/passage-ref/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/phet-simulation/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/plotter/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/python-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/radio/prompt-utils.d.ts +15 -0
- package/dist/widget-ai-utils/sorter/prompt-utils.d.ts +9 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +11 -0
- package/dist/widget-ai-utils/video/prompt-utils.d.ts +2 -0
- package/dist/widgets/__shared__/{noop-validator.d.ts → score-noop.d.ts} +4 -4
- package/dist/widgets/categorizer/categorizer.d.ts +10 -19
- package/dist/widgets/categorizer/{categorizer-validator.d.ts → score-categorizer.d.ts} +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +8 -19
- package/dist/widgets/cs-program/{cs-program-validator.d.ts → score-cs-program.d.ts} +2 -1
- package/dist/widgets/definition/definition.d.ts +8 -19
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +4 -19
- package/dist/widgets/dropdown/dropdown.d.ts +10 -19
- package/dist/widgets/dropdown/score-dropdown.d.ts +4 -0
- package/dist/widgets/explanation/explanation.d.ts +9 -19
- package/dist/widgets/expression/expression.d.ts +26 -290
- package/dist/widgets/expression/{expression-validator.d.ts → score-expression.d.ts} +2 -2
- package/dist/widgets/graded-group/graded-group.d.ts +13 -21
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +9 -20
- package/dist/widgets/grapher/grapher.d.ts +9 -19
- package/dist/widgets/grapher/score-grapher.d.ts +4 -0
- package/dist/widgets/group/group.d.ts +7 -19
- package/dist/widgets/iframe/iframe.d.ts +7 -19
- package/dist/widgets/iframe/{iframe-validator.d.ts → score-iframe.d.ts} +1 -1
- package/dist/widgets/image/image.d.ts +9 -19
- package/dist/widgets/input-number/input-number.d.ts +11 -19
- package/dist/widgets/input-number/{input-number-validator.d.ts → score-input-number.d.ts} +2 -2
- package/dist/widgets/interaction/interaction.d.ts +7 -19
- package/dist/widgets/interactive-graph.d.ts +8 -19
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +4 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
- package/dist/widgets/label-image/label-image.d.ts +10 -86
- package/dist/widgets/label-image/{label-image-validator.d.ts → score-label-image.d.ts} +2 -2
- package/dist/widgets/matcher/matcher.d.ts +7 -19
- package/dist/widgets/matcher/score-matcher.d.ts +4 -0
- package/dist/widgets/matrix/matrix.d.ts +10 -19
- package/dist/widgets/matrix/{matrix-validator.d.ts → score-matrix.d.ts} +2 -2
- package/dist/widgets/measurer/measurer.d.ts +13 -19
- package/dist/widgets/molecule/molecule.d.ts +4 -19
- package/dist/widgets/number-line/number-line.d.ts +8 -19
- package/dist/widgets/number-line/score-number-line.d.ts +4 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +22 -19
- package/dist/widgets/numeric-input/{numeric-input-validator.d.ts → score-numeric-input.d.ts} +2 -2
- package/dist/widgets/orderer/orderer.d.ts +8 -19
- package/dist/widgets/orderer/{orderer-validator.d.ts → score-orderer.d.ts} +1 -1
- package/dist/widgets/passage/passage.d.ts +8 -19
- package/dist/widgets/passage-ref/passage-ref.d.ts +12 -19
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +11 -19
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +5 -19
- package/dist/widgets/plotter/plotter.d.ts +8 -19
- package/dist/widgets/plotter/score-plotter.d.ts +4 -0
- package/dist/widgets/python-program/python-program.d.ts +4 -19
- package/dist/widgets/radio/radio-component.d.ts +3 -1
- package/dist/widgets/radio/radio.d.ts +18 -19
- package/dist/widgets/radio/{radio-validator.d.ts → score-radio.d.ts} +2 -2
- package/dist/widgets/sorter/score-sorter.d.ts +4 -0
- package/dist/widgets/sorter/sorter.d.ts +7 -19
- package/dist/widgets/table/{table-validator.d.ts → score-table.d.ts} +2 -2
- package/dist/widgets/table/table.d.ts +8 -19
- package/dist/widgets/table/utils.d.ts +7 -0
- package/dist/widgets/table/validate-table.d.ts +6 -0
- package/dist/widgets/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +5 -5
- package/dist/widgets/dropdown/dropdown-validator.d.ts +0 -4
- package/dist/widgets/grapher/grapher-validator.d.ts +0 -4
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +0 -4
- package/dist/widgets/number-line/number-line-validator.d.ts +0 -4
- package/dist/widgets/plotter/plotter-validator.d.ts +0 -4
- package/dist/widgets/sorter/sorter-validator.d.ts +0 -4
package/dist/es/index.js
CHANGED
|
@@ -19,10 +19,11 @@ import $ from 'jquery';
|
|
|
19
19
|
import { v4 } from 'uuid';
|
|
20
20
|
import { PerseusError, Errors, addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
21
21
|
import PropTypes from 'prop-types';
|
|
22
|
-
import { number, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
22
|
+
import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
25
|
+
import SimpleMarkdown$1 from '@khanacademy/simple-markdown';
|
|
26
|
+
import * as SimpleMarkdown from '@khanacademy/pure-markdown';
|
|
26
27
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
27
28
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
28
29
|
import Button from '@khanacademy/wonder-blocks-button';
|
|
@@ -1168,6 +1169,42 @@ const ClassNames = {
|
|
|
1168
1169
|
MOBILE: "perseus-mobile"
|
|
1169
1170
|
};
|
|
1170
1171
|
|
|
1172
|
+
const getPromptJSON$t = (renderProps, userInput) => {
|
|
1173
|
+
return {
|
|
1174
|
+
type: "expression",
|
|
1175
|
+
label: renderProps.visibleLabel,
|
|
1176
|
+
userInput: {
|
|
1177
|
+
value: userInput
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Get the character used for separating decimals.
|
|
1184
|
+
*/
|
|
1185
|
+
const getDecimalSeparator = locale => {
|
|
1186
|
+
var _match$;
|
|
1187
|
+
switch (locale) {
|
|
1188
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1189
|
+
// locale properly.
|
|
1190
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1191
|
+
//
|
|
1192
|
+
// Supported locales in Chrome:
|
|
1193
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1194
|
+
case "ka":
|
|
1195
|
+
return ",";
|
|
1196
|
+
default:
|
|
1197
|
+
const numberWithDecimalSeparator = 1.1;
|
|
1198
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1199
|
+
// support Safari 12.
|
|
1200
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1201
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1202
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1203
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
1204
|
+
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1171
1208
|
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1172
1209
|
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1173
1210
|
// which get's the injected logger.
|
|
@@ -1288,7 +1325,7 @@ const KhanMath = {
|
|
|
1288
1325
|
toFixedApprox: function (num, precision) {
|
|
1289
1326
|
// TODO(aria): Make this locale-dependent
|
|
1290
1327
|
const fixedStr = num.toFixed(precision);
|
|
1291
|
-
if (number.equal(+fixedStr, num)) {
|
|
1328
|
+
if (number$1.equal(+fixedStr, num)) {
|
|
1292
1329
|
return fixedStr;
|
|
1293
1330
|
}
|
|
1294
1331
|
return "\\approx " + fixedStr;
|
|
@@ -1300,7 +1337,7 @@ const KhanMath = {
|
|
|
1300
1337
|
*/
|
|
1301
1338
|
roundToApprox: function (num, precision) {
|
|
1302
1339
|
const fixed = KhanMath.roundTo(precision, num);
|
|
1303
|
-
if (number.equal(fixed, num)) {
|
|
1340
|
+
if (number$1.equal(fixed, num)) {
|
|
1304
1341
|
return String(fixed);
|
|
1305
1342
|
}
|
|
1306
1343
|
return KhanMath.toFixedApprox(num, precision);
|
|
@@ -1372,32 +1409,32 @@ const KhanMath = {
|
|
|
1372
1409
|
return null;
|
|
1373
1410
|
},
|
|
1374
1411
|
// Returns a string of the number in a specified format
|
|
1375
|
-
toNumericString: function (number
|
|
1376
|
-
if (number
|
|
1412
|
+
toNumericString: function (number, format) {
|
|
1413
|
+
if (number == null) {
|
|
1377
1414
|
return "";
|
|
1378
1415
|
}
|
|
1379
|
-
if (number
|
|
1416
|
+
if (number === 0) {
|
|
1380
1417
|
return "0"; // otherwise it might end up as 0% or 0pi
|
|
1381
1418
|
}
|
|
1382
1419
|
|
|
1383
1420
|
if (format === "percent") {
|
|
1384
|
-
return number
|
|
1421
|
+
return number * 100 + "%";
|
|
1385
1422
|
}
|
|
1386
1423
|
if (format === "pi") {
|
|
1387
|
-
const fraction = number.toFraction(number
|
|
1424
|
+
const fraction = number$1.toFraction(number / Math.PI);
|
|
1388
1425
|
const numerator = Math.abs(fraction[0]);
|
|
1389
1426
|
const denominator = fraction[1];
|
|
1390
|
-
if (number.isInteger(numerator)) {
|
|
1391
|
-
const sign = number
|
|
1427
|
+
if (number$1.isInteger(numerator)) {
|
|
1428
|
+
const sign = number < 0 ? "-" : "";
|
|
1392
1429
|
const pi = "\u03C0";
|
|
1393
1430
|
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
1394
1431
|
}
|
|
1395
1432
|
}
|
|
1396
1433
|
if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
1397
|
-
const fraction = number.toFraction(number
|
|
1434
|
+
const fraction = number$1.toFraction(number);
|
|
1398
1435
|
const numerator = Math.abs(fraction[0]);
|
|
1399
1436
|
const denominator = fraction[1];
|
|
1400
|
-
const sign = number
|
|
1437
|
+
const sign = number < 0 ? "-" : "";
|
|
1401
1438
|
if (denominator === 1) {
|
|
1402
1439
|
return sign + numerator; // for integers, irrational, d > 1000
|
|
1403
1440
|
}
|
|
@@ -1411,7 +1448,7 @@ const KhanMath = {
|
|
|
1411
1448
|
}
|
|
1412
1449
|
|
|
1413
1450
|
// otherwise (decimal, float, long long)
|
|
1414
|
-
return String(number
|
|
1451
|
+
return String(number);
|
|
1415
1452
|
}
|
|
1416
1453
|
};
|
|
1417
1454
|
function sum$1(array) {
|
|
@@ -1427,7 +1464,7 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
|
1427
1464
|
// TOOD(kevinb): Figure out how this relates to KEScore in
|
|
1428
1465
|
// perseus-all-package/types.js and see if there's a way to
|
|
1429
1466
|
// unify these types.
|
|
1430
|
-
|
|
1467
|
+
/**
|
|
1431
1468
|
* Answer types
|
|
1432
1469
|
*
|
|
1433
1470
|
* Utility for creating answerable questions displayed in exercises
|
|
@@ -1470,7 +1507,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
|
1470
1507
|
* TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
|
|
1471
1508
|
*
|
|
1472
1509
|
*/
|
|
1473
|
-
|
|
1474
1510
|
const KhanAnswerTypes = {
|
|
1475
1511
|
/*
|
|
1476
1512
|
* predicate answer type
|
|
@@ -1941,9 +1977,7 @@ const KhanAnswerTypes = {
|
|
|
1941
1977
|
})];
|
|
1942
1978
|
},
|
|
1943
1979
|
createValidatorFunctional: function (correctAnswer, options, strings) {
|
|
1944
|
-
return KhanAnswerTypes.predicate.createValidatorFunctional(
|
|
1945
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
1946
|
-
...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
1980
|
+
return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
|
|
1947
1981
|
}
|
|
1948
1982
|
},
|
|
1949
1983
|
/*
|
|
@@ -2099,32 +2133,6 @@ const KhanAnswerTypes = {
|
|
|
2099
2133
|
}
|
|
2100
2134
|
};
|
|
2101
2135
|
|
|
2102
|
-
/**
|
|
2103
|
-
* Get the character used for separating decimals.
|
|
2104
|
-
*/
|
|
2105
|
-
const getDecimalSeparator = locale => {
|
|
2106
|
-
var _match$;
|
|
2107
|
-
switch (locale) {
|
|
2108
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2109
|
-
// locale properly.
|
|
2110
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2111
|
-
//
|
|
2112
|
-
// Supported locales in Chrome:
|
|
2113
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2114
|
-
case "ka":
|
|
2115
|
-
return ",";
|
|
2116
|
-
default:
|
|
2117
|
-
const numberWithDecimalSeparator = 1.1;
|
|
2118
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2119
|
-
// support Safari 12.
|
|
2120
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2121
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2122
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2123
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
2124
|
-
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
2125
|
-
}
|
|
2126
|
-
};
|
|
2127
|
-
|
|
2128
2136
|
/* Content creators input a list of answers which are matched from top to
|
|
2129
2137
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2130
2138
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2143,7 +2151,7 @@ const getDecimalSeparator = locale => {
|
|
|
2143
2151
|
* show the user an error. TODO(joel) - what error?
|
|
2144
2152
|
* - Otherwise, pass through the resulting points and message.
|
|
2145
2153
|
*/
|
|
2146
|
-
function
|
|
2154
|
+
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2147
2155
|
const options = _.clone(rubric);
|
|
2148
2156
|
_.extend(options, {
|
|
2149
2157
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2393,6 +2401,9 @@ class Expression extends React.Component {
|
|
|
2393
2401
|
getUserInput() {
|
|
2394
2402
|
return Expression.getUserInputFromProps(this.props);
|
|
2395
2403
|
}
|
|
2404
|
+
getPromptJSON() {
|
|
2405
|
+
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2406
|
+
}
|
|
2396
2407
|
focusInputPath(inputPath) {
|
|
2397
2408
|
// eslint-disable-next-line react/no-string-refs
|
|
2398
2409
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
@@ -2643,7 +2654,7 @@ var Expression$1 = {
|
|
|
2643
2654
|
propUpgrades: propUpgrades$2,
|
|
2644
2655
|
// For use by the editor
|
|
2645
2656
|
isLintable: true,
|
|
2646
|
-
|
|
2657
|
+
scorer: scoreExpression,
|
|
2647
2658
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2648
2659
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2649
2660
|
if (correctAnswers.length === 0) {
|
|
@@ -9330,7 +9341,7 @@ class Graphie$1 {
|
|
|
9330
9341
|
// We round the coordinates to make testing easier, because trig
|
|
9331
9342
|
// operations return unround numbers.
|
|
9332
9343
|
// TODO(benchristel): move rounding to scalePoint?
|
|
9333
|
-
const round = x => number.round(x, 6);
|
|
9344
|
+
const round = x => number$1.round(x, 6);
|
|
9334
9345
|
const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
|
|
9335
9346
|
const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
|
|
9336
9347
|
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
@@ -10887,7 +10898,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
10887
10898
|
return eq$3(factors[0], factor);
|
|
10888
10899
|
});
|
|
10889
10900
|
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
10890
|
-
return number.equal(pair[0], pair[1], tolerance);
|
|
10901
|
+
return number$1.equal(pair[0], pair[1], tolerance);
|
|
10891
10902
|
});
|
|
10892
10903
|
if (same && congruentEnough) {
|
|
10893
10904
|
return true;
|
|
@@ -17914,7 +17925,7 @@ class InteractionTracker {
|
|
|
17914
17925
|
*/
|
|
17915
17926
|
const arrayRules = {
|
|
17916
17927
|
fence: {
|
|
17917
|
-
match: SimpleMarkdown.defaultRules.fence.match,
|
|
17928
|
+
match: SimpleMarkdown$1.defaultRules.fence.match,
|
|
17918
17929
|
order: 1,
|
|
17919
17930
|
parse: (capture, state, parse) => ({
|
|
17920
17931
|
type: "codeBlock",
|
|
@@ -17923,14 +17934,14 @@ const arrayRules = {
|
|
|
17923
17934
|
})
|
|
17924
17935
|
},
|
|
17925
17936
|
paragraph: {
|
|
17926
|
-
match: SimpleMarkdown.defaultRules.paragraph.match,
|
|
17937
|
+
match: SimpleMarkdown$1.defaultRules.paragraph.match,
|
|
17927
17938
|
order: 2,
|
|
17928
17939
|
parse: (capture, state, parse) => ({
|
|
17929
17940
|
content: capture[1]
|
|
17930
17941
|
})
|
|
17931
17942
|
}
|
|
17932
17943
|
};
|
|
17933
|
-
const builtArrayParser = SimpleMarkdown.parserFor(arrayRules);
|
|
17944
|
+
const builtArrayParser = SimpleMarkdown$1.parserFor(arrayRules);
|
|
17934
17945
|
|
|
17935
17946
|
// This should just return an array of strings! magick!
|
|
17936
17947
|
const parseToArray = source => {
|
|
@@ -18355,7 +18366,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18355
18366
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18356
18367
|
table: _extends({}, pureMarkdownRules.table, {
|
|
18357
18368
|
react: function (node, output, state) {
|
|
18358
|
-
const table = SimpleMarkdown.defaultRules.table.react(node, output, state);
|
|
18369
|
+
const table = SimpleMarkdown$1.defaultRules.table.react(node, output, state);
|
|
18359
18370
|
if (!state.isMobile) {
|
|
18360
18371
|
return table;
|
|
18361
18372
|
}
|
|
@@ -18404,7 +18415,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18404
18415
|
contents = rules$1.lint.react(node.table, output, state);
|
|
18405
18416
|
}
|
|
18406
18417
|
} else {
|
|
18407
|
-
const tableOutput = SimpleMarkdown.defaultRules.table.react(node.table, output, state);
|
|
18418
|
+
const tableOutput = SimpleMarkdown$1.defaultRules.table.react(node.table, output, state);
|
|
18408
18419
|
const caption = /*#__PURE__*/React.createElement("caption", {
|
|
18409
18420
|
key: "caption",
|
|
18410
18421
|
className: "perseus-table-title"
|
|
@@ -18474,7 +18485,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18474
18485
|
// window and handle reverse tabnapping phishing attacks
|
|
18475
18486
|
link: _extends({}, pureMarkdownRules.link, {
|
|
18476
18487
|
react: function (node, output, state) {
|
|
18477
|
-
const link = SimpleMarkdown.defaultRules.link.react(node, output, state);
|
|
18488
|
+
const link = SimpleMarkdown$1.defaultRules.link.react(node, output, state);
|
|
18478
18489
|
const href = link.props.href;
|
|
18479
18490
|
let rel = null;
|
|
18480
18491
|
// The href prop should always be here, but just in case somehow the article's
|
|
@@ -18507,7 +18518,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18507
18518
|
className: "perseus-markdown-alt perseus-sr-only"
|
|
18508
18519
|
}, output(node.content, state));
|
|
18509
18520
|
}
|
|
18510
|
-
return SimpleMarkdown.defaultRules.codeBlock.react(node, output, state);
|
|
18521
|
+
return SimpleMarkdown$1.defaultRules.codeBlock.react(node, output, state);
|
|
18511
18522
|
}
|
|
18512
18523
|
}),
|
|
18513
18524
|
// The lint rule never actually matches anything.
|
|
@@ -18549,7 +18560,7 @@ const inlineNodeTypes = {
|
|
|
18549
18560
|
};
|
|
18550
18561
|
|
|
18551
18562
|
// @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
|
|
18552
|
-
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
18563
|
+
const builtParser$1 = SimpleMarkdown$1.parserFor(rules$1);
|
|
18553
18564
|
const parse$3 = (source, state) => {
|
|
18554
18565
|
const paragraphedSource = source + "\n\n";
|
|
18555
18566
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
@@ -18642,10 +18653,10 @@ var PerseusMarkdown = {
|
|
|
18642
18653
|
traverseContent: traverseContent,
|
|
18643
18654
|
parse: parse$3,
|
|
18644
18655
|
parseInline: inlineParser,
|
|
18645
|
-
reactFor: SimpleMarkdown.reactFor,
|
|
18646
|
-
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
18647
|
-
basicOutput: SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules$1, "react")),
|
|
18648
|
-
sanitizeUrl: SimpleMarkdown.sanitizeUrl
|
|
18656
|
+
reactFor: SimpleMarkdown$1.reactFor,
|
|
18657
|
+
ruleOutput: SimpleMarkdown$1.ruleOutput(rules$1, "react"),
|
|
18658
|
+
basicOutput: SimpleMarkdown$1.reactFor(SimpleMarkdown$1.ruleOutput(rules$1, "react")),
|
|
18659
|
+
sanitizeUrl: SimpleMarkdown$1.sanitizeUrl
|
|
18649
18660
|
};
|
|
18650
18661
|
|
|
18651
18662
|
class QuestionParagraph extends React.Component {
|
|
@@ -18766,9 +18777,9 @@ const getWidgetExport = name => {
|
|
|
18766
18777
|
var _widgets$name3;
|
|
18767
18778
|
return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
|
|
18768
18779
|
};
|
|
18769
|
-
const
|
|
18770
|
-
var _widgets$name$
|
|
18771
|
-
return (_widgets$name$
|
|
18780
|
+
const getWidgetScorer = name => {
|
|
18781
|
+
var _widgets$name$scorer, _widgets$name4;
|
|
18782
|
+
return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
|
|
18772
18783
|
};
|
|
18773
18784
|
const getEditor = name => {
|
|
18774
18785
|
return _.has(editors, name) ? editors[name] : null;
|
|
@@ -19073,7 +19084,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19073
19084
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
19074
19085
|
getWidget: getWidget,
|
|
19075
19086
|
getWidgetExport: getWidgetExport,
|
|
19076
|
-
|
|
19087
|
+
getWidgetScorer: getWidgetScorer,
|
|
19077
19088
|
getEditor: getEditor,
|
|
19078
19089
|
getTransform: getTransform,
|
|
19079
19090
|
getVersion: getVersion,
|
|
@@ -19105,12 +19116,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19105
19116
|
}
|
|
19106
19117
|
let score = null;
|
|
19107
19118
|
const userInput = userInputMap[id];
|
|
19108
|
-
const
|
|
19119
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19109
19120
|
if (widget.type === "group") {
|
|
19110
19121
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19111
19122
|
score = Util.flattenScores(scores);
|
|
19112
|
-
} else if (
|
|
19113
|
-
score =
|
|
19123
|
+
} else if (scorer) {
|
|
19124
|
+
score = scorer(userInput, widget.options, strings, locale);
|
|
19114
19125
|
}
|
|
19115
19126
|
if (score) {
|
|
19116
19127
|
return Util.scoreIsEmpty(score);
|
|
@@ -19135,12 +19146,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19135
19146
|
return;
|
|
19136
19147
|
}
|
|
19137
19148
|
const userInput = userInputMap[id];
|
|
19138
|
-
const
|
|
19149
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19139
19150
|
if (widget.type === "group") {
|
|
19140
19151
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19141
19152
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19142
|
-
} else if (
|
|
19143
|
-
widgetScores[id] =
|
|
19153
|
+
} else if (scorer) {
|
|
19154
|
+
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19144
19155
|
}
|
|
19145
19156
|
});
|
|
19146
19157
|
return widgetScores;
|
|
@@ -19599,42 +19610,6 @@ class Renderer extends React.Component {
|
|
|
19599
19610
|
}
|
|
19600
19611
|
return null;
|
|
19601
19612
|
};
|
|
19602
|
-
this.getWidgetProps = id => {
|
|
19603
|
-
const apiOptions = this.getApiOptions();
|
|
19604
|
-
const widgetProps = this.state.widgetProps[id] || {};
|
|
19605
|
-
|
|
19606
|
-
// The widget needs access to its "rubric" at all times when in review
|
|
19607
|
-
// mode (which is really just part of its widget info).
|
|
19608
|
-
const widgetInfo = this.state.widgetInfo[id];
|
|
19609
|
-
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
19610
|
-
if (!this._interactionTrackers) {
|
|
19611
|
-
this._interactionTrackers = {};
|
|
19612
|
-
}
|
|
19613
|
-
let interactionTracker = this._interactionTrackers[id];
|
|
19614
|
-
if (!interactionTracker) {
|
|
19615
|
-
interactionTracker = this._interactionTrackers[id] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, id, getTracking(widgetInfo && widgetInfo.type));
|
|
19616
|
-
}
|
|
19617
|
-
return _extends({}, widgetProps, {
|
|
19618
|
-
widgetId: id,
|
|
19619
|
-
alignment: widgetInfo && widgetInfo.alignment,
|
|
19620
|
-
static: widgetInfo == null ? void 0 : widgetInfo.static,
|
|
19621
|
-
problemNum: this.props.problemNum,
|
|
19622
|
-
apiOptions: this.getApiOptions(),
|
|
19623
|
-
keypadElement: this.props.keypadElement,
|
|
19624
|
-
questionCompleted: this.props.questionCompleted,
|
|
19625
|
-
showSolutions: this.props.showSolutions,
|
|
19626
|
-
onFocus: _.partial(this._onWidgetFocus, id),
|
|
19627
|
-
onBlur: _.partial(this._onWidgetBlur, id),
|
|
19628
|
-
findWidgets: this.findWidgets,
|
|
19629
|
-
reviewModeRubric: reviewModeRubric,
|
|
19630
|
-
reviewMode: this.props.reviewMode,
|
|
19631
|
-
onChange: (newProps, cb, silent = false) => {
|
|
19632
|
-
this._setWidgetProps(id, newProps, cb, silent);
|
|
19633
|
-
},
|
|
19634
|
-
trackInteraction: interactionTracker.track,
|
|
19635
|
-
isLastUsedWidget: id === this.state.lastUsedWidgetId
|
|
19636
|
-
});
|
|
19637
|
-
};
|
|
19638
19613
|
/**
|
|
19639
19614
|
* Serializes the questions state so it can be recovered.
|
|
19640
19615
|
*
|
|
@@ -20583,9 +20558,9 @@ class Renderer extends React.Component {
|
|
|
20583
20558
|
// WidgetContainers don't update their widgets' props when
|
|
20584
20559
|
// they are re-rendered, so even if they've been
|
|
20585
20560
|
// re-rendered we need to call these methods on them.
|
|
20586
|
-
|
|
20561
|
+
this.widgetIds.forEach(id => {
|
|
20587
20562
|
const container = this._widgetContainers.get(makeContainerId(id));
|
|
20588
|
-
container
|
|
20563
|
+
container == null || container.replaceWidgetProps(this.getWidgetProps(id));
|
|
20589
20564
|
});
|
|
20590
20565
|
if (this.props.serializedState && !_.isEqual(this.props.serializedState, this.getSerializedState())) {
|
|
20591
20566
|
this.restoreSerializedState(this.props.serializedState);
|
|
@@ -20614,6 +20589,42 @@ class Renderer extends React.Component {
|
|
|
20614
20589
|
}
|
|
20615
20590
|
this._isMounted = false;
|
|
20616
20591
|
}
|
|
20592
|
+
getWidgetProps(widgetId) {
|
|
20593
|
+
const apiOptions = this.getApiOptions();
|
|
20594
|
+
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
20595
|
+
|
|
20596
|
+
// The widget needs access to its "rubric" at all times when in review
|
|
20597
|
+
// mode (which is really just part of its widget info).
|
|
20598
|
+
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
20599
|
+
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
20600
|
+
if (!this._interactionTrackers) {
|
|
20601
|
+
this._interactionTrackers = {};
|
|
20602
|
+
}
|
|
20603
|
+
let interactionTracker = this._interactionTrackers[widgetId];
|
|
20604
|
+
if (!interactionTracker) {
|
|
20605
|
+
interactionTracker = this._interactionTrackers[widgetId] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, widgetId, getTracking(widgetInfo && widgetInfo.type));
|
|
20606
|
+
}
|
|
20607
|
+
return _extends({}, widgetProps, {
|
|
20608
|
+
widgetId: widgetId,
|
|
20609
|
+
alignment: widgetInfo && widgetInfo.alignment,
|
|
20610
|
+
static: widgetInfo == null ? void 0 : widgetInfo.static,
|
|
20611
|
+
problemNum: this.props.problemNum,
|
|
20612
|
+
apiOptions: this.getApiOptions(),
|
|
20613
|
+
keypadElement: this.props.keypadElement,
|
|
20614
|
+
questionCompleted: this.props.questionCompleted,
|
|
20615
|
+
showSolutions: this.props.showSolutions,
|
|
20616
|
+
onFocus: _.partial(this._onWidgetFocus, widgetId),
|
|
20617
|
+
onBlur: _.partial(this._onWidgetBlur, widgetId),
|
|
20618
|
+
findWidgets: this.findWidgets,
|
|
20619
|
+
reviewModeRubric: reviewModeRubric,
|
|
20620
|
+
reviewMode: this.props.reviewMode,
|
|
20621
|
+
onChange: (newProps, cb, silent = false) => {
|
|
20622
|
+
this._setWidgetProps(widgetId, newProps, cb, silent);
|
|
20623
|
+
},
|
|
20624
|
+
trackInteraction: interactionTracker.track,
|
|
20625
|
+
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20626
|
+
});
|
|
20627
|
+
}
|
|
20617
20628
|
emptyWidgets() {
|
|
20618
20629
|
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20619
20630
|
}
|
|
@@ -20650,6 +20661,25 @@ class Renderer extends React.Component {
|
|
|
20650
20661
|
});
|
|
20651
20662
|
return userInputMap;
|
|
20652
20663
|
}
|
|
20664
|
+
/**
|
|
20665
|
+
* Returns a JSON representation of the content and widgets
|
|
20666
|
+
* that can be passed to an LLM for prompt context.
|
|
20667
|
+
*/
|
|
20668
|
+
getPromptJSON() {
|
|
20669
|
+
const {
|
|
20670
|
+
content
|
|
20671
|
+
} = this.props;
|
|
20672
|
+
const widgetJSON = {};
|
|
20673
|
+
this.widgetIds.forEach(id => {
|
|
20674
|
+
const widget = this.getWidgetInstance(id);
|
|
20675
|
+
widgetJSON[id] = (widget == null || widget.getPromptJSON == null ? void 0 : widget.getPromptJSON()) || {};
|
|
20676
|
+
});
|
|
20677
|
+
return {
|
|
20678
|
+
content,
|
|
20679
|
+
widgets: widgetJSON
|
|
20680
|
+
};
|
|
20681
|
+
}
|
|
20682
|
+
|
|
20653
20683
|
/**
|
|
20654
20684
|
* Returns an object mapping from widget ID to perseus-style score.
|
|
20655
20685
|
* The keys of this object are the values of the array returned
|
|
@@ -21389,6 +21419,19 @@ SimpleKeypadInput.propTypes = {
|
|
|
21389
21419
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
21390
21420
|
};
|
|
21391
21421
|
|
|
21422
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
21423
|
+
return {
|
|
21424
|
+
type: "input-number",
|
|
21425
|
+
options: {
|
|
21426
|
+
simplify: renderProps.simplify,
|
|
21427
|
+
answerType: renderProps.answerType
|
|
21428
|
+
},
|
|
21429
|
+
userInput: {
|
|
21430
|
+
value: userInput.currentValue
|
|
21431
|
+
}
|
|
21432
|
+
};
|
|
21433
|
+
};
|
|
21434
|
+
|
|
21392
21435
|
/*
|
|
21393
21436
|
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21394
21437
|
* curly brackets.
|
|
@@ -21579,7 +21622,7 @@ const answerTypes = {
|
|
|
21579
21622
|
forms: "pi"
|
|
21580
21623
|
}
|
|
21581
21624
|
};
|
|
21582
|
-
function
|
|
21625
|
+
function scoreInputNumber(userInput, rubric, strings) {
|
|
21583
21626
|
if (rubric.answerType == null) {
|
|
21584
21627
|
rubric.answerType = "number";
|
|
21585
21628
|
}
|
|
@@ -21713,6 +21756,9 @@ class InputNumber extends React.Component {
|
|
|
21713
21756
|
getUserInput() {
|
|
21714
21757
|
return InputNumber.getUserInputFromProps(this.props);
|
|
21715
21758
|
}
|
|
21759
|
+
getPromptJSON() {
|
|
21760
|
+
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21761
|
+
}
|
|
21716
21762
|
render() {
|
|
21717
21763
|
if (this.props.apiOptions.customKeypad) {
|
|
21718
21764
|
// TODO(charlie): Support "Review Mode".
|
|
@@ -21814,7 +21860,7 @@ var InputNumber$1 = {
|
|
|
21814
21860
|
widget: InputNumber,
|
|
21815
21861
|
transform: propTransform$3,
|
|
21816
21862
|
isLintable: true,
|
|
21817
|
-
|
|
21863
|
+
scorer: scoreInputNumber,
|
|
21818
21864
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
21819
21865
|
if (rubric.value == null) {
|
|
21820
21866
|
return;
|
|
@@ -21827,6 +21873,16 @@ var InputNumber$1 = {
|
|
|
21827
21873
|
}
|
|
21828
21874
|
};
|
|
21829
21875
|
|
|
21876
|
+
const getPromptJSON$r = (renderProps, userInput) => {
|
|
21877
|
+
return {
|
|
21878
|
+
type: "numeric-input",
|
|
21879
|
+
label: renderProps.labelText,
|
|
21880
|
+
userInput: {
|
|
21881
|
+
value: userInput.currentValue
|
|
21882
|
+
}
|
|
21883
|
+
};
|
|
21884
|
+
};
|
|
21885
|
+
|
|
21830
21886
|
const ParseTex = TexWrangler.parseTex;
|
|
21831
21887
|
const answerFormButtons = [{
|
|
21832
21888
|
title: "Integers",
|
|
@@ -21885,7 +21941,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
21885
21941
|
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
21886
21942
|
return value;
|
|
21887
21943
|
}
|
|
21888
|
-
function
|
|
21944
|
+
function scoreNumericInput(userInput, rubric, strings) {
|
|
21889
21945
|
var _matchedAnswer$messag;
|
|
21890
21946
|
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
21891
21947
|
// Don't default to validating the answer as a pi answer
|
|
@@ -22056,6 +22112,9 @@ class NumericInput extends React.Component {
|
|
|
22056
22112
|
getUserInput() {
|
|
22057
22113
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22058
22114
|
}
|
|
22115
|
+
getPromptJSON() {
|
|
22116
|
+
return getPromptJSON$r(this.props, this.getUserInput());
|
|
22117
|
+
}
|
|
22059
22118
|
render() {
|
|
22060
22119
|
let labelText = this.props.labelText;
|
|
22061
22120
|
if (labelText == null || labelText === "") {
|
|
@@ -22180,7 +22239,7 @@ var NumericInput$1 = {
|
|
|
22180
22239
|
widget: NumericInput,
|
|
22181
22240
|
transform: propsTransform,
|
|
22182
22241
|
isLintable: true,
|
|
22183
|
-
|
|
22242
|
+
scorer: scoreNumericInput,
|
|
22184
22243
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22185
22244
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22186
22245
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -22211,15 +22270,43 @@ var NumericInput$1 = {
|
|
|
22211
22270
|
}
|
|
22212
22271
|
};
|
|
22213
22272
|
|
|
22273
|
+
const getPromptJSON$q = (renderProps, userInput) => {
|
|
22274
|
+
const choices = renderProps.choices || [];
|
|
22275
|
+
const options = choices.map(choice => {
|
|
22276
|
+
return {
|
|
22277
|
+
value: choice.content
|
|
22278
|
+
};
|
|
22279
|
+
});
|
|
22280
|
+
return {
|
|
22281
|
+
type: "radio",
|
|
22282
|
+
hasNoneOfTheAbove: !!renderProps.hasNoneOfTheAbove,
|
|
22283
|
+
options,
|
|
22284
|
+
userInput: {
|
|
22285
|
+
selectedOptions: userInput.choicesSelected.slice()
|
|
22286
|
+
}
|
|
22287
|
+
};
|
|
22288
|
+
};
|
|
22289
|
+
|
|
22290
|
+
const getPromptJSON$p = renderProps => {
|
|
22291
|
+
return {
|
|
22292
|
+
type: "passage-ref",
|
|
22293
|
+
options: {
|
|
22294
|
+
passageNumber: renderProps.passageNumber,
|
|
22295
|
+
referenceNumber: renderProps.referenceNumber,
|
|
22296
|
+
summaryText: renderProps.summaryText
|
|
22297
|
+
}
|
|
22298
|
+
};
|
|
22299
|
+
};
|
|
22300
|
+
|
|
22214
22301
|
/**
|
|
22215
|
-
* Several widgets don't have "right"/"wrong"
|
|
22302
|
+
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
22216
22303
|
* so this just says to move on past those widgets
|
|
22217
22304
|
*
|
|
22218
22305
|
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
22219
|
-
*
|
|
22306
|
+
* scoring logic and the thing scoring an exercise
|
|
22220
22307
|
* should just know to skip these
|
|
22221
22308
|
*/
|
|
22222
|
-
function
|
|
22309
|
+
function scoreNoop(points = 0) {
|
|
22223
22310
|
return {
|
|
22224
22311
|
type: "points",
|
|
22225
22312
|
earned: points,
|
|
@@ -23603,6 +23690,17 @@ const styles$x = StyleSheet.create({
|
|
|
23603
23690
|
}
|
|
23604
23691
|
});
|
|
23605
23692
|
|
|
23693
|
+
const getPromptJSON$o = renderProps => {
|
|
23694
|
+
return {
|
|
23695
|
+
type: "passage",
|
|
23696
|
+
options: {
|
|
23697
|
+
passageTitle: renderProps.passageTitle,
|
|
23698
|
+
passageText: renderProps.passageText,
|
|
23699
|
+
footnotes: renderProps.footnotes
|
|
23700
|
+
}
|
|
23701
|
+
};
|
|
23702
|
+
};
|
|
23703
|
+
|
|
23606
23704
|
function getInitialParseState() {
|
|
23607
23705
|
return {
|
|
23608
23706
|
currentRef: [],
|
|
@@ -23652,12 +23750,12 @@ const AltText = ({
|
|
|
23652
23750
|
}));
|
|
23653
23751
|
};
|
|
23654
23752
|
const rules = {
|
|
23655
|
-
newline: SimpleMarkdown.defaultRules.newline,
|
|
23656
|
-
paragraph: SimpleMarkdown.defaultRules.paragraph,
|
|
23657
|
-
escape: SimpleMarkdown.defaultRules.escape,
|
|
23753
|
+
newline: SimpleMarkdown$1.defaultRules.newline,
|
|
23754
|
+
paragraph: SimpleMarkdown$1.defaultRules.paragraph,
|
|
23755
|
+
escape: SimpleMarkdown$1.defaultRules.escape,
|
|
23658
23756
|
passageFootnote: {
|
|
23659
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.1,
|
|
23660
|
-
match: SimpleMarkdown.inlineRegex(/^\^/),
|
|
23757
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.1,
|
|
23758
|
+
match: SimpleMarkdown$1.inlineRegex(/^\^/),
|
|
23661
23759
|
parse: (capture, parse, state) => {
|
|
23662
23760
|
// if no footnotes have been seen, we're id 1. otherwise,
|
|
23663
23761
|
// we're the next subsequent id
|
|
@@ -23686,7 +23784,7 @@ const rules = {
|
|
|
23686
23784
|
}
|
|
23687
23785
|
},
|
|
23688
23786
|
refStart: {
|
|
23689
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.2,
|
|
23787
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.2,
|
|
23690
23788
|
match: function (source) {
|
|
23691
23789
|
const capture = /^\{\{/.exec(source);
|
|
23692
23790
|
if (capture) {
|
|
@@ -23772,8 +23870,8 @@ const rules = {
|
|
|
23772
23870
|
}
|
|
23773
23871
|
},
|
|
23774
23872
|
refEnd: {
|
|
23775
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.3,
|
|
23776
|
-
match: SimpleMarkdown.inlineRegex(/^\}\}/),
|
|
23873
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.3,
|
|
23874
|
+
match: SimpleMarkdown$1.inlineRegex(/^\}\}/),
|
|
23777
23875
|
parse: (capture, parse, state) => {
|
|
23778
23876
|
if (!state.useRefs) {
|
|
23779
23877
|
return {
|
|
@@ -23801,8 +23899,8 @@ const rules = {
|
|
|
23801
23899
|
}
|
|
23802
23900
|
},
|
|
23803
23901
|
squareLabel: {
|
|
23804
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.4,
|
|
23805
|
-
match: SimpleMarkdown.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
23902
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.4,
|
|
23903
|
+
match: SimpleMarkdown$1.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
23806
23904
|
parse: (capture, parse, state) => {
|
|
23807
23905
|
if (!state.firstQuestionRef) {
|
|
23808
23906
|
state.firstQuestionRef = capture[1];
|
|
@@ -23828,8 +23926,8 @@ const rules = {
|
|
|
23828
23926
|
}
|
|
23829
23927
|
},
|
|
23830
23928
|
circleLabel: {
|
|
23831
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.5,
|
|
23832
|
-
match: SimpleMarkdown.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
23929
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.5,
|
|
23930
|
+
match: SimpleMarkdown$1.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
23833
23931
|
parse: (capture, parse, state) => {
|
|
23834
23932
|
return {
|
|
23835
23933
|
content: capture[1],
|
|
@@ -23853,8 +23951,8 @@ const rules = {
|
|
|
23853
23951
|
}
|
|
23854
23952
|
},
|
|
23855
23953
|
squareBracketRef: {
|
|
23856
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.6,
|
|
23857
|
-
match: SimpleMarkdown.inlineRegex(/^\[(\d+)\]( *)/),
|
|
23954
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.6,
|
|
23955
|
+
match: SimpleMarkdown$1.inlineRegex(/^\[(\d+)\]( *)/),
|
|
23858
23956
|
parse: (capture, parse, state) => {
|
|
23859
23957
|
if (!state.firstSentenceRef) {
|
|
23860
23958
|
state.firstSentenceRef = capture[1];
|
|
@@ -23877,8 +23975,8 @@ const rules = {
|
|
|
23877
23975
|
}
|
|
23878
23976
|
},
|
|
23879
23977
|
highlight: {
|
|
23880
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
23881
|
-
match: SimpleMarkdown.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
23978
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.7,
|
|
23979
|
+
match: SimpleMarkdown$1.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
23882
23980
|
parse: (capture, parse, state) => {
|
|
23883
23981
|
return {
|
|
23884
23982
|
content: capture[1]
|
|
@@ -23892,8 +23990,8 @@ const rules = {
|
|
|
23892
23990
|
}
|
|
23893
23991
|
},
|
|
23894
23992
|
reviewHighlight: {
|
|
23895
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
23896
|
-
match: SimpleMarkdown.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
23993
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.7,
|
|
23994
|
+
match: SimpleMarkdown$1.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
23897
23995
|
parse: (capture, parse, state) => {
|
|
23898
23996
|
return {
|
|
23899
23997
|
content: capture[1]
|
|
@@ -23906,11 +24004,11 @@ const rules = {
|
|
|
23906
24004
|
}, node.content)];
|
|
23907
24005
|
}
|
|
23908
24006
|
},
|
|
23909
|
-
strong: SimpleMarkdown.defaultRules.strong,
|
|
23910
|
-
u: SimpleMarkdown.defaultRules.u,
|
|
23911
|
-
em: SimpleMarkdown.defaultRules.em,
|
|
23912
|
-
del: SimpleMarkdown.defaultRules.del,
|
|
23913
|
-
text: _extends({}, SimpleMarkdown.defaultRules.text, {
|
|
24007
|
+
strong: SimpleMarkdown$1.defaultRules.strong,
|
|
24008
|
+
u: SimpleMarkdown$1.defaultRules.u,
|
|
24009
|
+
em: SimpleMarkdown$1.defaultRules.em,
|
|
24010
|
+
del: SimpleMarkdown$1.defaultRules.del,
|
|
24011
|
+
text: _extends({}, SimpleMarkdown$1.defaultRules.text, {
|
|
23914
24012
|
react(node, output, state) {
|
|
23915
24013
|
return /*#__PURE__*/React.createElement("span", {
|
|
23916
24014
|
key: state.key
|
|
@@ -23951,7 +24049,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
23951
24049
|
borderRadius: "50%",
|
|
23952
24050
|
textAlign: "center"
|
|
23953
24051
|
};
|
|
23954
|
-
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24052
|
+
const builtParser = SimpleMarkdown$1.parserFor(rules);
|
|
23955
24053
|
const parse$2 = (source, state) => {
|
|
23956
24054
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
23957
24055
|
state = state || {};
|
|
@@ -23960,7 +24058,7 @@ const parse$2 = (source, state) => {
|
|
|
23960
24058
|
};
|
|
23961
24059
|
|
|
23962
24060
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
23963
|
-
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
24061
|
+
const output = SimpleMarkdown$1.reactFor(SimpleMarkdown$1.ruleOutput(rules, "react"));
|
|
23964
24062
|
var PassageMarkdown = {
|
|
23965
24063
|
parse: parse$2,
|
|
23966
24064
|
output,
|
|
@@ -24233,6 +24331,9 @@ class Passage extends React.Component {
|
|
|
24233
24331
|
content: refContent
|
|
24234
24332
|
};
|
|
24235
24333
|
}
|
|
24334
|
+
getPromptJSON() {
|
|
24335
|
+
return getPromptJSON$o(this.props);
|
|
24336
|
+
}
|
|
24236
24337
|
|
|
24237
24338
|
/**
|
|
24238
24339
|
* Rendering
|
|
@@ -24387,7 +24488,7 @@ var Passage$1 = {
|
|
|
24387
24488
|
return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
24388
24489
|
},
|
|
24389
24490
|
isLintable: true,
|
|
24390
|
-
|
|
24491
|
+
scorer: () => scoreNoop()
|
|
24391
24492
|
};
|
|
24392
24493
|
|
|
24393
24494
|
/**
|
|
@@ -24462,6 +24563,9 @@ class PassageRef extends React.Component {
|
|
|
24462
24563
|
window.removeEventListener("resize", this._throttledUpdateRange);
|
|
24463
24564
|
this._isMounted = false;
|
|
24464
24565
|
}
|
|
24566
|
+
getPromptJSON() {
|
|
24567
|
+
return getPromptJSON$p(this.props);
|
|
24568
|
+
}
|
|
24465
24569
|
render() {
|
|
24466
24570
|
const {
|
|
24467
24571
|
strings
|
|
@@ -24517,8 +24621,8 @@ var PassageRef$1 = {
|
|
|
24517
24621
|
major: 0,
|
|
24518
24622
|
minor: 1
|
|
24519
24623
|
},
|
|
24520
|
-
// TODO: things that aren't interactive shouldn't need
|
|
24521
|
-
|
|
24624
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
24625
|
+
scorer: () => scoreNoop()
|
|
24522
24626
|
};
|
|
24523
24627
|
|
|
24524
24628
|
/**
|
|
@@ -25876,7 +25980,7 @@ const styles$p = StyleSheet.create({
|
|
|
25876
25980
|
}
|
|
25877
25981
|
});
|
|
25878
25982
|
|
|
25879
|
-
function
|
|
25983
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
25880
25984
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25881
25985
|
return sum + (selected ? 1 : 0);
|
|
25882
25986
|
}, 0);
|
|
@@ -25886,10 +25990,10 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25886
25990
|
message: null
|
|
25887
25991
|
};
|
|
25888
25992
|
}
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
if (
|
|
25993
|
+
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
25994
|
+
return currentChoice.correct ? sum + 1 : sum;
|
|
25995
|
+
}, 0);
|
|
25996
|
+
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
25893
25997
|
return {
|
|
25894
25998
|
type: "invalid",
|
|
25895
25999
|
message: strings.chooseCorrectNum
|
|
@@ -25897,9 +26001,8 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25897
26001
|
// If NOTA and some other answer are checked, ...
|
|
25898
26002
|
}
|
|
25899
26003
|
|
|
25900
|
-
|
|
25901
|
-
|
|
25902
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
26004
|
+
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
26005
|
+
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
25903
26006
|
return {
|
|
25904
26007
|
type: "invalid",
|
|
25905
26008
|
message: strings.notNoneOfTheAbove
|
|
@@ -25907,9 +26010,7 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25907
26010
|
}
|
|
25908
26011
|
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
25909
26012
|
let isCorrect;
|
|
25910
|
-
|
|
25911
|
-
// instead of the userInput?
|
|
25912
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
26013
|
+
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
25913
26014
|
isCorrect = rubric.choices.every((choice, j) => {
|
|
25914
26015
|
return i === j || !choice.correct;
|
|
25915
26016
|
});
|
|
@@ -26031,7 +26132,7 @@ class Radio$1 extends React.Component {
|
|
|
26031
26132
|
choiceStates
|
|
26032
26133
|
} = this.props;
|
|
26033
26134
|
if (choiceStates) {
|
|
26034
|
-
const score =
|
|
26135
|
+
const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
|
|
26035
26136
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26036
26137
|
const newStates = choiceStates.map(state => _extends({}, state, {
|
|
26037
26138
|
highlighted: state.selected,
|
|
@@ -26079,32 +26180,19 @@ class Radio$1 extends React.Component {
|
|
|
26079
26180
|
}
|
|
26080
26181
|
};
|
|
26081
26182
|
}
|
|
26082
|
-
static getUserInputFromProps(props) {
|
|
26183
|
+
static getUserInputFromProps(props, unshuffle = true) {
|
|
26083
26184
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
26084
26185
|
// future timeline implementers: this used to be {value: i} before
|
|
26085
26186
|
// multiple select was added)
|
|
26086
26187
|
if (props.choiceStates) {
|
|
26087
|
-
let noneOfTheAboveIndex = null;
|
|
26088
|
-
let noneOfTheAboveSelected = false;
|
|
26089
26188
|
const choiceStates = props.choiceStates;
|
|
26090
26189
|
const choicesSelected = choiceStates.map(() => false);
|
|
26091
|
-
const numCorrect = props.numCorrect;
|
|
26092
26190
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26093
|
-
const index = props.choices[i].originalIndex;
|
|
26191
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26094
26192
|
choicesSelected[index] = choiceStates[i].selected;
|
|
26095
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26096
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26097
|
-
noneOfTheAboveIndex = index;
|
|
26098
|
-
if (choicesSelected[i]) {
|
|
26099
|
-
noneOfTheAboveSelected = true;
|
|
26100
|
-
}
|
|
26101
|
-
}
|
|
26102
26193
|
}
|
|
26103
26194
|
return {
|
|
26104
|
-
choicesSelected
|
|
26105
|
-
numCorrect,
|
|
26106
|
-
noneOfTheAboveIndex,
|
|
26107
|
-
noneOfTheAboveSelected
|
|
26195
|
+
choicesSelected
|
|
26108
26196
|
};
|
|
26109
26197
|
// Support legacy choiceState implementation
|
|
26110
26198
|
}
|
|
@@ -26113,27 +26201,14 @@ class Radio$1 extends React.Component {
|
|
|
26113
26201
|
values
|
|
26114
26202
|
} = props;
|
|
26115
26203
|
if (values) {
|
|
26116
|
-
let noneOfTheAboveIndex = null;
|
|
26117
|
-
let noneOfTheAboveSelected = false;
|
|
26118
26204
|
const choicesSelected = [...values];
|
|
26119
|
-
const numCorrect = props.numCorrect;
|
|
26120
26205
|
const valuesLength = values.length;
|
|
26121
26206
|
for (let i = 0; i < valuesLength; i++) {
|
|
26122
|
-
const index = props.choices[i].originalIndex;
|
|
26207
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26123
26208
|
choicesSelected[index] = values[i];
|
|
26124
|
-
if (props.choices[i].isNoneOfTheAbove) {
|
|
26125
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
26126
|
-
noneOfTheAboveIndex = index;
|
|
26127
|
-
if (choicesSelected[i]) {
|
|
26128
|
-
noneOfTheAboveSelected = true;
|
|
26129
|
-
}
|
|
26130
|
-
}
|
|
26131
26209
|
}
|
|
26132
26210
|
return {
|
|
26133
|
-
choicesSelected
|
|
26134
|
-
noneOfTheAboveIndex,
|
|
26135
|
-
noneOfTheAboveSelected,
|
|
26136
|
-
numCorrect
|
|
26211
|
+
choicesSelected
|
|
26137
26212
|
};
|
|
26138
26213
|
}
|
|
26139
26214
|
// Nothing checked
|
|
@@ -26165,6 +26240,10 @@ class Radio$1 extends React.Component {
|
|
|
26165
26240
|
getUserInput() {
|
|
26166
26241
|
return Radio$1.getUserInputFromProps(this.props);
|
|
26167
26242
|
}
|
|
26243
|
+
getPromptJSON() {
|
|
26244
|
+
const userInput = Radio$1.getUserInputFromProps(this.props, false);
|
|
26245
|
+
return getPromptJSON$q(this.props, userInput);
|
|
26246
|
+
}
|
|
26168
26247
|
render() {
|
|
26169
26248
|
const {
|
|
26170
26249
|
choices
|
|
@@ -26402,13 +26481,26 @@ var Radio = {
|
|
|
26402
26481
|
},
|
|
26403
26482
|
propUpgrades: propUpgrades$1,
|
|
26404
26483
|
isLintable: true,
|
|
26405
|
-
|
|
26484
|
+
scorer: scoreRadio
|
|
26406
26485
|
};
|
|
26407
26486
|
|
|
26408
26487
|
/* eslint-disable import/no-named-as-default */
|
|
26409
26488
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
26410
26489
|
|
|
26411
|
-
|
|
26490
|
+
const getPromptJSON$n = (renderProps, userInput) => {
|
|
26491
|
+
return {
|
|
26492
|
+
type: "categorizer",
|
|
26493
|
+
options: {
|
|
26494
|
+
items: renderProps.items,
|
|
26495
|
+
categories: renderProps.categories
|
|
26496
|
+
},
|
|
26497
|
+
userInput: {
|
|
26498
|
+
itemToCategoryMapping: userInput.values
|
|
26499
|
+
}
|
|
26500
|
+
};
|
|
26501
|
+
};
|
|
26502
|
+
|
|
26503
|
+
function scoreCategorizer(userInput, rubric, strings) {
|
|
26412
26504
|
let completed = true;
|
|
26413
26505
|
let allCorrect = true;
|
|
26414
26506
|
rubric.values.forEach((value, i) => {
|
|
@@ -26453,6 +26545,9 @@ class Categorizer extends React.Component {
|
|
|
26453
26545
|
getUserInput() {
|
|
26454
26546
|
return Categorizer.getUserInputFromProps(this.props);
|
|
26455
26547
|
}
|
|
26548
|
+
getPromptJSON() {
|
|
26549
|
+
return getPromptJSON$n(this.props, this.getUserInput());
|
|
26550
|
+
}
|
|
26456
26551
|
onChange(itemNum, catNum) {
|
|
26457
26552
|
const values = [...this.props.values];
|
|
26458
26553
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -26606,7 +26701,7 @@ var Categorizer$1 = {
|
|
|
26606
26701
|
return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
26607
26702
|
},
|
|
26608
26703
|
isLintable: true,
|
|
26609
|
-
|
|
26704
|
+
scorer: scoreCategorizer
|
|
26610
26705
|
};
|
|
26611
26706
|
|
|
26612
26707
|
/**
|
|
@@ -26656,7 +26751,19 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
26656
26751
|
*/
|
|
26657
26752
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
26658
26753
|
|
|
26659
|
-
|
|
26754
|
+
const getUnsupportedPromptJSON = (widgetType, message = "") => {
|
|
26755
|
+
return {
|
|
26756
|
+
type: widgetType,
|
|
26757
|
+
isSupported: false,
|
|
26758
|
+
message
|
|
26759
|
+
};
|
|
26760
|
+
};
|
|
26761
|
+
|
|
26762
|
+
const getPromptJSON$m = () => {
|
|
26763
|
+
return getUnsupportedPromptJSON("cs-program");
|
|
26764
|
+
};
|
|
26765
|
+
|
|
26766
|
+
function scoreCSProgram(state) {
|
|
26660
26767
|
// The iframe can tell us whether it's correct or incorrect,
|
|
26661
26768
|
// and pass an optional message
|
|
26662
26769
|
if (state.status === "correct") {
|
|
@@ -26742,6 +26849,9 @@ class CSProgram extends React.Component {
|
|
|
26742
26849
|
message: this.props.message
|
|
26743
26850
|
};
|
|
26744
26851
|
}
|
|
26852
|
+
getPromptJSON() {
|
|
26853
|
+
return getPromptJSON$m();
|
|
26854
|
+
}
|
|
26745
26855
|
render() {
|
|
26746
26856
|
if (!this.props.programID) {
|
|
26747
26857
|
return /*#__PURE__*/React.createElement("div", null);
|
|
@@ -26824,7 +26934,15 @@ var CSProgram$1 = {
|
|
|
26824
26934
|
supportedAlignments: ["block", "full-width"],
|
|
26825
26935
|
widget: CSProgram,
|
|
26826
26936
|
hidden: true,
|
|
26827
|
-
|
|
26937
|
+
scorer: scoreCSProgram
|
|
26938
|
+
};
|
|
26939
|
+
|
|
26940
|
+
const getPromptJSON$l = renderProps => {
|
|
26941
|
+
return {
|
|
26942
|
+
type: "definition",
|
|
26943
|
+
definition: renderProps.definition,
|
|
26944
|
+
togglePrompt: renderProps.togglePrompt
|
|
26945
|
+
};
|
|
26828
26946
|
};
|
|
26829
26947
|
|
|
26830
26948
|
class Definition extends React.Component {
|
|
@@ -26834,6 +26952,9 @@ class Definition extends React.Component {
|
|
|
26834
26952
|
// doesn't implement any Widget methods
|
|
26835
26953
|
this.isWidget = true;
|
|
26836
26954
|
}
|
|
26955
|
+
getPromptJSON() {
|
|
26956
|
+
return getPromptJSON$l(this.props);
|
|
26957
|
+
}
|
|
26837
26958
|
render() {
|
|
26838
26959
|
return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
|
|
26839
26960
|
activeDefinitionId,
|
|
@@ -26889,8 +27010,8 @@ var Definition$1 = {
|
|
|
26889
27010
|
defaultAlignment: "inline",
|
|
26890
27011
|
widget: Definition,
|
|
26891
27012
|
transform: x => x,
|
|
26892
|
-
// TODO: things that aren't interactive shouldn't need
|
|
26893
|
-
|
|
27013
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27014
|
+
scorer: () => scoreNoop()
|
|
26894
27015
|
};
|
|
26895
27016
|
|
|
26896
27017
|
// The props are type `any` on purpose so that this can receive props
|
|
@@ -26921,11 +27042,23 @@ var DeprecatedStandin$1 = {
|
|
|
26921
27042
|
displayName: "Deprecated Standin",
|
|
26922
27043
|
widget: DeprecatedStandin,
|
|
26923
27044
|
hidden: true,
|
|
26924
|
-
// TODO: things that aren't interactive shouldn't need
|
|
26925
|
-
|
|
27045
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27046
|
+
scorer: () => scoreNoop(1)
|
|
27047
|
+
};
|
|
27048
|
+
|
|
27049
|
+
const getPromptJSON$k = (renderProps, userInput) => {
|
|
27050
|
+
return {
|
|
27051
|
+
type: "dropdown",
|
|
27052
|
+
options: {
|
|
27053
|
+
items: renderProps.choices
|
|
27054
|
+
},
|
|
27055
|
+
userInput: {
|
|
27056
|
+
selectedIndex: userInput.value - 1 // Offset to account for placeholder
|
|
27057
|
+
}
|
|
27058
|
+
};
|
|
26926
27059
|
};
|
|
26927
27060
|
|
|
26928
|
-
function
|
|
27061
|
+
function scoreDropdown(userInput, rubric) {
|
|
26929
27062
|
const selected = userInput.value;
|
|
26930
27063
|
if (selected === 0) {
|
|
26931
27064
|
return {
|
|
@@ -26968,6 +27101,9 @@ class Dropdown extends React.Component {
|
|
|
26968
27101
|
value: this.props.selected
|
|
26969
27102
|
};
|
|
26970
27103
|
}
|
|
27104
|
+
getPromptJSON() {
|
|
27105
|
+
return getPromptJSON$k(this.props, this.getUserInput());
|
|
27106
|
+
}
|
|
26971
27107
|
render() {
|
|
26972
27108
|
const children = [/*#__PURE__*/React.createElement(OptionItem, {
|
|
26973
27109
|
key: "placeholder",
|
|
@@ -27015,7 +27151,15 @@ var Dropdown$1 = {
|
|
|
27015
27151
|
accessible: true,
|
|
27016
27152
|
widget: Dropdown,
|
|
27017
27153
|
transform: optionsTransform,
|
|
27018
|
-
|
|
27154
|
+
scorer: scoreDropdown
|
|
27155
|
+
};
|
|
27156
|
+
|
|
27157
|
+
const getPromptJSON$j = renderProps => {
|
|
27158
|
+
return {
|
|
27159
|
+
type: "explanation",
|
|
27160
|
+
showPrompt: renderProps.showPrompt,
|
|
27161
|
+
explanation: renderProps.explanation
|
|
27162
|
+
};
|
|
27019
27163
|
};
|
|
27020
27164
|
|
|
27021
27165
|
// transform = _.identity
|
|
@@ -27052,6 +27196,9 @@ class Explanation extends React.Component {
|
|
|
27052
27196
|
componentWillUnmount() {
|
|
27053
27197
|
this._mounted = false;
|
|
27054
27198
|
}
|
|
27199
|
+
getPromptJSON() {
|
|
27200
|
+
return getPromptJSON$j(this.props);
|
|
27201
|
+
}
|
|
27055
27202
|
render() {
|
|
27056
27203
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
27057
27204
|
const caretIcon = this.state.expanded ? caretUp : caretDown;
|
|
@@ -27166,8 +27313,25 @@ var Explanation$1 = {
|
|
|
27166
27313
|
widget: Explanation,
|
|
27167
27314
|
transform: _.identity,
|
|
27168
27315
|
isLintable: true,
|
|
27169
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27170
|
-
|
|
27316
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27317
|
+
scorer: () => scoreNoop()
|
|
27318
|
+
};
|
|
27319
|
+
|
|
27320
|
+
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
27321
|
+
if (!rendererJSON) {
|
|
27322
|
+
return {
|
|
27323
|
+
type: "graded-group",
|
|
27324
|
+
title,
|
|
27325
|
+
content: "",
|
|
27326
|
+
widgets: {},
|
|
27327
|
+
hint: hintRendererJSON
|
|
27328
|
+
};
|
|
27329
|
+
}
|
|
27330
|
+
return _extends({}, rendererJSON, {
|
|
27331
|
+
title,
|
|
27332
|
+
type: "graded-group",
|
|
27333
|
+
hint: hintRendererJSON
|
|
27334
|
+
});
|
|
27171
27335
|
};
|
|
27172
27336
|
|
|
27173
27337
|
class GradedGroupAnswerBar extends React.Component {
|
|
@@ -27302,6 +27466,8 @@ class GradedGroup extends React.Component {
|
|
|
27302
27466
|
message: "",
|
|
27303
27467
|
answerBarState: "HIDDEN"
|
|
27304
27468
|
};
|
|
27469
|
+
this.rendererRef = /*#__PURE__*/React.createRef();
|
|
27470
|
+
this.hintRendererRef = /*#__PURE__*/React.createRef();
|
|
27305
27471
|
this.change = (...args) => {
|
|
27306
27472
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
27307
27473
|
return change.apply(this, args);
|
|
@@ -27317,13 +27483,9 @@ class GradedGroup extends React.Component {
|
|
|
27317
27483
|
status: GRADING_STATUSES.ungraded,
|
|
27318
27484
|
message: ""
|
|
27319
27485
|
});
|
|
27320
|
-
|
|
27321
|
-
// eslint-disable-next-line react/no-string-refs
|
|
27322
|
-
if (this.refs.renderer) {
|
|
27486
|
+
if (this.rendererRef.current) {
|
|
27323
27487
|
this.change("widgets", this.props.widgets);
|
|
27324
|
-
|
|
27325
|
-
// @ts-expect-error - TS2339 - Property 'emptyWidgets' does not exist on type 'ReactInstance'.
|
|
27326
|
-
const emptyWidgets = this.refs.renderer.emptyWidgets();
|
|
27488
|
+
const emptyWidgets = this.rendererRef.current.emptyWidgets();
|
|
27327
27489
|
const answerable = emptyWidgets.length === 0;
|
|
27328
27490
|
const answerBarState = this.state.answerBarState;
|
|
27329
27491
|
this.setState({
|
|
@@ -27332,12 +27494,11 @@ class GradedGroup extends React.Component {
|
|
|
27332
27494
|
}
|
|
27333
27495
|
};
|
|
27334
27496
|
this._checkAnswer = () => {
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
this.
|
|
27338
|
-
|
|
27339
|
-
|
|
27340
|
-
const score = this.refs.renderer.score();
|
|
27497
|
+
var _this$rendererRef$cur, _this$rendererRef$cur2;
|
|
27498
|
+
(_this$rendererRef$cur = this.rendererRef.current) == null || _this$rendererRef$cur.showRationalesForCurrentlySelectedChoices();
|
|
27499
|
+
const score = ((_this$rendererRef$cur2 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur2.score()) || {
|
|
27500
|
+
type: "invalid"
|
|
27501
|
+
};
|
|
27341
27502
|
const {
|
|
27342
27503
|
INVALID_MESSAGE_PREFIX,
|
|
27343
27504
|
DEFAULT_INVALID_MESSAGE_1,
|
|
@@ -27357,34 +27518,39 @@ class GradedGroup extends React.Component {
|
|
|
27357
27518
|
};
|
|
27358
27519
|
// Mobile API
|
|
27359
27520
|
this.getInputPaths = () => {
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
return this.refs.renderer.getInputPaths();
|
|
27521
|
+
var _this$rendererRef$cur3;
|
|
27522
|
+
return ((_this$rendererRef$cur3 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur3.getInputPaths()) || [];
|
|
27363
27523
|
};
|
|
27364
27524
|
this.setInputValue = (path, newValue, cb) => {
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
return this.refs.renderer.setInputValue(path, newValue, cb);
|
|
27525
|
+
var _this$rendererRef$cur4;
|
|
27526
|
+
return (_this$rendererRef$cur4 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur4.setInputValue(path, newValue, cb);
|
|
27368
27527
|
};
|
|
27369
27528
|
this.focus = () => {
|
|
27370
|
-
|
|
27371
|
-
|
|
27372
|
-
return this.refs.renderer.focus();
|
|
27529
|
+
var _this$rendererRef$cur5;
|
|
27530
|
+
return !!((_this$rendererRef$cur5 = this.rendererRef.current) != null && _this$rendererRef$cur5.focus());
|
|
27373
27531
|
};
|
|
27374
27532
|
this.focusInputPath = path => {
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
this.refs.renderer.focusPath(path);
|
|
27533
|
+
var _this$rendererRef$cur6;
|
|
27534
|
+
(_this$rendererRef$cur6 = this.rendererRef.current) == null || _this$rendererRef$cur6.focusPath(path);
|
|
27378
27535
|
};
|
|
27379
27536
|
this.blurInputPath = path => {
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
this.refs.renderer.blurPath(path);
|
|
27537
|
+
var _this$rendererRef$cur7;
|
|
27538
|
+
(_this$rendererRef$cur7 = this.rendererRef.current) == null || _this$rendererRef$cur7.blurPath(path);
|
|
27383
27539
|
};
|
|
27384
27540
|
}
|
|
27385
27541
|
shouldComponentUpdate(nextProps, nextState) {
|
|
27386
27542
|
return nextProps !== this.props || nextState !== this.state;
|
|
27387
27543
|
}
|
|
27544
|
+
getPromptJSON() {
|
|
27545
|
+
var _this$hintRendererRef, _this$props$hint, _this$rendererRef$cur8;
|
|
27546
|
+
// If the hint isn't expanded, we can't get the prompt JSON from the rendered widgets.
|
|
27547
|
+
// We'll just pass in the hint content as a string instead.
|
|
27548
|
+
const hint = ((_this$hintRendererRef = this.hintRendererRef.current) == null ? void 0 : _this$hintRendererRef.getPromptJSON()) || {
|
|
27549
|
+
content: ((_this$props$hint = this.props.hint) == null ? void 0 : _this$props$hint.content) || "",
|
|
27550
|
+
widgets: {}
|
|
27551
|
+
};
|
|
27552
|
+
return getPromptJSON$i(this.props.title, (_this$rendererRef$cur8 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur8.getPromptJSON(), hint);
|
|
27553
|
+
}
|
|
27388
27554
|
render() {
|
|
27389
27555
|
const apiOptions = _.extend({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
27390
27556
|
// Api Rewriting to support correct onFocus/onBlur
|
|
@@ -27441,8 +27607,7 @@ class GradedGroup extends React.Component {
|
|
|
27441
27607
|
}, !!this.props.title && /*#__PURE__*/React.createElement("div", {
|
|
27442
27608
|
className: css(styles$j.title)
|
|
27443
27609
|
}, this.props.title), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
|
|
27444
|
-
|
|
27445
|
-
ref: "renderer",
|
|
27610
|
+
ref: this.rendererRef,
|
|
27446
27611
|
apiOptions: _extends({}, apiOptions, {
|
|
27447
27612
|
readOnly
|
|
27448
27613
|
}),
|
|
@@ -27484,8 +27649,7 @@ class GradedGroup extends React.Component {
|
|
|
27484
27649
|
});
|
|
27485
27650
|
}
|
|
27486
27651
|
}, this.context.strings.hideExplanation), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props.hint, {
|
|
27487
|
-
|
|
27488
|
-
ref: "hints-renderer",
|
|
27652
|
+
ref: this.hintRendererRef,
|
|
27489
27653
|
apiOptions: apiOptions,
|
|
27490
27654
|
linterContext: this.props.linterContext,
|
|
27491
27655
|
strings: this.context.strings
|
|
@@ -27587,6 +27751,16 @@ var GradedGroup$1 = {
|
|
|
27587
27751
|
isLintable: true
|
|
27588
27752
|
};
|
|
27589
27753
|
|
|
27754
|
+
const getPromptJSON$h = (renderProps, activeGroupJSON) => {
|
|
27755
|
+
return {
|
|
27756
|
+
type: "graded-group-set",
|
|
27757
|
+
options: {
|
|
27758
|
+
groupCount: renderProps.gradedGroups.length,
|
|
27759
|
+
currentGroup: activeGroupJSON
|
|
27760
|
+
}
|
|
27761
|
+
};
|
|
27762
|
+
};
|
|
27763
|
+
|
|
27590
27764
|
class Indicators extends React.Component {
|
|
27591
27765
|
constructor(...args) {
|
|
27592
27766
|
super(...args);
|
|
@@ -27674,6 +27848,10 @@ class GradedGroupSet extends React.Component {
|
|
|
27674
27848
|
nextProps.gradedGroups;
|
|
27675
27849
|
return nextProps !== this.props || nextState !== this.state;
|
|
27676
27850
|
}
|
|
27851
|
+
getPromptJSON() {
|
|
27852
|
+
const activeGroupPromptJSON = this._childGroup.getPromptJSON();
|
|
27853
|
+
return getPromptJSON$h(this.props, activeGroupPromptJSON);
|
|
27854
|
+
}
|
|
27677
27855
|
render() {
|
|
27678
27856
|
// When used in the context of TranslationEditor, render the
|
|
27679
27857
|
// GradedGroup widget one below another instead of using an indicator
|
|
@@ -27911,6 +28089,33 @@ const styles$h = StyleSheet.create({
|
|
|
27911
28089
|
}
|
|
27912
28090
|
});
|
|
27913
28091
|
|
|
28092
|
+
const getPromptJSON$g = (renderProps, userInput) => {
|
|
28093
|
+
const {
|
|
28094
|
+
type,
|
|
28095
|
+
coords
|
|
28096
|
+
} = userInput;
|
|
28097
|
+
const input = {
|
|
28098
|
+
type,
|
|
28099
|
+
coords
|
|
28100
|
+
};
|
|
28101
|
+
if (userInput.type === "logarithm" || userInput.type === "exponential") {
|
|
28102
|
+
input["asymptote"] = userInput.asymptote;
|
|
28103
|
+
}
|
|
28104
|
+
return {
|
|
28105
|
+
type: "grapher",
|
|
28106
|
+
options: {
|
|
28107
|
+
availableTypes: renderProps.availableTypes,
|
|
28108
|
+
range: renderProps.graph.range,
|
|
28109
|
+
labels: renderProps.graph.labels,
|
|
28110
|
+
tickStep: renderProps.graph.step,
|
|
28111
|
+
gridStep: renderProps.graph.gridStep,
|
|
28112
|
+
snapStep: renderProps.graph.snapStep,
|
|
28113
|
+
backgroundImageUrl: renderProps.graph.backgroundImage.url
|
|
28114
|
+
},
|
|
28115
|
+
userInput: input
|
|
28116
|
+
};
|
|
28117
|
+
};
|
|
28118
|
+
|
|
27914
28119
|
/* eslint-disable one-var */
|
|
27915
28120
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
27916
28121
|
const Plot$1 = Graphie.Plot;
|
|
@@ -28434,7 +28639,7 @@ function getCoefficientsByType(data) {
|
|
|
28434
28639
|
throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
|
|
28435
28640
|
}
|
|
28436
28641
|
}
|
|
28437
|
-
function
|
|
28642
|
+
function scoreGrapher(userInput, rubric) {
|
|
28438
28643
|
if (userInput.type !== rubric.correct.type) {
|
|
28439
28644
|
return {
|
|
28440
28645
|
type: "points",
|
|
@@ -28583,7 +28788,7 @@ class FunctionGrapher extends React.Component {
|
|
|
28583
28788
|
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
28584
28789
|
// Always enforce that this is a function
|
|
28585
28790
|
const isFunction = _.all(this._coords(), (otherCoord, j) => {
|
|
28586
|
-
return i === j || !otherCoord || !number.equal(coord[0], otherCoord[0]);
|
|
28791
|
+
return i === j || !otherCoord || !number$1.equal(coord[0], otherCoord[0]);
|
|
28587
28792
|
});
|
|
28588
28793
|
|
|
28589
28794
|
// Evaluate this criteria before per-point
|
|
@@ -28783,6 +28988,9 @@ class Grapher extends React.Component {
|
|
|
28783
28988
|
getUserInput() {
|
|
28784
28989
|
return Grapher.getUserInputFromProps(this.props);
|
|
28785
28990
|
}
|
|
28991
|
+
getPromptJSON() {
|
|
28992
|
+
return getPromptJSON$g(this.props, this.getUserInput());
|
|
28993
|
+
}
|
|
28786
28994
|
render() {
|
|
28787
28995
|
const type = this.props.plot.type;
|
|
28788
28996
|
const coords = this.props.plot.coords;
|
|
@@ -28868,7 +29076,20 @@ var Grapher$1 = {
|
|
|
28868
29076
|
widget: Grapher,
|
|
28869
29077
|
transform: propTransform$2,
|
|
28870
29078
|
staticTransform: staticTransform$4,
|
|
28871
|
-
|
|
29079
|
+
scorer: scoreGrapher
|
|
29080
|
+
};
|
|
29081
|
+
|
|
29082
|
+
const getPromptJSON$f = rendererJSON => {
|
|
29083
|
+
if (!rendererJSON) {
|
|
29084
|
+
return {
|
|
29085
|
+
type: "group",
|
|
29086
|
+
content: "",
|
|
29087
|
+
widgets: {}
|
|
29088
|
+
};
|
|
29089
|
+
}
|
|
29090
|
+
return _extends({}, rendererJSON, {
|
|
29091
|
+
type: "group"
|
|
29092
|
+
});
|
|
28872
29093
|
};
|
|
28873
29094
|
|
|
28874
29095
|
// exports has no 'transform'
|
|
@@ -28926,17 +29147,21 @@ class Group extends React.Component {
|
|
|
28926
29147
|
var _this$rendererRef8;
|
|
28927
29148
|
return (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.getUserInput();
|
|
28928
29149
|
}
|
|
29150
|
+
getPromptJSON() {
|
|
29151
|
+
var _this$rendererRef9;
|
|
29152
|
+
return getPromptJSON$f((_this$rendererRef9 = this.rendererRef) == null ? void 0 : _this$rendererRef9.getPromptJSON());
|
|
29153
|
+
}
|
|
28929
29154
|
// Mobile API:
|
|
28930
29155
|
getInputPaths() {
|
|
28931
|
-
var _this$rendererRef$get, _this$
|
|
28932
|
-
return (_this$rendererRef$get = (_this$
|
|
29156
|
+
var _this$rendererRef$get, _this$rendererRef10;
|
|
29157
|
+
return (_this$rendererRef$get = (_this$rendererRef10 = this.rendererRef) == null ? void 0 : _this$rendererRef10.getInputPaths()) != null ? _this$rendererRef$get : [];
|
|
28933
29158
|
}
|
|
28934
29159
|
focus() {
|
|
28935
|
-
var _this$rendererRef$foc, _this$
|
|
28936
|
-
return (_this$rendererRef$foc = (_this$
|
|
29160
|
+
var _this$rendererRef$foc, _this$rendererRef11;
|
|
29161
|
+
return (_this$rendererRef$foc = (_this$rendererRef11 = this.rendererRef) == null ? void 0 : _this$rendererRef11.focus()) != null ? _this$rendererRef$foc : false;
|
|
28937
29162
|
}
|
|
28938
29163
|
render() {
|
|
28939
|
-
var _this$
|
|
29164
|
+
var _this$rendererRef12;
|
|
28940
29165
|
const apiOptions = _extends({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
28941
29166
|
// Api Rewriting to support correct onFocus/onBlur
|
|
28942
29167
|
// events for the mobile API
|
|
@@ -28972,7 +29197,7 @@ class Group extends React.Component {
|
|
|
28972
29197
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
28973
29198
|
}
|
|
28974
29199
|
};
|
|
28975
|
-
const score = (_this$
|
|
29200
|
+
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
28976
29201
|
const isValid = score && score.type !== "invalid";
|
|
28977
29202
|
const isInvalid = score && score.type === "invalid";
|
|
28978
29203
|
|
|
@@ -29022,7 +29247,11 @@ var Group$1 = {
|
|
|
29022
29247
|
isLintable: true
|
|
29023
29248
|
};
|
|
29024
29249
|
|
|
29025
|
-
|
|
29250
|
+
const getPromptJSON$e = () => {
|
|
29251
|
+
return getUnsupportedPromptJSON("iframe");
|
|
29252
|
+
};
|
|
29253
|
+
|
|
29254
|
+
function scoreIframe(userInput) {
|
|
29026
29255
|
// The iframe can tell us whether it's correct or incorrect,
|
|
29027
29256
|
// and pass an optional message
|
|
29028
29257
|
if (userInput.status === "correct") {
|
|
@@ -29098,6 +29327,9 @@ class Iframe extends React.Component {
|
|
|
29098
29327
|
message: this.props.message
|
|
29099
29328
|
};
|
|
29100
29329
|
}
|
|
29330
|
+
getPromptJSON() {
|
|
29331
|
+
return getPromptJSON$e();
|
|
29332
|
+
}
|
|
29101
29333
|
render() {
|
|
29102
29334
|
const style = {
|
|
29103
29335
|
width: String(this.props.width),
|
|
@@ -29172,7 +29404,19 @@ var Iframe$1 = {
|
|
|
29172
29404
|
widget: Iframe,
|
|
29173
29405
|
// Let's not expose it to all content creators yet
|
|
29174
29406
|
hidden: true,
|
|
29175
|
-
|
|
29407
|
+
scorer: scoreIframe
|
|
29408
|
+
};
|
|
29409
|
+
|
|
29410
|
+
const getPromptJSON$d = renderProps => {
|
|
29411
|
+
return {
|
|
29412
|
+
type: "image",
|
|
29413
|
+
options: {
|
|
29414
|
+
altText: renderProps.alt,
|
|
29415
|
+
title: renderProps.title,
|
|
29416
|
+
caption: renderProps.caption,
|
|
29417
|
+
imageUrl: renderProps.backgroundImage.url
|
|
29418
|
+
}
|
|
29419
|
+
};
|
|
29176
29420
|
};
|
|
29177
29421
|
|
|
29178
29422
|
const defaultBoxSize = 400;
|
|
@@ -29197,6 +29441,9 @@ class ImageWidget extends React.Component {
|
|
|
29197
29441
|
return change.apply(this, args);
|
|
29198
29442
|
};
|
|
29199
29443
|
}
|
|
29444
|
+
getPromptJSON() {
|
|
29445
|
+
return getPromptJSON$d(this.props);
|
|
29446
|
+
}
|
|
29200
29447
|
render() {
|
|
29201
29448
|
let image;
|
|
29202
29449
|
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
@@ -29352,8 +29599,12 @@ var Image$1 = {
|
|
|
29352
29599
|
displayName: "Image",
|
|
29353
29600
|
widget: ImageWidget,
|
|
29354
29601
|
isLintable: true,
|
|
29355
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29356
|
-
|
|
29602
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
29603
|
+
scorer: () => scoreNoop()
|
|
29604
|
+
};
|
|
29605
|
+
|
|
29606
|
+
const getPromptJSON$c = () => {
|
|
29607
|
+
return getUnsupportedPromptJSON("interaction");
|
|
29357
29608
|
};
|
|
29358
29609
|
|
|
29359
29610
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -29526,6 +29777,9 @@ class Interaction extends React.Component {
|
|
|
29526
29777
|
});
|
|
29527
29778
|
}
|
|
29528
29779
|
}
|
|
29780
|
+
getPromptJSON() {
|
|
29781
|
+
return getPromptJSON$c();
|
|
29782
|
+
}
|
|
29529
29783
|
render() {
|
|
29530
29784
|
const range = this.props.graph.range;
|
|
29531
29785
|
let labels = this.props.graph.labels;
|
|
@@ -29945,8 +30199,8 @@ var Interactive = {
|
|
|
29945
30199
|
widget: Interaction,
|
|
29946
30200
|
transform: _.identity,
|
|
29947
30201
|
hidden: true,
|
|
29948
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29949
|
-
|
|
30202
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
30203
|
+
scorer: () => scoreNoop()
|
|
29950
30204
|
};
|
|
29951
30205
|
|
|
29952
30206
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -30249,6 +30503,139 @@ Graph.defaultProps = {
|
|
|
30249
30503
|
isMobile: false
|
|
30250
30504
|
};
|
|
30251
30505
|
|
|
30506
|
+
const getPromptJSON$b = (props, userInput) => {
|
|
30507
|
+
var _props$backgroundImag;
|
|
30508
|
+
return {
|
|
30509
|
+
type: "interactive-graph",
|
|
30510
|
+
options: {
|
|
30511
|
+
graph: getGraphOptionsForProps(props),
|
|
30512
|
+
backgroundImageUrl: (_props$backgroundImag = props.backgroundImage) == null ? void 0 : _props$backgroundImag.url,
|
|
30513
|
+
range: props.range,
|
|
30514
|
+
labels: props.labels
|
|
30515
|
+
},
|
|
30516
|
+
userInput: getUserInput(userInput)
|
|
30517
|
+
};
|
|
30518
|
+
};
|
|
30519
|
+
const getGraphOptionsForProps = props => {
|
|
30520
|
+
var _props$graph$startCoo, _props$graph$startCoo2;
|
|
30521
|
+
const type = props.graph.type;
|
|
30522
|
+
switch (type) {
|
|
30523
|
+
case "angle":
|
|
30524
|
+
return {
|
|
30525
|
+
type: props.graph.type,
|
|
30526
|
+
angleOffsetDegrees: props.graph.angleOffsetDeg,
|
|
30527
|
+
startCoords: props.graph.startCoords
|
|
30528
|
+
};
|
|
30529
|
+
case "circle":
|
|
30530
|
+
return {
|
|
30531
|
+
type: props.graph.type,
|
|
30532
|
+
startParams: {
|
|
30533
|
+
center: (_props$graph$startCoo = props.graph.startCoords) == null ? void 0 : _props$graph$startCoo.center,
|
|
30534
|
+
radius: (_props$graph$startCoo2 = props.graph.startCoords) == null ? void 0 : _props$graph$startCoo2.radius
|
|
30535
|
+
}
|
|
30536
|
+
};
|
|
30537
|
+
case "linear":
|
|
30538
|
+
return {
|
|
30539
|
+
type: props.graph.type,
|
|
30540
|
+
startCoords: props.graph.startCoords
|
|
30541
|
+
};
|
|
30542
|
+
case "linear-system":
|
|
30543
|
+
return {
|
|
30544
|
+
type: props.graph.type,
|
|
30545
|
+
startCoords: props.graph.startCoords
|
|
30546
|
+
};
|
|
30547
|
+
case "point":
|
|
30548
|
+
return {
|
|
30549
|
+
type: props.graph.type,
|
|
30550
|
+
numPoints: props.graph.numPoints,
|
|
30551
|
+
startCoords: props.graph.startCoords
|
|
30552
|
+
};
|
|
30553
|
+
case "polygon":
|
|
30554
|
+
return {
|
|
30555
|
+
type: props.graph.type,
|
|
30556
|
+
match: props.graph.match,
|
|
30557
|
+
numSides: props.graph.numSides,
|
|
30558
|
+
startCoords: props.graph.startCoords
|
|
30559
|
+
};
|
|
30560
|
+
case "quadratic":
|
|
30561
|
+
return {
|
|
30562
|
+
type: props.graph.type,
|
|
30563
|
+
startCoords: props.graph.startCoords
|
|
30564
|
+
};
|
|
30565
|
+
case "ray":
|
|
30566
|
+
return {
|
|
30567
|
+
type: props.graph.type,
|
|
30568
|
+
startCoords: props.graph.startCoords
|
|
30569
|
+
};
|
|
30570
|
+
case "segment":
|
|
30571
|
+
return {
|
|
30572
|
+
type: props.graph.type,
|
|
30573
|
+
numSegments: props.graph.numSegments,
|
|
30574
|
+
startCoords: props.graph.startCoords
|
|
30575
|
+
};
|
|
30576
|
+
case "sinusoid":
|
|
30577
|
+
return {
|
|
30578
|
+
type: props.graph.type,
|
|
30579
|
+
startCoords: props.graph.startCoords
|
|
30580
|
+
};
|
|
30581
|
+
case "none":
|
|
30582
|
+
return {};
|
|
30583
|
+
default:
|
|
30584
|
+
throw new UnreachableCaseError(type);
|
|
30585
|
+
}
|
|
30586
|
+
};
|
|
30587
|
+
const getUserInput = userInput => {
|
|
30588
|
+
const type = userInput.type;
|
|
30589
|
+
switch (type) {
|
|
30590
|
+
case "angle":
|
|
30591
|
+
return {
|
|
30592
|
+
coords: userInput.coords,
|
|
30593
|
+
angleOffsetDegrees: userInput.angleOffsetDeg
|
|
30594
|
+
};
|
|
30595
|
+
case "circle":
|
|
30596
|
+
return {
|
|
30597
|
+
center: userInput.center,
|
|
30598
|
+
radius: userInput.radius
|
|
30599
|
+
};
|
|
30600
|
+
case "linear":
|
|
30601
|
+
return {
|
|
30602
|
+
coords: userInput.coords
|
|
30603
|
+
};
|
|
30604
|
+
case "linear-system":
|
|
30605
|
+
return {
|
|
30606
|
+
coords: userInput.coords
|
|
30607
|
+
};
|
|
30608
|
+
case "point":
|
|
30609
|
+
return {
|
|
30610
|
+
coords: userInput.coords
|
|
30611
|
+
};
|
|
30612
|
+
case "polygon":
|
|
30613
|
+
return {
|
|
30614
|
+
coords: userInput.coords
|
|
30615
|
+
};
|
|
30616
|
+
case "quadratic":
|
|
30617
|
+
return {
|
|
30618
|
+
coords: userInput.coords
|
|
30619
|
+
};
|
|
30620
|
+
case "ray":
|
|
30621
|
+
return {
|
|
30622
|
+
coords: userInput.coords
|
|
30623
|
+
};
|
|
30624
|
+
case "segment":
|
|
30625
|
+
return {
|
|
30626
|
+
coords: userInput.coords
|
|
30627
|
+
};
|
|
30628
|
+
case "sinusoid":
|
|
30629
|
+
return {
|
|
30630
|
+
coords: userInput.coords
|
|
30631
|
+
};
|
|
30632
|
+
case "none":
|
|
30633
|
+
return {};
|
|
30634
|
+
default:
|
|
30635
|
+
throw new UnreachableCaseError(type);
|
|
30636
|
+
}
|
|
30637
|
+
};
|
|
30638
|
+
|
|
30252
30639
|
function pathBuilder() {
|
|
30253
30640
|
return new PathBuilder();
|
|
30254
30641
|
}
|
|
@@ -31313,7 +31700,7 @@ class NumberInput extends React.Component {
|
|
|
31313
31700
|
};
|
|
31314
31701
|
}
|
|
31315
31702
|
componentDidUpdate(prevProps) {
|
|
31316
|
-
if (!number.equal(this.getValue(), this.props.value)) {
|
|
31703
|
+
if (!number$1.equal(this.getValue(), this.props.value)) {
|
|
31317
31704
|
this._setValue(this.props.value, this.state.format);
|
|
31318
31705
|
}
|
|
31319
31706
|
}
|
|
@@ -31482,6 +31869,10 @@ const LegacyGrid = ({
|
|
|
31482
31869
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
31483
31870
|
* area.
|
|
31484
31871
|
*/
|
|
31872
|
+
/**
|
|
31873
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
31874
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
31875
|
+
*/
|
|
31485
31876
|
/**
|
|
31486
31877
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
31487
31878
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
@@ -31578,6 +31969,10 @@ const ItemExtras = [
|
|
|
31578
31969
|
// A background image applied to various widgets.
|
|
31579
31970
|
|
|
31580
31971
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
31972
|
+
|
|
31973
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
31974
|
+
|
|
31975
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
31581
31976
|
const lockedFigureColors = {
|
|
31582
31977
|
blue: "#3D7586",
|
|
31583
31978
|
green: "#447A53",
|
|
@@ -31644,16 +32039,60 @@ function isUnlimitedGraphState(state) {
|
|
|
31644
32039
|
* and the text outside of the $ blocks will be non-TeX text.
|
|
31645
32040
|
*/
|
|
31646
32041
|
function replaceOutsideTeX(mathString) {
|
|
31647
|
-
|
|
32042
|
+
// All the information we need is in the first section,
|
|
32043
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
32044
|
+
const firstSection = SimpleMarkdown.parse(mathString)[0];
|
|
32045
|
+
|
|
32046
|
+
// If it's blockMath, the outer level has the full math content.
|
|
32047
|
+
if (firstSection.type === "blockMath") {
|
|
32048
|
+
return firstSection.content;
|
|
32049
|
+
}
|
|
32050
|
+
|
|
32051
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
32052
|
+
// to look for individual math blocks.
|
|
32053
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31648
32054
|
let result = "";
|
|
31649
|
-
const
|
|
31650
|
-
|
|
31651
|
-
const part = splitString[i];
|
|
31652
|
-
result += currentlyTeX ? part : `\\text{${part}}`;
|
|
31653
|
-
currentlyTeX = !currentlyTeX;
|
|
32055
|
+
for (const piece of condensedNodes) {
|
|
32056
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
31654
32057
|
}
|
|
31655
32058
|
return result;
|
|
31656
32059
|
}
|
|
32060
|
+
// Helper function for replaceOutsideTeX()
|
|
32061
|
+
// Condense adjacent text nodes into a single text node
|
|
32062
|
+
function condenseTextNodes(nodes) {
|
|
32063
|
+
const result = [];
|
|
32064
|
+
if (!nodes) {
|
|
32065
|
+
return result;
|
|
32066
|
+
}
|
|
32067
|
+
let currentText = "";
|
|
32068
|
+
for (const node of nodes) {
|
|
32069
|
+
if (node.type === "math") {
|
|
32070
|
+
if (currentText) {
|
|
32071
|
+
result.push({
|
|
32072
|
+
type: "text",
|
|
32073
|
+
content: currentText
|
|
32074
|
+
});
|
|
32075
|
+
currentText = "";
|
|
32076
|
+
}
|
|
32077
|
+
result.push(node);
|
|
32078
|
+
} else {
|
|
32079
|
+
currentText += node.content;
|
|
32080
|
+
}
|
|
32081
|
+
}
|
|
32082
|
+
if (currentText) {
|
|
32083
|
+
result.push({
|
|
32084
|
+
type: "text",
|
|
32085
|
+
content: currentText
|
|
32086
|
+
});
|
|
32087
|
+
}
|
|
32088
|
+
return result;
|
|
32089
|
+
}
|
|
32090
|
+
|
|
32091
|
+
// Helper function for replaceOutsideTeX()
|
|
32092
|
+
function escapeSpecialChars(str) {
|
|
32093
|
+
// Escape $, \, {, and } characters
|
|
32094
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32095
|
+
}
|
|
31657
32096
|
|
|
31658
32097
|
function LockedLabel(props) {
|
|
31659
32098
|
const {
|
|
@@ -35915,7 +36354,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
35915
36354
|
|
|
35916
36355
|
const eq$1 = Util.eq;
|
|
35917
36356
|
const deepEq$2 = Util.deepEq;
|
|
35918
|
-
function
|
|
36357
|
+
function scoreInteractiveGraph(userInput, rubric) {
|
|
35919
36358
|
// None-type graphs are not graded
|
|
35920
36359
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35921
36360
|
return {
|
|
@@ -36024,7 +36463,7 @@ function interactiveGraphValidator(userInput, rubric) {
|
|
|
36024
36463
|
if (rubric.correct.match === "similar") {
|
|
36025
36464
|
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36026
36465
|
} else if (rubric.correct.match === "congruent") {
|
|
36027
|
-
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
36466
|
+
match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
|
|
36028
36467
|
} else if (rubric.correct.match === "approx") {
|
|
36029
36468
|
match = similar(guess, correct, 0.1);
|
|
36030
36469
|
} else {
|
|
@@ -36333,7 +36772,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
36333
36772
|
};
|
|
36334
36773
|
this.setTrashCanVisibility = opacity => {
|
|
36335
36774
|
const graphie = this.graphie;
|
|
36336
|
-
if (number.equal(opacity, 0)) {
|
|
36775
|
+
if (number$1.equal(opacity, 0)) {
|
|
36337
36776
|
if (this.trashCan) {
|
|
36338
36777
|
this.trashCan.remove();
|
|
36339
36778
|
this.trashCan = null;
|
|
@@ -37427,6 +37866,9 @@ class InteractiveGraph extends React.Component {
|
|
|
37427
37866
|
}
|
|
37428
37867
|
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
37429
37868
|
}
|
|
37869
|
+
getPromptJSON() {
|
|
37870
|
+
return getPromptJSON$b(this.props, this.getUserInput());
|
|
37871
|
+
}
|
|
37430
37872
|
render() {
|
|
37431
37873
|
var _this$props$apiOption;
|
|
37432
37874
|
// Mafs shim
|
|
@@ -37805,7 +38247,7 @@ var InteractiveGraph$1 = {
|
|
|
37805
38247
|
displayName: "Interactive graph (Assessments only)",
|
|
37806
38248
|
widget: InteractiveGraph,
|
|
37807
38249
|
staticTransform: staticTransform$3,
|
|
37808
|
-
|
|
38250
|
+
scorer: scoreInteractiveGraph
|
|
37809
38251
|
};
|
|
37810
38252
|
|
|
37811
38253
|
// TODO: can we use WB typography?
|
|
@@ -37817,6 +38259,32 @@ const bodyXsmallBold = {
|
|
|
37817
38259
|
lineHeight: "22px"
|
|
37818
38260
|
};
|
|
37819
38261
|
|
|
38262
|
+
const getPromptJSON$a = (renderProps, userInput) => {
|
|
38263
|
+
const propMarkers = renderProps.markers.map(marker => {
|
|
38264
|
+
return {
|
|
38265
|
+
label: marker.label
|
|
38266
|
+
};
|
|
38267
|
+
});
|
|
38268
|
+
const inputMarkers = userInput.markers.map(marker => {
|
|
38269
|
+
return {
|
|
38270
|
+
label: marker.label,
|
|
38271
|
+
selected: marker.selected
|
|
38272
|
+
};
|
|
38273
|
+
});
|
|
38274
|
+
return {
|
|
38275
|
+
type: "label-image",
|
|
38276
|
+
options: {
|
|
38277
|
+
choices: renderProps.choices,
|
|
38278
|
+
imageUrl: renderProps.imageUrl,
|
|
38279
|
+
imageAlt: renderProps.imageAlt,
|
|
38280
|
+
markers: propMarkers
|
|
38281
|
+
},
|
|
38282
|
+
userInput: {
|
|
38283
|
+
markers: inputMarkers
|
|
38284
|
+
}
|
|
38285
|
+
};
|
|
38286
|
+
};
|
|
38287
|
+
|
|
37820
38288
|
const AnswerChoices = props => {
|
|
37821
38289
|
const {
|
|
37822
38290
|
strings
|
|
@@ -37899,59 +38367,6 @@ const styles$d = StyleSheet.create({
|
|
|
37899
38367
|
}
|
|
37900
38368
|
});
|
|
37901
38369
|
|
|
37902
|
-
// Question state for marker as result of user selected answers.
|
|
37903
|
-
|
|
37904
|
-
function scoreMarker(marker) {
|
|
37905
|
-
const score = {
|
|
37906
|
-
hasAnswers: false,
|
|
37907
|
-
isCorrect: false
|
|
37908
|
-
};
|
|
37909
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
37910
|
-
score.hasAnswers = true;
|
|
37911
|
-
}
|
|
37912
|
-
if (marker.answers.length > 0) {
|
|
37913
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
37914
|
-
// All correct answers are selected by the user.
|
|
37915
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
37916
|
-
}
|
|
37917
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
37918
|
-
// Correct as no answers should be selected by the user.
|
|
37919
|
-
score.isCorrect = true;
|
|
37920
|
-
}
|
|
37921
|
-
return score;
|
|
37922
|
-
}
|
|
37923
|
-
|
|
37924
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
37925
|
-
function labelImageValidator(userInput, rubric) {
|
|
37926
|
-
let numAnswered = 0;
|
|
37927
|
-
let numCorrect = 0;
|
|
37928
|
-
for (const marker of userInput.markers) {
|
|
37929
|
-
const score = scoreMarker(marker);
|
|
37930
|
-
if (score.hasAnswers) {
|
|
37931
|
-
numAnswered++;
|
|
37932
|
-
}
|
|
37933
|
-
if (score.isCorrect) {
|
|
37934
|
-
numCorrect++;
|
|
37935
|
-
}
|
|
37936
|
-
}
|
|
37937
|
-
|
|
37938
|
-
// We expect all question markers to be answered before grading.
|
|
37939
|
-
if (numAnswered !== userInput.markers.length) {
|
|
37940
|
-
return {
|
|
37941
|
-
type: "invalid",
|
|
37942
|
-
message: null
|
|
37943
|
-
};
|
|
37944
|
-
}
|
|
37945
|
-
return {
|
|
37946
|
-
type: "points",
|
|
37947
|
-
// Markers with no expected answers are graded as correct if user
|
|
37948
|
-
// makes no answer selection.
|
|
37949
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
37950
|
-
total: 1,
|
|
37951
|
-
message: null
|
|
37952
|
-
};
|
|
37953
|
-
}
|
|
37954
|
-
|
|
37955
38370
|
const BringToFront = {
|
|
37956
38371
|
boxShadow: `0 8px 8px ${color.offBlack64}`,
|
|
37957
38372
|
zIndex: 1000
|
|
@@ -38205,6 +38620,59 @@ const styles$b = StyleSheet.create({
|
|
|
38205
38620
|
}
|
|
38206
38621
|
});
|
|
38207
38622
|
|
|
38623
|
+
// Question state for marker as result of user selected answers.
|
|
38624
|
+
|
|
38625
|
+
function scoreMarker(marker) {
|
|
38626
|
+
const score = {
|
|
38627
|
+
hasAnswers: false,
|
|
38628
|
+
isCorrect: false
|
|
38629
|
+
};
|
|
38630
|
+
if (marker.selected && marker.selected.length > 0) {
|
|
38631
|
+
score.hasAnswers = true;
|
|
38632
|
+
}
|
|
38633
|
+
if (marker.answers.length > 0) {
|
|
38634
|
+
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38635
|
+
// All correct answers are selected by the user.
|
|
38636
|
+
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38637
|
+
}
|
|
38638
|
+
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38639
|
+
// Correct as no answers should be selected by the user.
|
|
38640
|
+
score.isCorrect = true;
|
|
38641
|
+
}
|
|
38642
|
+
return score;
|
|
38643
|
+
}
|
|
38644
|
+
|
|
38645
|
+
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38646
|
+
function scoreLabelImage(userInput, rubric) {
|
|
38647
|
+
let numAnswered = 0;
|
|
38648
|
+
let numCorrect = 0;
|
|
38649
|
+
for (const marker of userInput.markers) {
|
|
38650
|
+
const score = scoreMarker(marker);
|
|
38651
|
+
if (score.hasAnswers) {
|
|
38652
|
+
numAnswered++;
|
|
38653
|
+
}
|
|
38654
|
+
if (score.isCorrect) {
|
|
38655
|
+
numCorrect++;
|
|
38656
|
+
}
|
|
38657
|
+
}
|
|
38658
|
+
|
|
38659
|
+
// We expect all question markers to be answered before grading.
|
|
38660
|
+
if (numAnswered !== userInput.markers.length) {
|
|
38661
|
+
return {
|
|
38662
|
+
type: "invalid",
|
|
38663
|
+
message: null
|
|
38664
|
+
};
|
|
38665
|
+
}
|
|
38666
|
+
return {
|
|
38667
|
+
type: "points",
|
|
38668
|
+
// Markers with no expected answers are graded as correct if user
|
|
38669
|
+
// makes no answer selection.
|
|
38670
|
+
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38671
|
+
total: 1,
|
|
38672
|
+
message: null
|
|
38673
|
+
};
|
|
38674
|
+
}
|
|
38675
|
+
|
|
38208
38676
|
/**
|
|
38209
38677
|
* Represents a direction vector.
|
|
38210
38678
|
* No diagonals allowed.
|
|
@@ -38413,6 +38881,9 @@ class LabelImage extends React.Component {
|
|
|
38413
38881
|
markers
|
|
38414
38882
|
};
|
|
38415
38883
|
}
|
|
38884
|
+
getPromptJSON() {
|
|
38885
|
+
return getPromptJSON$a(this.props, this.getUserInput());
|
|
38886
|
+
}
|
|
38416
38887
|
|
|
38417
38888
|
// TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
|
|
38418
38889
|
showRationalesForCurrentlySelectedChoices(rubric) {
|
|
@@ -38769,7 +39240,7 @@ var LabelImage$1 = {
|
|
|
38769
39240
|
widget: LabelImageWithDependencies,
|
|
38770
39241
|
accessible: true,
|
|
38771
39242
|
isLintable: true,
|
|
38772
|
-
|
|
39243
|
+
scorer: scoreLabelImage
|
|
38773
39244
|
};
|
|
38774
39245
|
|
|
38775
39246
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
@@ -39481,7 +39952,23 @@ const styles$9 = StyleSheet.create({
|
|
|
39481
39952
|
}
|
|
39482
39953
|
});
|
|
39483
39954
|
|
|
39484
|
-
|
|
39955
|
+
const getPromptJSON$9 = (renderProps, userInput) => {
|
|
39956
|
+
return {
|
|
39957
|
+
type: "matcher",
|
|
39958
|
+
options: {
|
|
39959
|
+
labels: renderProps.labels,
|
|
39960
|
+
left: renderProps.left,
|
|
39961
|
+
right: renderProps.right,
|
|
39962
|
+
orderMatters: renderProps.orderMatters
|
|
39963
|
+
},
|
|
39964
|
+
userInput: {
|
|
39965
|
+
left: userInput.left,
|
|
39966
|
+
right: userInput.right
|
|
39967
|
+
}
|
|
39968
|
+
};
|
|
39969
|
+
};
|
|
39970
|
+
|
|
39971
|
+
function scoreMatcher(state, rubric) {
|
|
39485
39972
|
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
39486
39973
|
return {
|
|
39487
39974
|
type: "points",
|
|
@@ -39554,6 +40041,9 @@ class Matcher extends React.Component {
|
|
|
39554
40041
|
this.refs.right.moveOptionToIndex(option, index);
|
|
39555
40042
|
};
|
|
39556
40043
|
}
|
|
40044
|
+
getPromptJSON() {
|
|
40045
|
+
return getPromptJSON$9(this.props, this.getUserInput());
|
|
40046
|
+
}
|
|
39557
40047
|
render() {
|
|
39558
40048
|
// To minimize layout shift, we display a spinner until our math
|
|
39559
40049
|
// renderer is ready to render the math inside the matcher. To
|
|
@@ -39692,10 +40182,23 @@ var Matcher$1 = {
|
|
|
39692
40182
|
displayName: "Matcher (two column)",
|
|
39693
40183
|
widget: Matcher,
|
|
39694
40184
|
isLintable: true,
|
|
39695
|
-
|
|
40185
|
+
scorer: scoreMatcher
|
|
39696
40186
|
};
|
|
39697
40187
|
|
|
39698
|
-
|
|
40188
|
+
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
40189
|
+
return {
|
|
40190
|
+
type: "matrix",
|
|
40191
|
+
options: {
|
|
40192
|
+
height: renderProps.matrixBoardSize[0],
|
|
40193
|
+
width: renderProps.matrixBoardSize[1]
|
|
40194
|
+
},
|
|
40195
|
+
userInput: {
|
|
40196
|
+
answerRows: userInput.answers
|
|
40197
|
+
}
|
|
40198
|
+
};
|
|
40199
|
+
};
|
|
40200
|
+
|
|
40201
|
+
function scoreMatrix(userInput, rubric, strings) {
|
|
39699
40202
|
const solution = rubric.answers;
|
|
39700
40203
|
const supplied = userInput.answers;
|
|
39701
40204
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -39965,6 +40468,9 @@ class Matrix extends React.Component {
|
|
|
39965
40468
|
answers: this.props.answers
|
|
39966
40469
|
};
|
|
39967
40470
|
}
|
|
40471
|
+
getPromptJSON() {
|
|
40472
|
+
return getPromptJSON$8(this.props, this.getUserInput());
|
|
40473
|
+
}
|
|
39968
40474
|
render() {
|
|
39969
40475
|
// Set the input sizes through JS so we can control the size of the
|
|
39970
40476
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40158,7 +40664,11 @@ var Matrix$1 = {
|
|
|
40158
40664
|
transform: propTransform$1,
|
|
40159
40665
|
staticTransform: staticTransform$2,
|
|
40160
40666
|
isLintable: true,
|
|
40161
|
-
|
|
40667
|
+
scorer: scoreMatrix
|
|
40668
|
+
};
|
|
40669
|
+
|
|
40670
|
+
const getPromptJSON$7 = () => {
|
|
40671
|
+
return getUnsupportedPromptJSON("measurer");
|
|
40162
40672
|
};
|
|
40163
40673
|
|
|
40164
40674
|
const defaultImage = {
|
|
@@ -40225,6 +40735,9 @@ class Measurer extends React.Component {
|
|
|
40225
40735
|
});
|
|
40226
40736
|
}
|
|
40227
40737
|
}
|
|
40738
|
+
getPromptJSON() {
|
|
40739
|
+
return getPromptJSON$7();
|
|
40740
|
+
}
|
|
40228
40741
|
render() {
|
|
40229
40742
|
const image = _.extend({}, defaultImage, this.props.image);
|
|
40230
40743
|
|
|
@@ -40284,8 +40797,8 @@ var Measurer$1 = {
|
|
|
40284
40797
|
minor: 0
|
|
40285
40798
|
},
|
|
40286
40799
|
propUpgrades: propUpgrades,
|
|
40287
|
-
// TODO: things that aren't interactive shouldn't need
|
|
40288
|
-
|
|
40800
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
40801
|
+
scorer: () => scoreNoop(1)
|
|
40289
40802
|
};
|
|
40290
40803
|
|
|
40291
40804
|
/**
|
|
@@ -41174,16 +41687,31 @@ var Molecule$1 = {
|
|
|
41174
41687
|
displayName: "Molecule renderer",
|
|
41175
41688
|
hidden: true,
|
|
41176
41689
|
widget: MoleculeWidget,
|
|
41177
|
-
|
|
41690
|
+
scorer: () => scoreNoop()
|
|
41691
|
+
};
|
|
41692
|
+
|
|
41693
|
+
const getPromptJSON$6 = (renderProps, userInput) => {
|
|
41694
|
+
return {
|
|
41695
|
+
type: "number-line",
|
|
41696
|
+
options: {
|
|
41697
|
+
range: renderProps.range,
|
|
41698
|
+
numDivisions: renderProps.numDivisions,
|
|
41699
|
+
snapDivisions: renderProps.snapDivisions
|
|
41700
|
+
},
|
|
41701
|
+
userInput: {
|
|
41702
|
+
numLinePosition: userInput.numLinePosition,
|
|
41703
|
+
numDivisions: userInput.numDivisions
|
|
41704
|
+
}
|
|
41705
|
+
};
|
|
41178
41706
|
};
|
|
41179
41707
|
|
|
41180
|
-
function
|
|
41708
|
+
function scoreNumberLine(state, rubric) {
|
|
41181
41709
|
const range = rubric.range;
|
|
41182
41710
|
const divisionRange = state.divisionRange;
|
|
41183
41711
|
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
41184
41712
|
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
41185
41713
|
const correctRel = rubric.correctRel || "eq";
|
|
41186
|
-
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41714
|
+
const correctPos = number$1.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41187
41715
|
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
41188
41716
|
|
|
41189
41717
|
// TODO: I don't think isTickCrtl is a thing anymore
|
|
@@ -41312,7 +41840,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
41312
41840
|
const x = range[0] + i * tickStep;
|
|
41313
41841
|
fractions.push(x);
|
|
41314
41842
|
}
|
|
41315
|
-
const getDenom = x => number.toFraction(x)[1];
|
|
41843
|
+
const getDenom = x => number$1.toFraction(x)[1];
|
|
41316
41844
|
const denoms = _.map(fractions, getDenom);
|
|
41317
41845
|
base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
41318
41846
|
} else {
|
|
@@ -41375,7 +41903,7 @@ class NumberLine extends React.Component {
|
|
|
41375
41903
|
let initialX = this.props.numLinePosition;
|
|
41376
41904
|
const divisionRange = this.props.divisionRange;
|
|
41377
41905
|
initialX = initialX == null ? range[0] : initialX;
|
|
41378
|
-
return range[0] < range[1] && number.sign(initialX - range[0]) >= 0 && number.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
41906
|
+
return range[0] < range[1] && number$1.sign(initialX - range[0]) >= 0 && number$1.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
41379
41907
|
};
|
|
41380
41908
|
this.onNumDivisionsChange = (numDivisions, cb) => {
|
|
41381
41909
|
const divRange = this.props.divisionRange.slice();
|
|
@@ -41478,7 +42006,7 @@ class NumberLine extends React.Component {
|
|
|
41478
42006
|
const right = props.range[1];
|
|
41479
42007
|
const snapX = props.tickStep / props.snapDivisions;
|
|
41480
42008
|
let x = bound(numLinePosition, left, right);
|
|
41481
|
-
x = left + number.roundTo(x - left, snapX);
|
|
42009
|
+
x = left + number$1.roundTo(x - left, snapX);
|
|
41482
42010
|
assert$1(_.isFinite(x));
|
|
41483
42011
|
return x;
|
|
41484
42012
|
};
|
|
@@ -41645,6 +42173,9 @@ class NumberLine extends React.Component {
|
|
|
41645
42173
|
divisionRange: this.props.divisionRange
|
|
41646
42174
|
};
|
|
41647
42175
|
}
|
|
42176
|
+
getPromptJSON() {
|
|
42177
|
+
return getPromptJSON$6(this.props, this.getUserInput());
|
|
42178
|
+
}
|
|
41648
42179
|
render() {
|
|
41649
42180
|
const {
|
|
41650
42181
|
strings
|
|
@@ -41764,10 +42295,22 @@ var NumberLine$1 = {
|
|
|
41764
42295
|
widget: NumberLine,
|
|
41765
42296
|
transform: numberLineTransform,
|
|
41766
42297
|
staticTransform: staticTransform$1,
|
|
41767
|
-
|
|
42298
|
+
scorer: scoreNumberLine
|
|
41768
42299
|
};
|
|
41769
42300
|
|
|
41770
|
-
|
|
42301
|
+
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
42302
|
+
return {
|
|
42303
|
+
type: "orderer",
|
|
42304
|
+
options: {
|
|
42305
|
+
options: renderProps.options.map(option => option.content)
|
|
42306
|
+
},
|
|
42307
|
+
userInput: {
|
|
42308
|
+
values: userInput.current
|
|
42309
|
+
}
|
|
42310
|
+
};
|
|
42311
|
+
};
|
|
42312
|
+
|
|
42313
|
+
function scoreOrderer(userInput, rubric) {
|
|
41771
42314
|
if (userInput.current.length === 0) {
|
|
41772
42315
|
return {
|
|
41773
42316
|
type: "invalid",
|
|
@@ -42211,6 +42754,9 @@ class Orderer extends React.Component {
|
|
|
42211
42754
|
})
|
|
42212
42755
|
};
|
|
42213
42756
|
}
|
|
42757
|
+
getPromptJSON() {
|
|
42758
|
+
return getPromptJSON$5(this.props, this.getUserInput());
|
|
42759
|
+
}
|
|
42214
42760
|
render() {
|
|
42215
42761
|
// This is the card we are currently dragging
|
|
42216
42762
|
const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
|
|
@@ -42329,7 +42875,7 @@ var Orderer$1 = {
|
|
|
42329
42875
|
hidden: true,
|
|
42330
42876
|
widget: Orderer,
|
|
42331
42877
|
isLintable: true,
|
|
42332
|
-
|
|
42878
|
+
scorer: scoreOrderer
|
|
42333
42879
|
};
|
|
42334
42880
|
|
|
42335
42881
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -42378,8 +42924,12 @@ var PassageRefTarget$1 = {
|
|
|
42378
42924
|
minor: 0
|
|
42379
42925
|
},
|
|
42380
42926
|
isLintable: true,
|
|
42381
|
-
// TODO: things that aren't interactive shouldn't need
|
|
42382
|
-
|
|
42927
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
42928
|
+
scorer: () => scoreNoop()
|
|
42929
|
+
};
|
|
42930
|
+
|
|
42931
|
+
const getPromptJSON$4 = () => {
|
|
42932
|
+
return getUnsupportedPromptJSON("phet-simulation");
|
|
42383
42933
|
};
|
|
42384
42934
|
|
|
42385
42935
|
/**
|
|
@@ -42437,6 +42987,9 @@ class PhetSimulation extends React.Component {
|
|
|
42437
42987
|
await this.updateSimState(this.props.url);
|
|
42438
42988
|
}
|
|
42439
42989
|
}
|
|
42990
|
+
getPromptJSON() {
|
|
42991
|
+
return getPromptJSON$4();
|
|
42992
|
+
}
|
|
42440
42993
|
async updateSimState(urlString) {
|
|
42441
42994
|
const url = makeSafeUrl(urlString, this.locale);
|
|
42442
42995
|
if (url === null) {
|
|
@@ -42585,10 +43138,14 @@ var PhetSimulation$1 = {
|
|
|
42585
43138
|
isLintable: true
|
|
42586
43139
|
};
|
|
42587
43140
|
|
|
43141
|
+
const getPromptJSON$3 = () => {
|
|
43142
|
+
return getUnsupportedPromptJSON("plotter");
|
|
43143
|
+
};
|
|
43144
|
+
|
|
42588
43145
|
const {
|
|
42589
43146
|
deepEq: deepEq$1
|
|
42590
43147
|
} = Util;
|
|
42591
|
-
function
|
|
43148
|
+
function scorePlotter(userInput, rubric) {
|
|
42592
43149
|
if (deepEq$1(userInput, rubric.starting)) {
|
|
42593
43150
|
return {
|
|
42594
43151
|
type: "invalid",
|
|
@@ -43361,6 +43918,9 @@ class Plotter extends React.Component {
|
|
|
43361
43918
|
getUserInput() {
|
|
43362
43919
|
return this.state.values;
|
|
43363
43920
|
}
|
|
43921
|
+
getPromptJSON() {
|
|
43922
|
+
return getPromptJSON$3();
|
|
43923
|
+
}
|
|
43364
43924
|
render() {
|
|
43365
43925
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
43366
43926
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -43401,7 +43961,11 @@ var Plotter$1 = {
|
|
|
43401
43961
|
hidden: true,
|
|
43402
43962
|
widget: Plotter,
|
|
43403
43963
|
staticTransform: staticTransform,
|
|
43404
|
-
|
|
43964
|
+
scorer: scorePlotter
|
|
43965
|
+
};
|
|
43966
|
+
|
|
43967
|
+
const getPromptJSON$2 = () => {
|
|
43968
|
+
return getUnsupportedPromptJSON("python-program");
|
|
43405
43969
|
};
|
|
43406
43970
|
|
|
43407
43971
|
/**
|
|
@@ -43423,6 +43987,9 @@ class PythonProgram extends React.Component {
|
|
|
43423
43987
|
getDOMNodeForPath(path) {
|
|
43424
43988
|
return null;
|
|
43425
43989
|
}
|
|
43990
|
+
getPromptJSON() {
|
|
43991
|
+
return getPromptJSON$2();
|
|
43992
|
+
}
|
|
43426
43993
|
render() {
|
|
43427
43994
|
const url = getUrlFromProgramID(this.props.programID);
|
|
43428
43995
|
const iframeStyle = {
|
|
@@ -43460,7 +44027,17 @@ var PythonProgram$1 = {
|
|
|
43460
44027
|
widget: PythonProgram
|
|
43461
44028
|
};
|
|
43462
44029
|
|
|
43463
|
-
|
|
44030
|
+
const getPromptJSON$1 = userInput => {
|
|
44031
|
+
return {
|
|
44032
|
+
type: "sorter",
|
|
44033
|
+
userInput: {
|
|
44034
|
+
values: userInput.options,
|
|
44035
|
+
changed: userInput.changed
|
|
44036
|
+
}
|
|
44037
|
+
};
|
|
44038
|
+
};
|
|
44039
|
+
|
|
44040
|
+
function scoreSorter(userInput, rubric) {
|
|
43464
44041
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43465
44042
|
// prevents the "Check" button from becoming active. We want the user
|
|
43466
44043
|
// to make a change before trying to move forward. This makes an
|
|
@@ -43532,6 +44109,9 @@ class Sorter extends React.Component {
|
|
|
43532
44109
|
changed: this.state.changed
|
|
43533
44110
|
};
|
|
43534
44111
|
}
|
|
44112
|
+
getPromptJSON() {
|
|
44113
|
+
return getPromptJSON$1(this.getUserInput());
|
|
44114
|
+
}
|
|
43535
44115
|
render() {
|
|
43536
44116
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
43537
44117
|
const {
|
|
@@ -43566,20 +44146,26 @@ var Sorter$1 = {
|
|
|
43566
44146
|
displayName: "Sorter",
|
|
43567
44147
|
widget: Sorter,
|
|
43568
44148
|
isLintable: true,
|
|
43569
|
-
|
|
44149
|
+
scorer: scoreSorter
|
|
43570
44150
|
};
|
|
43571
44151
|
|
|
43572
|
-
|
|
43573
|
-
|
|
43574
|
-
|
|
43575
|
-
|
|
43576
|
-
|
|
43577
|
-
|
|
43578
|
-
|
|
43579
|
-
|
|
43580
|
-
|
|
43581
|
-
|
|
43582
|
-
|
|
44152
|
+
/**
|
|
44153
|
+
* Filters the given table (modelled as a 2D array) to remove any rows that are
|
|
44154
|
+
* completely empty.
|
|
44155
|
+
*
|
|
44156
|
+
* @returns A new table with only non-empty rows.
|
|
44157
|
+
*/
|
|
44158
|
+
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44159
|
+
return table.filter(function (row) {
|
|
44160
|
+
// Return only rows that are non-empty.
|
|
44161
|
+
return row.some(cell => cell);
|
|
44162
|
+
});
|
|
44163
|
+
};
|
|
44164
|
+
|
|
44165
|
+
function validateTable(userInput) {
|
|
44166
|
+
const supplied = filterNonEmpty(userInput);
|
|
44167
|
+
const hasEmptyCell = supplied.some(function (row) {
|
|
44168
|
+
return row.some(function (cell) {
|
|
43583
44169
|
return cell === "";
|
|
43584
44170
|
});
|
|
43585
44171
|
});
|
|
@@ -43589,6 +44175,16 @@ function tableValidator(state, rubric, strings) {
|
|
|
43589
44175
|
message: null
|
|
43590
44176
|
};
|
|
43591
44177
|
}
|
|
44178
|
+
return null;
|
|
44179
|
+
}
|
|
44180
|
+
|
|
44181
|
+
function scoreTable(userInput, rubric, strings) {
|
|
44182
|
+
const validationResult = validateTable(userInput);
|
|
44183
|
+
if (validationResult != null) {
|
|
44184
|
+
return validationResult;
|
|
44185
|
+
}
|
|
44186
|
+
const supplied = filterNonEmpty(userInput);
|
|
44187
|
+
const solution = filterNonEmpty(rubric.answers);
|
|
43592
44188
|
if (supplied.length !== solution.length) {
|
|
43593
44189
|
return {
|
|
43594
44190
|
type: "points",
|
|
@@ -43599,17 +44195,16 @@ function tableValidator(state, rubric, strings) {
|
|
|
43599
44195
|
}
|
|
43600
44196
|
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
43601
44197
|
let message = null;
|
|
43602
|
-
const allCorrect =
|
|
44198
|
+
const allCorrect = solution.every(function (rowSolution) {
|
|
43603
44199
|
for (let i = 0; i < supplied.length; i++) {
|
|
43604
44200
|
const rowSupplied = supplied[i];
|
|
43605
|
-
const correct =
|
|
44201
|
+
const correct = rowSupplied.every(function (cellSupplied, i) {
|
|
43606
44202
|
const cellSolution = rowSolution[i];
|
|
43607
44203
|
const validator = createValidator(cellSolution, {
|
|
43608
44204
|
simplify: true
|
|
43609
44205
|
}, strings);
|
|
43610
44206
|
const result = validator(cellSupplied);
|
|
43611
44207
|
if (result.message) {
|
|
43612
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
43613
44208
|
message = result.message;
|
|
43614
44209
|
}
|
|
43615
44210
|
return result.correct;
|
|
@@ -43625,7 +44220,7 @@ function tableValidator(state, rubric, strings) {
|
|
|
43625
44220
|
type: "points",
|
|
43626
44221
|
earned: allCorrect ? 1 : 0,
|
|
43627
44222
|
total: 1,
|
|
43628
|
-
message
|
|
44223
|
+
message
|
|
43629
44224
|
};
|
|
43630
44225
|
}
|
|
43631
44226
|
|
|
@@ -43846,7 +44441,11 @@ var Table$1 = {
|
|
|
43846
44441
|
transform: propTransform,
|
|
43847
44442
|
hidden: true,
|
|
43848
44443
|
isLintable: true,
|
|
43849
|
-
|
|
44444
|
+
scorer: scoreTable
|
|
44445
|
+
};
|
|
44446
|
+
|
|
44447
|
+
const getPromptJSON = () => {
|
|
44448
|
+
return getUnsupportedPromptJSON("video");
|
|
43850
44449
|
};
|
|
43851
44450
|
|
|
43852
44451
|
/**
|
|
@@ -43942,6 +44541,9 @@ class Video extends React.Component {
|
|
|
43942
44541
|
return change.apply(this, args);
|
|
43943
44542
|
};
|
|
43944
44543
|
}
|
|
44544
|
+
getPromptJSON() {
|
|
44545
|
+
return getPromptJSON();
|
|
44546
|
+
}
|
|
43945
44547
|
render() {
|
|
43946
44548
|
const {
|
|
43947
44549
|
InitialRequestUrl
|
|
@@ -44002,8 +44604,8 @@ var Video$1 = {
|
|
|
44002
44604
|
defaultAlignment: "block",
|
|
44003
44605
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44004
44606
|
widget: Video,
|
|
44005
|
-
// TODO: things that aren't interactive shouldn't need
|
|
44006
|
-
|
|
44607
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44608
|
+
scorer: () => scoreNoop()
|
|
44007
44609
|
};
|
|
44008
44610
|
|
|
44009
44611
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -44022,7 +44624,7 @@ const init = function init() {
|
|
|
44022
44624
|
|
|
44023
44625
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44024
44626
|
const libName = "@khanacademy/perseus";
|
|
44025
|
-
const libVersion = "41.
|
|
44627
|
+
const libVersion = "41.4.0";
|
|
44026
44628
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44027
44629
|
|
|
44028
44630
|
const apiVersion = {
|
|
@@ -44759,6 +45361,9 @@ class ServerItemRenderer extends React.Component {
|
|
|
44759
45361
|
getNumHints() {
|
|
44760
45362
|
return this.props.item.hints.length;
|
|
44761
45363
|
}
|
|
45364
|
+
getPromptJSON() {
|
|
45365
|
+
return this.questionRenderer.getPromptJSON();
|
|
45366
|
+
}
|
|
44762
45367
|
|
|
44763
45368
|
/**
|
|
44764
45369
|
* Grades the item.
|
|
@@ -45406,7 +46011,7 @@ function treeToItem(node) {
|
|
|
45406
46011
|
};
|
|
45407
46012
|
}
|
|
45408
46013
|
|
|
45409
|
-
var isObject = function isObject(obj) {
|
|
46014
|
+
var isObject$1 = function isObject(obj) {
|
|
45410
46015
|
return obj === Object(obj);
|
|
45411
46016
|
};
|
|
45412
46017
|
var merge = function merge() {
|
|
@@ -45422,7 +46027,7 @@ var merge = function merge() {
|
|
|
45422
46027
|
return obj;
|
|
45423
46028
|
};
|
|
45424
46029
|
var clone = function clone(obj) {
|
|
45425
|
-
if (!isObject(obj)) {
|
|
46030
|
+
if (!isObject$1(obj)) {
|
|
45426
46031
|
return obj;
|
|
45427
46032
|
}
|
|
45428
46033
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45438,7 +46043,7 @@ var clone = function clone(obj) {
|
|
|
45438
46043
|
var dispatch = function dispatch(x) {
|
|
45439
46044
|
if (Array.isArray(x)) {
|
|
45440
46045
|
return arr;
|
|
45441
|
-
} else if (isObject(x)) {
|
|
46046
|
+
} else if (isObject$1(x)) {
|
|
45442
46047
|
return obj;
|
|
45443
46048
|
} else if (typeof x === "string") {
|
|
45444
46049
|
return str;
|
|
@@ -47359,6 +47964,690 @@ function buildTestData(testObject) {
|
|
|
47359
47964
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47360
47965
|
}
|
|
47361
47966
|
|
|
47967
|
+
function success(value) {
|
|
47968
|
+
return {
|
|
47969
|
+
type: "success",
|
|
47970
|
+
value
|
|
47971
|
+
};
|
|
47972
|
+
}
|
|
47973
|
+
function failure(detail) {
|
|
47974
|
+
return {
|
|
47975
|
+
type: "failure",
|
|
47976
|
+
detail
|
|
47977
|
+
};
|
|
47978
|
+
}
|
|
47979
|
+
function isFailure(result) {
|
|
47980
|
+
return result.type === "failure";
|
|
47981
|
+
}
|
|
47982
|
+
function isSuccess(result) {
|
|
47983
|
+
return result.type === "success";
|
|
47984
|
+
}
|
|
47985
|
+
|
|
47986
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
47987
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
47988
|
+
function all(results, combineFailureDetails = a => a) {
|
|
47989
|
+
const values = [];
|
|
47990
|
+
const failureDetails = [];
|
|
47991
|
+
for (const result of results) {
|
|
47992
|
+
if (result.type === "success") {
|
|
47993
|
+
values.push(result.value);
|
|
47994
|
+
} else {
|
|
47995
|
+
failureDetails.push(result.detail);
|
|
47996
|
+
}
|
|
47997
|
+
}
|
|
47998
|
+
if (failureDetails.length > 0) {
|
|
47999
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
48000
|
+
}
|
|
48001
|
+
return success(values);
|
|
48002
|
+
}
|
|
48003
|
+
|
|
48004
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
48005
|
+
|
|
48006
|
+
function array(elementParser) {
|
|
48007
|
+
return (rawValue, ctx) => {
|
|
48008
|
+
if (!Array.isArray(rawValue)) {
|
|
48009
|
+
return ctx.failure("array", rawValue);
|
|
48010
|
+
}
|
|
48011
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
48012
|
+
return all(elementResults, concat);
|
|
48013
|
+
};
|
|
48014
|
+
}
|
|
48015
|
+
function concat(a, b) {
|
|
48016
|
+
return [...a, ...b];
|
|
48017
|
+
}
|
|
48018
|
+
|
|
48019
|
+
function boolean(rawValue, ctx) {
|
|
48020
|
+
if (typeof rawValue === "boolean") {
|
|
48021
|
+
return ctx.success(rawValue);
|
|
48022
|
+
}
|
|
48023
|
+
return ctx.failure("boolean", rawValue);
|
|
48024
|
+
}
|
|
48025
|
+
|
|
48026
|
+
function constant(acceptedValue) {
|
|
48027
|
+
return (rawValue, ctx) => {
|
|
48028
|
+
if (rawValue !== acceptedValue) {
|
|
48029
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
48030
|
+
}
|
|
48031
|
+
return ctx.success(acceptedValue);
|
|
48032
|
+
};
|
|
48033
|
+
}
|
|
48034
|
+
|
|
48035
|
+
function enumeration(...acceptedValues) {
|
|
48036
|
+
return (rawValue, ctx) => {
|
|
48037
|
+
if (typeof rawValue === "string") {
|
|
48038
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
48039
|
+
if (index > -1) {
|
|
48040
|
+
return ctx.success(acceptedValues[index]);
|
|
48041
|
+
}
|
|
48042
|
+
}
|
|
48043
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
48044
|
+
return ctx.failure(expected, rawValue);
|
|
48045
|
+
};
|
|
48046
|
+
}
|
|
48047
|
+
|
|
48048
|
+
function isObject(x) {
|
|
48049
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
48050
|
+
}
|
|
48051
|
+
|
|
48052
|
+
function nullable(parseValue) {
|
|
48053
|
+
return (rawValue, ctx) => {
|
|
48054
|
+
if (rawValue === null) {
|
|
48055
|
+
return ctx.success(rawValue);
|
|
48056
|
+
}
|
|
48057
|
+
return parseValue(rawValue, ctx);
|
|
48058
|
+
};
|
|
48059
|
+
}
|
|
48060
|
+
|
|
48061
|
+
const number = (rawValue, ctx) => {
|
|
48062
|
+
if (typeof rawValue === "number") {
|
|
48063
|
+
return ctx.success(rawValue);
|
|
48064
|
+
}
|
|
48065
|
+
return ctx.failure("number", rawValue);
|
|
48066
|
+
};
|
|
48067
|
+
|
|
48068
|
+
function object(schema) {
|
|
48069
|
+
return (rawValue, ctx) => {
|
|
48070
|
+
if (!isObject(rawValue)) {
|
|
48071
|
+
return ctx.failure("object", rawValue);
|
|
48072
|
+
}
|
|
48073
|
+
const ret = _extends({}, rawValue);
|
|
48074
|
+
const mismatches = [];
|
|
48075
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
48076
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
48077
|
+
if (isSuccess(result)) {
|
|
48078
|
+
ret[prop] = result.value;
|
|
48079
|
+
} else {
|
|
48080
|
+
mismatches.push(...result.detail);
|
|
48081
|
+
}
|
|
48082
|
+
}
|
|
48083
|
+
if (mismatches.length > 0) {
|
|
48084
|
+
return failure(mismatches);
|
|
48085
|
+
}
|
|
48086
|
+
return ctx.success(ret);
|
|
48087
|
+
};
|
|
48088
|
+
}
|
|
48089
|
+
|
|
48090
|
+
function optional(parseValue) {
|
|
48091
|
+
return (rawValue, ctx) => {
|
|
48092
|
+
if (rawValue === undefined) {
|
|
48093
|
+
return ctx.success(rawValue);
|
|
48094
|
+
}
|
|
48095
|
+
return parseValue(rawValue, ctx);
|
|
48096
|
+
};
|
|
48097
|
+
}
|
|
48098
|
+
|
|
48099
|
+
function pair(parseA, parseB) {
|
|
48100
|
+
return (rawValue, ctx) => {
|
|
48101
|
+
if (!Array.isArray(rawValue)) {
|
|
48102
|
+
return ctx.failure("array", rawValue);
|
|
48103
|
+
}
|
|
48104
|
+
if (rawValue.length !== 2) {
|
|
48105
|
+
return ctx.failure("array of length 2", rawValue);
|
|
48106
|
+
}
|
|
48107
|
+
const [rawA, rawB] = rawValue;
|
|
48108
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
48109
|
+
if (isFailure(resultA)) {
|
|
48110
|
+
return resultA;
|
|
48111
|
+
}
|
|
48112
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
48113
|
+
if (isFailure(resultB)) {
|
|
48114
|
+
return resultB;
|
|
48115
|
+
}
|
|
48116
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
48117
|
+
};
|
|
48118
|
+
}
|
|
48119
|
+
|
|
48120
|
+
function record(parseKey, parseValue) {
|
|
48121
|
+
return (rawValue, ctx) => {
|
|
48122
|
+
if (!isObject(rawValue)) {
|
|
48123
|
+
return ctx.failure("object", rawValue);
|
|
48124
|
+
}
|
|
48125
|
+
const result = {};
|
|
48126
|
+
const mismatches = [];
|
|
48127
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
48128
|
+
const entryCtx = ctx.forSubtree(key);
|
|
48129
|
+
const keyResult = parseKey(key, entryCtx);
|
|
48130
|
+
if (isFailure(keyResult)) {
|
|
48131
|
+
mismatches.push(...keyResult.detail);
|
|
48132
|
+
}
|
|
48133
|
+
const valueResult = parseValue(value, entryCtx);
|
|
48134
|
+
if (isFailure(valueResult)) {
|
|
48135
|
+
mismatches.push(...valueResult.detail);
|
|
48136
|
+
}
|
|
48137
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
48138
|
+
result[keyResult.value] = valueResult.value;
|
|
48139
|
+
}
|
|
48140
|
+
}
|
|
48141
|
+
if (mismatches.length > 0) {
|
|
48142
|
+
return failure(mismatches);
|
|
48143
|
+
}
|
|
48144
|
+
return ctx.success(result);
|
|
48145
|
+
};
|
|
48146
|
+
}
|
|
48147
|
+
|
|
48148
|
+
const string = (rawValue, ctx) => {
|
|
48149
|
+
if (typeof rawValue === "string") {
|
|
48150
|
+
return ctx.success(rawValue);
|
|
48151
|
+
}
|
|
48152
|
+
return ctx.failure("string", rawValue);
|
|
48153
|
+
};
|
|
48154
|
+
|
|
48155
|
+
function trio(parseA, parseB, parseC) {
|
|
48156
|
+
return (rawValue, ctx) => {
|
|
48157
|
+
if (!Array.isArray(rawValue)) {
|
|
48158
|
+
return ctx.failure("array", rawValue);
|
|
48159
|
+
}
|
|
48160
|
+
if (rawValue.length !== 3) {
|
|
48161
|
+
return ctx.failure("array of length 3", rawValue);
|
|
48162
|
+
}
|
|
48163
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
48164
|
+
if (isFailure(resultA)) {
|
|
48165
|
+
return resultA;
|
|
48166
|
+
}
|
|
48167
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
48168
|
+
if (isFailure(resultB)) {
|
|
48169
|
+
return resultB;
|
|
48170
|
+
}
|
|
48171
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
48172
|
+
if (isFailure(resultC)) {
|
|
48173
|
+
return resultC;
|
|
48174
|
+
}
|
|
48175
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
48176
|
+
};
|
|
48177
|
+
}
|
|
48178
|
+
|
|
48179
|
+
function union(parseBranch) {
|
|
48180
|
+
return new UnionBuilder(parseBranch);
|
|
48181
|
+
}
|
|
48182
|
+
class UnionBuilder {
|
|
48183
|
+
constructor(parser) {
|
|
48184
|
+
this.parser = parser;
|
|
48185
|
+
}
|
|
48186
|
+
or(newBranch) {
|
|
48187
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
48188
|
+
}
|
|
48189
|
+
}
|
|
48190
|
+
function either(parseA, parseB) {
|
|
48191
|
+
return (rawValue, ctx) => {
|
|
48192
|
+
const resultA = parseA(rawValue, ctx);
|
|
48193
|
+
if (isSuccess(resultA)) {
|
|
48194
|
+
return resultA;
|
|
48195
|
+
}
|
|
48196
|
+
return parseB(rawValue, ctx);
|
|
48197
|
+
};
|
|
48198
|
+
}
|
|
48199
|
+
|
|
48200
|
+
function parseWidget(parseType, parseOptions) {
|
|
48201
|
+
return object({
|
|
48202
|
+
type: parseType,
|
|
48203
|
+
static: optional(boolean),
|
|
48204
|
+
graded: optional(boolean),
|
|
48205
|
+
alignment: optional(string),
|
|
48206
|
+
options: parseOptions,
|
|
48207
|
+
key: optional(number),
|
|
48208
|
+
version: object({
|
|
48209
|
+
major: number,
|
|
48210
|
+
minor: number
|
|
48211
|
+
})
|
|
48212
|
+
});
|
|
48213
|
+
}
|
|
48214
|
+
|
|
48215
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
48216
|
+
items: array(string),
|
|
48217
|
+
categories: array(string),
|
|
48218
|
+
randomizeItems: boolean,
|
|
48219
|
+
static: boolean,
|
|
48220
|
+
values: array(number),
|
|
48221
|
+
highlightLint: optional(boolean),
|
|
48222
|
+
linterContext: optional(object({
|
|
48223
|
+
contentType: string,
|
|
48224
|
+
paths: array(string),
|
|
48225
|
+
stack: array(string)
|
|
48226
|
+
}))
|
|
48227
|
+
}));
|
|
48228
|
+
|
|
48229
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
48230
|
+
programID: string,
|
|
48231
|
+
programType: any,
|
|
48232
|
+
settings: array(object({
|
|
48233
|
+
name: string,
|
|
48234
|
+
value: string
|
|
48235
|
+
})),
|
|
48236
|
+
showEditor: boolean,
|
|
48237
|
+
showButtons: boolean,
|
|
48238
|
+
width: number,
|
|
48239
|
+
height: number,
|
|
48240
|
+
static: boolean
|
|
48241
|
+
}));
|
|
48242
|
+
|
|
48243
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48244
|
+
togglePrompt: string,
|
|
48245
|
+
definition: string,
|
|
48246
|
+
static: boolean
|
|
48247
|
+
}));
|
|
48248
|
+
|
|
48249
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48250
|
+
placeholder: string,
|
|
48251
|
+
static: boolean,
|
|
48252
|
+
choices: array(object({
|
|
48253
|
+
content: string,
|
|
48254
|
+
correct: boolean
|
|
48255
|
+
}))
|
|
48256
|
+
}));
|
|
48257
|
+
|
|
48258
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
48259
|
+
showPrompt: string,
|
|
48260
|
+
hidePrompt: string,
|
|
48261
|
+
explanation: string,
|
|
48262
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
48263
|
+
// referenced before it is defined. There is an import cycle between
|
|
48264
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
48265
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48266
|
+
static: boolean
|
|
48267
|
+
}));
|
|
48268
|
+
|
|
48269
|
+
const parseAnswerForm = object({
|
|
48270
|
+
value: string,
|
|
48271
|
+
form: boolean,
|
|
48272
|
+
simplify: boolean,
|
|
48273
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48274
|
+
key: optional(string)
|
|
48275
|
+
});
|
|
48276
|
+
const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
48277
|
+
answerForms: array(parseAnswerForm),
|
|
48278
|
+
functions: array(string),
|
|
48279
|
+
times: boolean,
|
|
48280
|
+
visibleLabel: optional(string),
|
|
48281
|
+
ariaLabel: optional(string),
|
|
48282
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48283
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48284
|
+
}));
|
|
48285
|
+
|
|
48286
|
+
const parsePerseusImageBackground = object({
|
|
48287
|
+
url: optional(nullable(string)),
|
|
48288
|
+
width: optional(number),
|
|
48289
|
+
height: optional(number),
|
|
48290
|
+
top: optional(number),
|
|
48291
|
+
left: optional(number),
|
|
48292
|
+
bottom: optional(number),
|
|
48293
|
+
// TODO(benchristel): convert scale to a number
|
|
48294
|
+
scale: optional(union(number).or(string).parser)
|
|
48295
|
+
});
|
|
48296
|
+
|
|
48297
|
+
// Used to represent 2-D points and ranges
|
|
48298
|
+
const pairOfNumbers = pair(number, number);
|
|
48299
|
+
const parsePerseusGraphTypeAngle = object({
|
|
48300
|
+
type: constant("angle"),
|
|
48301
|
+
showAngles: optional(boolean),
|
|
48302
|
+
allowReflexAngles: optional(boolean),
|
|
48303
|
+
angleOffsetDeg: optional(number),
|
|
48304
|
+
snapDegrees: optional(number),
|
|
48305
|
+
match: optional(constant("congruent")),
|
|
48306
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48307
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
48308
|
+
});
|
|
48309
|
+
const parsePerseusGraphTypeCircle = object({
|
|
48310
|
+
type: constant("circle"),
|
|
48311
|
+
center: optional(pairOfNumbers),
|
|
48312
|
+
radius: optional(number),
|
|
48313
|
+
startCoords: optional(object({
|
|
48314
|
+
center: pairOfNumbers,
|
|
48315
|
+
radius: number
|
|
48316
|
+
})),
|
|
48317
|
+
// TODO: remove coord? it's legacy.
|
|
48318
|
+
coord: optional(pairOfNumbers)
|
|
48319
|
+
});
|
|
48320
|
+
const parsePerseusGraphTypeLinear = object({
|
|
48321
|
+
type: constant("linear"),
|
|
48322
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48323
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48324
|
+
// TODO: remove coord? it's legacy.
|
|
48325
|
+
coord: optional(pairOfNumbers)
|
|
48326
|
+
});
|
|
48327
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
48328
|
+
type: constant("linear-system"),
|
|
48329
|
+
// TODO(benchristel): default coords to empty array?
|
|
48330
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48331
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48332
|
+
// TODO: remove coord? it's legacy.
|
|
48333
|
+
coord: optional(pairOfNumbers)
|
|
48334
|
+
});
|
|
48335
|
+
const parsePerseusGraphTypeNone = object({
|
|
48336
|
+
type: constant("none")
|
|
48337
|
+
});
|
|
48338
|
+
const parsePerseusGraphTypePoint = object({
|
|
48339
|
+
type: constant("point"),
|
|
48340
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
48341
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48342
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48343
|
+
// TODO: remove coord? it's legacy.
|
|
48344
|
+
coord: optional(pairOfNumbers)
|
|
48345
|
+
});
|
|
48346
|
+
const parsePerseusGraphTypePolygon = object({
|
|
48347
|
+
type: constant("polygon"),
|
|
48348
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
48349
|
+
showAngles: optional(boolean),
|
|
48350
|
+
showSides: optional(boolean),
|
|
48351
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
48352
|
+
match: optional(enumeration("similar", "congruent", "approx")),
|
|
48353
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48354
|
+
// TODO: remove coord? it's legacy.
|
|
48355
|
+
coord: optional(pairOfNumbers)
|
|
48356
|
+
});
|
|
48357
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
48358
|
+
type: constant("quadratic"),
|
|
48359
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
48360
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48361
|
+
// TODO: remove coord? it's legacy.
|
|
48362
|
+
coord: optional(pairOfNumbers)
|
|
48363
|
+
});
|
|
48364
|
+
const parsePerseusGraphTypeRay = object({
|
|
48365
|
+
type: constant("ray"),
|
|
48366
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48367
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48368
|
+
// TODO: remove coord? it's legacy.
|
|
48369
|
+
coord: optional(pairOfNumbers)
|
|
48370
|
+
});
|
|
48371
|
+
const parsePerseusGraphTypeSegment = object({
|
|
48372
|
+
type: constant("segment"),
|
|
48373
|
+
// TODO(benchristel): default numSegments?
|
|
48374
|
+
numSegments: optional(number),
|
|
48375
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48376
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48377
|
+
// TODO: remove coord? it's legacy.
|
|
48378
|
+
coord: optional(pairOfNumbers)
|
|
48379
|
+
});
|
|
48380
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
48381
|
+
type: constant("sinusoid"),
|
|
48382
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48383
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48384
|
+
// TODO: remove coord? it's legacy.
|
|
48385
|
+
coord: optional(pairOfNumbers)
|
|
48386
|
+
});
|
|
48387
|
+
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;
|
|
48388
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48389
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48390
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48391
|
+
const parseLockedLabelType = object({
|
|
48392
|
+
type: constant("label"),
|
|
48393
|
+
coord: pairOfNumbers,
|
|
48394
|
+
text: string,
|
|
48395
|
+
color: parseLockedFigureColor,
|
|
48396
|
+
size: enumeration("small", "medium", "large")
|
|
48397
|
+
});
|
|
48398
|
+
const parseLockedPointType = object({
|
|
48399
|
+
type: constant("point"),
|
|
48400
|
+
coord: pairOfNumbers,
|
|
48401
|
+
color: parseLockedFigureColor,
|
|
48402
|
+
filled: boolean,
|
|
48403
|
+
// TODO(benchristel): default labels to empty array?
|
|
48404
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48405
|
+
ariaLabel: optional(string)
|
|
48406
|
+
});
|
|
48407
|
+
const parseLockedLineType = object({
|
|
48408
|
+
type: constant("line"),
|
|
48409
|
+
kind: enumeration("line", "ray", "segment"),
|
|
48410
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48411
|
+
color: parseLockedFigureColor,
|
|
48412
|
+
lineStyle: parseLockedLineStyle,
|
|
48413
|
+
showPoint1: boolean,
|
|
48414
|
+
showPoint2: boolean,
|
|
48415
|
+
// TODO(benchristel): default labels to empty array?
|
|
48416
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48417
|
+
ariaLabel: optional(string)
|
|
48418
|
+
});
|
|
48419
|
+
const parseLockedVectorType = object({
|
|
48420
|
+
type: constant("vector"),
|
|
48421
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
48422
|
+
color: parseLockedFigureColor,
|
|
48423
|
+
// TODO(benchristel): default labels to empty array?
|
|
48424
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48425
|
+
ariaLabel: optional(string)
|
|
48426
|
+
});
|
|
48427
|
+
const parseLockedEllipseType = object({
|
|
48428
|
+
type: constant("ellipse"),
|
|
48429
|
+
center: pairOfNumbers,
|
|
48430
|
+
radius: pairOfNumbers,
|
|
48431
|
+
angle: number,
|
|
48432
|
+
color: parseLockedFigureColor,
|
|
48433
|
+
fillStyle: parseLockedFigureFillType,
|
|
48434
|
+
strokeStyle: parseLockedLineStyle,
|
|
48435
|
+
// TODO(benchristel): default labels to empty array?
|
|
48436
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48437
|
+
ariaLabel: optional(string)
|
|
48438
|
+
});
|
|
48439
|
+
const parseLockedPolygonType = object({
|
|
48440
|
+
type: constant("polygon"),
|
|
48441
|
+
points: array(pairOfNumbers),
|
|
48442
|
+
color: parseLockedFigureColor,
|
|
48443
|
+
showVertices: boolean,
|
|
48444
|
+
fillStyle: parseLockedFigureFillType,
|
|
48445
|
+
strokeStyle: parseLockedLineStyle,
|
|
48446
|
+
// TODO(benchristel): default labels to empty array?
|
|
48447
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48448
|
+
ariaLabel: optional(string)
|
|
48449
|
+
});
|
|
48450
|
+
const parseLockedFunctionType = object({
|
|
48451
|
+
type: constant("function"),
|
|
48452
|
+
color: parseLockedFigureColor,
|
|
48453
|
+
strokeStyle: parseLockedLineStyle,
|
|
48454
|
+
equation: string,
|
|
48455
|
+
directionalAxis: enumeration("x", "y"),
|
|
48456
|
+
domain: optional(pairOfNumbers),
|
|
48457
|
+
// TODO(benchristel): default labels to empty array?
|
|
48458
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48459
|
+
ariaLabel: optional(string)
|
|
48460
|
+
});
|
|
48461
|
+
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
48462
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48463
|
+
step: pairOfNumbers,
|
|
48464
|
+
gridStep: pairOfNumbers,
|
|
48465
|
+
snapStep: pairOfNumbers,
|
|
48466
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
48467
|
+
markings: enumeration("graph", "grid", "none"),
|
|
48468
|
+
labels: array(string),
|
|
48469
|
+
showProtractor: boolean,
|
|
48470
|
+
showRuler: optional(boolean),
|
|
48471
|
+
showTooltips: optional(boolean),
|
|
48472
|
+
rulerLabel: optional(string),
|
|
48473
|
+
rulerTicks: optional(number),
|
|
48474
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
48475
|
+
graph: parsePerseusGraphType,
|
|
48476
|
+
correct: parsePerseusGraphType,
|
|
48477
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
48478
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
48479
|
+
fullGraphLabel: optional(string),
|
|
48480
|
+
fullGraphAriaDescription: optional(string)
|
|
48481
|
+
}));
|
|
48482
|
+
|
|
48483
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
48484
|
+
if (!isObject(rawValue)) {
|
|
48485
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
48486
|
+
}
|
|
48487
|
+
const widgetsMap = {};
|
|
48488
|
+
for (const key of Object.keys(rawValue)) {
|
|
48489
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
48490
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
48491
|
+
// against the widget type.
|
|
48492
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
48493
|
+
if (isFailure(entryResult)) {
|
|
48494
|
+
return entryResult;
|
|
48495
|
+
}
|
|
48496
|
+
}
|
|
48497
|
+
return ctx.success(widgetsMap);
|
|
48498
|
+
};
|
|
48499
|
+
const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
48500
|
+
const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
|
|
48501
|
+
if (isFailure(keyComponentsResult)) {
|
|
48502
|
+
return keyComponentsResult;
|
|
48503
|
+
}
|
|
48504
|
+
const [type, id] = keyComponentsResult.value;
|
|
48505
|
+
function parseAndAssign(key, parse) {
|
|
48506
|
+
const widgetResult = parse(widget, ctx);
|
|
48507
|
+
if (isFailure(widgetResult)) {
|
|
48508
|
+
return widgetResult;
|
|
48509
|
+
}
|
|
48510
|
+
widgetMap[key] = widgetResult.value;
|
|
48511
|
+
return ctx.success(undefined);
|
|
48512
|
+
}
|
|
48513
|
+
switch (type) {
|
|
48514
|
+
case "categorizer":
|
|
48515
|
+
return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
|
|
48516
|
+
case "cs-program":
|
|
48517
|
+
return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
|
|
48518
|
+
case "definition":
|
|
48519
|
+
return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
|
|
48520
|
+
case "dropdown":
|
|
48521
|
+
return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
|
|
48522
|
+
case "explanation":
|
|
48523
|
+
return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
|
|
48524
|
+
case "expression":
|
|
48525
|
+
return parseAndAssign(`expression ${id}`, parseExpressionWidget);
|
|
48526
|
+
case "grapher":
|
|
48527
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48528
|
+
return parseAndAssign(`grapher ${id}`, any);
|
|
48529
|
+
case "group":
|
|
48530
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48531
|
+
return parseAndAssign(`group ${id}`, any);
|
|
48532
|
+
case "graded-group":
|
|
48533
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48534
|
+
return parseAndAssign(`graded-group ${id}`, any);
|
|
48535
|
+
case "graded-group-set":
|
|
48536
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48537
|
+
return parseAndAssign(`graded-group-set ${id}`, any);
|
|
48538
|
+
case "iframe":
|
|
48539
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48540
|
+
return parseAndAssign(`iframe ${id}`, any);
|
|
48541
|
+
case "image":
|
|
48542
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48543
|
+
return parseAndAssign(`image ${id}`, any);
|
|
48544
|
+
case "input-number":
|
|
48545
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48546
|
+
return parseAndAssign(`input-number ${id}`, any);
|
|
48547
|
+
case "interaction":
|
|
48548
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48549
|
+
return parseAndAssign(`interaction ${id}`, any);
|
|
48550
|
+
case "interactive-graph":
|
|
48551
|
+
return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
|
|
48552
|
+
case "label-image":
|
|
48553
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48554
|
+
return parseAndAssign(`label-image ${id}`, any);
|
|
48555
|
+
case "matcher":
|
|
48556
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48557
|
+
return parseAndAssign(`matcher ${id}`, any);
|
|
48558
|
+
case "matrix":
|
|
48559
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48560
|
+
return parseAndAssign(`matrix ${id}`, any);
|
|
48561
|
+
case "measurer":
|
|
48562
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48563
|
+
return parseAndAssign(`measurer ${id}`, any);
|
|
48564
|
+
case "molecule-renderer":
|
|
48565
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48566
|
+
return parseAndAssign(`molecule-renderer ${id}`, any);
|
|
48567
|
+
case "number-line":
|
|
48568
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48569
|
+
return parseAndAssign(`number-line ${id}`, any);
|
|
48570
|
+
case "numeric-input":
|
|
48571
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48572
|
+
return parseAndAssign(`numeric-input ${id}`, any);
|
|
48573
|
+
case "orderer":
|
|
48574
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48575
|
+
return parseAndAssign(`orderer ${id}`, any);
|
|
48576
|
+
case "passage":
|
|
48577
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48578
|
+
return parseAndAssign(`passage ${id}`, any);
|
|
48579
|
+
case "passage-ref":
|
|
48580
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48581
|
+
return parseAndAssign(`passage-ref ${id}`, any);
|
|
48582
|
+
case "passage-ref-target":
|
|
48583
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48584
|
+
return parseAndAssign(`passage-ref-target ${id}`, any);
|
|
48585
|
+
case "phet-simulation":
|
|
48586
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48587
|
+
return parseAndAssign(`phet-simulation ${id}`, any);
|
|
48588
|
+
case "plotter":
|
|
48589
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48590
|
+
return parseAndAssign(`plotter ${id}`, any);
|
|
48591
|
+
case "python-program":
|
|
48592
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48593
|
+
return parseAndAssign(`python-program ${id}`, any);
|
|
48594
|
+
case "radio":
|
|
48595
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48596
|
+
return parseAndAssign(`radio ${id}`, any);
|
|
48597
|
+
case "sorter":
|
|
48598
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48599
|
+
return parseAndAssign(`sorter ${id}`, any);
|
|
48600
|
+
case "table":
|
|
48601
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48602
|
+
return parseAndAssign(`table ${id}`, any);
|
|
48603
|
+
case "video":
|
|
48604
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48605
|
+
return parseAndAssign(`video ${id}`, any);
|
|
48606
|
+
default:
|
|
48607
|
+
return ctx.failure("a valid widget type", type);
|
|
48608
|
+
}
|
|
48609
|
+
};
|
|
48610
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48611
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48612
|
+
return ctx.failure("numeric string", rawValue);
|
|
48613
|
+
}
|
|
48614
|
+
return ctx.success(+rawValue);
|
|
48615
|
+
};
|
|
48616
|
+
const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
48617
|
+
|
|
48618
|
+
const parseHint = object({
|
|
48619
|
+
replace: optional(boolean),
|
|
48620
|
+
content: string,
|
|
48621
|
+
widgets: parseWidgetsMap,
|
|
48622
|
+
metadata: optional(array(string)),
|
|
48623
|
+
images: record(string, object({
|
|
48624
|
+
width: number,
|
|
48625
|
+
height: number
|
|
48626
|
+
}))
|
|
48627
|
+
});
|
|
48628
|
+
|
|
48629
|
+
const parsePerseusRenderer = object({
|
|
48630
|
+
content: string,
|
|
48631
|
+
widgets: parseWidgetsMap,
|
|
48632
|
+
metadata: optional(array(string)),
|
|
48633
|
+
images: record(string, object({
|
|
48634
|
+
width: number,
|
|
48635
|
+
height: number
|
|
48636
|
+
}))
|
|
48637
|
+
});
|
|
48638
|
+
|
|
48639
|
+
object({
|
|
48640
|
+
question: parsePerseusRenderer,
|
|
48641
|
+
hints: array(parseHint),
|
|
48642
|
+
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
48643
|
+
itemDataVersion: object({
|
|
48644
|
+
major: number,
|
|
48645
|
+
minor: number
|
|
48646
|
+
}),
|
|
48647
|
+
// Deprecated field
|
|
48648
|
+
answer: any
|
|
48649
|
+
});
|
|
48650
|
+
|
|
47362
48651
|
/**
|
|
47363
48652
|
* Helper to parse PerseusItem JSON
|
|
47364
48653
|
* Why not just use JSON.parse? We want:
|