@khanacademy/perseus 41.1.1 → 41.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-widgets.d.ts +1 -1
- package/dist/article-renderer.d.ts +2 -2
- package/dist/basic-widgets.d.ts +1 -1
- package/dist/es/index.js +1663 -397
- package/dist/es/index.js.map +1 -1
- package/dist/extra-widgets.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1583 -316
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -8
- package/dist/perseus-api.d.ts +3 -3
- package/dist/perseus-types.d.ts +7 -2
- package/dist/prompt-types.d.ts +33 -0
- package/dist/renderer.d.ts +8 -2
- package/dist/server-item-renderer.d.ts +6 -4
- package/dist/types.d.ts +4 -2
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +15 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/util/{parse-perseus-json.d.ts → parse-perseus-json/index.d.ts} +7 -1
- package/dist/util/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse.d.ts +3 -0
- package/dist/util/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/util/parse-perseus-json/result.d.ts +16 -0
- package/dist/validation.types.d.ts +0 -3
- package/dist/widget-ai-utils/categorizer/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/cs-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/definition/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/dropdown/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/explanation/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/expression/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/graded-group/prompt-utils.d.ts +7 -0
- package/dist/widget-ai-utils/graded-group-set/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/grapher/prompt-utils.d.ts +18 -0
- package/dist/widget-ai-utils/group/prompt-utils.d.ts +5 -0
- package/dist/widget-ai-utils/iframe/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/image/prompt-utils.d.ts +12 -0
- package/dist/widget-ai-utils/input-number/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/interaction/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/interactive-graph/prompt-utils.d.ts +94 -0
- package/dist/widget-ai-utils/label-image/prompt-utils.d.ts +24 -0
- package/dist/widget-ai-utils/matcher/prompt-utils.d.ts +17 -0
- package/dist/widget-ai-utils/matrix/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/measurer/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/number-line/prompt-utils.d.ts +16 -0
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/orderer/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/passage/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/passage-ref/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/phet-simulation/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/plotter/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/python-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/radio/prompt-utils.d.ts +15 -0
- package/dist/widget-ai-utils/sorter/prompt-utils.d.ts +9 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +11 -0
- package/dist/widget-ai-utils/video/prompt-utils.d.ts +2 -0
- package/dist/widgets/__shared__/{noop-validator.d.ts → score-noop.d.ts} +4 -4
- package/dist/widgets/categorizer/categorizer.d.ts +10 -19
- package/dist/widgets/categorizer/{categorizer-validator.d.ts → score-categorizer.d.ts} +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +8 -19
- package/dist/widgets/cs-program/{cs-program-validator.d.ts → score-cs-program.d.ts} +2 -1
- package/dist/widgets/definition/definition.d.ts +8 -19
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +4 -19
- package/dist/widgets/dropdown/dropdown.d.ts +10 -19
- package/dist/widgets/dropdown/score-dropdown.d.ts +4 -0
- package/dist/widgets/explanation/explanation.d.ts +9 -19
- package/dist/widgets/expression/expression.d.ts +26 -290
- package/dist/widgets/expression/{expression-validator.d.ts → score-expression.d.ts} +2 -2
- package/dist/widgets/graded-group/graded-group.d.ts +13 -21
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +9 -20
- package/dist/widgets/grapher/grapher.d.ts +9 -19
- package/dist/widgets/grapher/score-grapher.d.ts +4 -0
- package/dist/widgets/group/group.d.ts +7 -19
- package/dist/widgets/iframe/iframe.d.ts +7 -19
- package/dist/widgets/iframe/{iframe-validator.d.ts → score-iframe.d.ts} +1 -1
- package/dist/widgets/image/image.d.ts +9 -19
- package/dist/widgets/input-number/input-number.d.ts +11 -19
- package/dist/widgets/input-number/{input-number-validator.d.ts → score-input-number.d.ts} +2 -2
- package/dist/widgets/interaction/interaction.d.ts +7 -19
- package/dist/widgets/interactive-graph.d.ts +8 -19
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +4 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +6 -0
- package/dist/widgets/label-image/label-image.d.ts +10 -86
- package/dist/widgets/label-image/{label-image-validator.d.ts → score-label-image.d.ts} +2 -2
- package/dist/widgets/matcher/matcher.d.ts +7 -19
- package/dist/widgets/matcher/score-matcher.d.ts +4 -0
- package/dist/widgets/matrix/matrix.d.ts +10 -19
- package/dist/widgets/matrix/{matrix-validator.d.ts → score-matrix.d.ts} +2 -2
- package/dist/widgets/measurer/measurer.d.ts +13 -19
- package/dist/widgets/molecule/molecule.d.ts +4 -19
- package/dist/widgets/number-line/number-line.d.ts +8 -19
- package/dist/widgets/number-line/score-number-line.d.ts +4 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +22 -19
- package/dist/widgets/numeric-input/{numeric-input-validator.d.ts → score-numeric-input.d.ts} +2 -2
- package/dist/widgets/orderer/orderer.d.ts +8 -19
- package/dist/widgets/orderer/{orderer-validator.d.ts → score-orderer.d.ts} +1 -1
- package/dist/widgets/passage/passage.d.ts +8 -19
- package/dist/widgets/passage-ref/passage-ref.d.ts +12 -19
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +11 -19
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +5 -19
- package/dist/widgets/plotter/plotter.d.ts +8 -19
- package/dist/widgets/plotter/score-plotter.d.ts +4 -0
- package/dist/widgets/python-program/python-program.d.ts +4 -19
- package/dist/widgets/radio/radio-component.d.ts +3 -1
- package/dist/widgets/radio/radio.d.ts +18 -19
- package/dist/widgets/radio/{radio-validator.d.ts → score-radio.d.ts} +2 -2
- package/dist/widgets/sorter/score-sorter.d.ts +4 -0
- package/dist/widgets/sorter/sorter.d.ts +7 -19
- package/dist/widgets/table/{table-validator.d.ts → score-table.d.ts} +2 -2
- package/dist/widgets/table/table.d.ts +8 -19
- package/dist/widgets/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +4 -4
- package/dist/widgets/dropdown/dropdown-validator.d.ts +0 -4
- package/dist/widgets/grapher/grapher-validator.d.ts +0 -4
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +0 -4
- package/dist/widgets/number-line/number-line-validator.d.ts +0 -4
- package/dist/widgets/plotter/plotter-validator.d.ts +0 -4
- package/dist/widgets/sorter/sorter-validator.d.ts +0 -4
package/dist/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) {
|
|
@@ -2099,32 +2136,6 @@ const KhanAnswerTypes = {
|
|
|
2099
2136
|
}
|
|
2100
2137
|
};
|
|
2101
2138
|
|
|
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
2139
|
/* Content creators input a list of answers which are matched from top to
|
|
2129
2140
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2130
2141
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2143,7 +2154,7 @@ const getDecimalSeparator = locale => {
|
|
|
2143
2154
|
* show the user an error. TODO(joel) - what error?
|
|
2144
2155
|
* - Otherwise, pass through the resulting points and message.
|
|
2145
2156
|
*/
|
|
2146
|
-
function
|
|
2157
|
+
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2147
2158
|
const options = _.clone(rubric);
|
|
2148
2159
|
_.extend(options, {
|
|
2149
2160
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2393,6 +2404,9 @@ class Expression extends React.Component {
|
|
|
2393
2404
|
getUserInput() {
|
|
2394
2405
|
return Expression.getUserInputFromProps(this.props);
|
|
2395
2406
|
}
|
|
2407
|
+
getPromptJSON() {
|
|
2408
|
+
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2409
|
+
}
|
|
2396
2410
|
focusInputPath(inputPath) {
|
|
2397
2411
|
// eslint-disable-next-line react/no-string-refs
|
|
2398
2412
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
@@ -2643,7 +2657,7 @@ var Expression$1 = {
|
|
|
2643
2657
|
propUpgrades: propUpgrades$2,
|
|
2644
2658
|
// For use by the editor
|
|
2645
2659
|
isLintable: true,
|
|
2646
|
-
|
|
2660
|
+
scorer: scoreExpression,
|
|
2647
2661
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2648
2662
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2649
2663
|
if (correctAnswers.length === 0) {
|
|
@@ -9330,7 +9344,7 @@ class Graphie$1 {
|
|
|
9330
9344
|
// We round the coordinates to make testing easier, because trig
|
|
9331
9345
|
// operations return unround numbers.
|
|
9332
9346
|
// TODO(benchristel): move rounding to scalePoint?
|
|
9333
|
-
const round = x => number.round(x, 6);
|
|
9347
|
+
const round = x => number$1.round(x, 6);
|
|
9334
9348
|
const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
|
|
9335
9349
|
const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
|
|
9336
9350
|
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
@@ -10887,7 +10901,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
10887
10901
|
return eq$3(factors[0], factor);
|
|
10888
10902
|
});
|
|
10889
10903
|
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
10890
|
-
return number.equal(pair[0], pair[1], tolerance);
|
|
10904
|
+
return number$1.equal(pair[0], pair[1], tolerance);
|
|
10891
10905
|
});
|
|
10892
10906
|
if (same && congruentEnough) {
|
|
10893
10907
|
return true;
|
|
@@ -17914,7 +17928,7 @@ class InteractionTracker {
|
|
|
17914
17928
|
*/
|
|
17915
17929
|
const arrayRules = {
|
|
17916
17930
|
fence: {
|
|
17917
|
-
match: SimpleMarkdown.defaultRules.fence.match,
|
|
17931
|
+
match: SimpleMarkdown$1.defaultRules.fence.match,
|
|
17918
17932
|
order: 1,
|
|
17919
17933
|
parse: (capture, state, parse) => ({
|
|
17920
17934
|
type: "codeBlock",
|
|
@@ -17923,14 +17937,14 @@ const arrayRules = {
|
|
|
17923
17937
|
})
|
|
17924
17938
|
},
|
|
17925
17939
|
paragraph: {
|
|
17926
|
-
match: SimpleMarkdown.defaultRules.paragraph.match,
|
|
17940
|
+
match: SimpleMarkdown$1.defaultRules.paragraph.match,
|
|
17927
17941
|
order: 2,
|
|
17928
17942
|
parse: (capture, state, parse) => ({
|
|
17929
17943
|
content: capture[1]
|
|
17930
17944
|
})
|
|
17931
17945
|
}
|
|
17932
17946
|
};
|
|
17933
|
-
const builtArrayParser = SimpleMarkdown.parserFor(arrayRules);
|
|
17947
|
+
const builtArrayParser = SimpleMarkdown$1.parserFor(arrayRules);
|
|
17934
17948
|
|
|
17935
17949
|
// This should just return an array of strings! magick!
|
|
17936
17950
|
const parseToArray = source => {
|
|
@@ -18355,7 +18369,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18355
18369
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18356
18370
|
table: _extends({}, pureMarkdownRules.table, {
|
|
18357
18371
|
react: function (node, output, state) {
|
|
18358
|
-
const table = SimpleMarkdown.defaultRules.table.react(node, output, state);
|
|
18372
|
+
const table = SimpleMarkdown$1.defaultRules.table.react(node, output, state);
|
|
18359
18373
|
if (!state.isMobile) {
|
|
18360
18374
|
return table;
|
|
18361
18375
|
}
|
|
@@ -18404,7 +18418,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18404
18418
|
contents = rules$1.lint.react(node.table, output, state);
|
|
18405
18419
|
}
|
|
18406
18420
|
} else {
|
|
18407
|
-
const tableOutput = SimpleMarkdown.defaultRules.table.react(node.table, output, state);
|
|
18421
|
+
const tableOutput = SimpleMarkdown$1.defaultRules.table.react(node.table, output, state);
|
|
18408
18422
|
const caption = /*#__PURE__*/React.createElement("caption", {
|
|
18409
18423
|
key: "caption",
|
|
18410
18424
|
className: "perseus-table-title"
|
|
@@ -18474,7 +18488,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18474
18488
|
// window and handle reverse tabnapping phishing attacks
|
|
18475
18489
|
link: _extends({}, pureMarkdownRules.link, {
|
|
18476
18490
|
react: function (node, output, state) {
|
|
18477
|
-
const link = SimpleMarkdown.defaultRules.link.react(node, output, state);
|
|
18491
|
+
const link = SimpleMarkdown$1.defaultRules.link.react(node, output, state);
|
|
18478
18492
|
const href = link.props.href;
|
|
18479
18493
|
let rel = null;
|
|
18480
18494
|
// The href prop should always be here, but just in case somehow the article's
|
|
@@ -18507,7 +18521,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18507
18521
|
className: "perseus-markdown-alt perseus-sr-only"
|
|
18508
18522
|
}, output(node.content, state));
|
|
18509
18523
|
}
|
|
18510
|
-
return SimpleMarkdown.defaultRules.codeBlock.react(node, output, state);
|
|
18524
|
+
return SimpleMarkdown$1.defaultRules.codeBlock.react(node, output, state);
|
|
18511
18525
|
}
|
|
18512
18526
|
}),
|
|
18513
18527
|
// The lint rule never actually matches anything.
|
|
@@ -18549,7 +18563,7 @@ const inlineNodeTypes = {
|
|
|
18549
18563
|
};
|
|
18550
18564
|
|
|
18551
18565
|
// @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);
|
|
18566
|
+
const builtParser$1 = SimpleMarkdown$1.parserFor(rules$1);
|
|
18553
18567
|
const parse$3 = (source, state) => {
|
|
18554
18568
|
const paragraphedSource = source + "\n\n";
|
|
18555
18569
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
@@ -18642,10 +18656,10 @@ var PerseusMarkdown = {
|
|
|
18642
18656
|
traverseContent: traverseContent,
|
|
18643
18657
|
parse: parse$3,
|
|
18644
18658
|
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
|
|
18659
|
+
reactFor: SimpleMarkdown$1.reactFor,
|
|
18660
|
+
ruleOutput: SimpleMarkdown$1.ruleOutput(rules$1, "react"),
|
|
18661
|
+
basicOutput: SimpleMarkdown$1.reactFor(SimpleMarkdown$1.ruleOutput(rules$1, "react")),
|
|
18662
|
+
sanitizeUrl: SimpleMarkdown$1.sanitizeUrl
|
|
18649
18663
|
};
|
|
18650
18664
|
|
|
18651
18665
|
class QuestionParagraph extends React.Component {
|
|
@@ -18766,9 +18780,9 @@ const getWidgetExport = name => {
|
|
|
18766
18780
|
var _widgets$name3;
|
|
18767
18781
|
return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
|
|
18768
18782
|
};
|
|
18769
|
-
const
|
|
18770
|
-
var _widgets$name$
|
|
18771
|
-
return (_widgets$name$
|
|
18783
|
+
const getWidgetScorer = name => {
|
|
18784
|
+
var _widgets$name$scorer, _widgets$name4;
|
|
18785
|
+
return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
|
|
18772
18786
|
};
|
|
18773
18787
|
const getEditor = name => {
|
|
18774
18788
|
return _.has(editors, name) ? editors[name] : null;
|
|
@@ -19073,7 +19087,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19073
19087
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
19074
19088
|
getWidget: getWidget,
|
|
19075
19089
|
getWidgetExport: getWidgetExport,
|
|
19076
|
-
|
|
19090
|
+
getWidgetScorer: getWidgetScorer,
|
|
19077
19091
|
getEditor: getEditor,
|
|
19078
19092
|
getTransform: getTransform,
|
|
19079
19093
|
getVersion: getVersion,
|
|
@@ -19105,12 +19119,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19105
19119
|
}
|
|
19106
19120
|
let score = null;
|
|
19107
19121
|
const userInput = userInputMap[id];
|
|
19108
|
-
const
|
|
19122
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19109
19123
|
if (widget.type === "group") {
|
|
19110
19124
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19111
19125
|
score = Util.flattenScores(scores);
|
|
19112
|
-
} else if (
|
|
19113
|
-
score =
|
|
19126
|
+
} else if (scorer) {
|
|
19127
|
+
score = scorer(userInput, widget.options, strings, locale);
|
|
19114
19128
|
}
|
|
19115
19129
|
if (score) {
|
|
19116
19130
|
return Util.scoreIsEmpty(score);
|
|
@@ -19135,12 +19149,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19135
19149
|
return;
|
|
19136
19150
|
}
|
|
19137
19151
|
const userInput = userInputMap[id];
|
|
19138
|
-
const
|
|
19152
|
+
const scorer = getWidgetScorer(widget.type);
|
|
19139
19153
|
if (widget.type === "group") {
|
|
19140
19154
|
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19141
19155
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19142
|
-
} else if (
|
|
19143
|
-
widgetScores[id] =
|
|
19156
|
+
} else if (scorer) {
|
|
19157
|
+
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
19144
19158
|
}
|
|
19145
19159
|
});
|
|
19146
19160
|
return widgetScores;
|
|
@@ -19599,42 +19613,6 @@ class Renderer extends React.Component {
|
|
|
19599
19613
|
}
|
|
19600
19614
|
return null;
|
|
19601
19615
|
};
|
|
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
19616
|
/**
|
|
19639
19617
|
* Serializes the questions state so it can be recovered.
|
|
19640
19618
|
*
|
|
@@ -20583,9 +20561,9 @@ class Renderer extends React.Component {
|
|
|
20583
20561
|
// WidgetContainers don't update their widgets' props when
|
|
20584
20562
|
// they are re-rendered, so even if they've been
|
|
20585
20563
|
// re-rendered we need to call these methods on them.
|
|
20586
|
-
|
|
20564
|
+
this.widgetIds.forEach(id => {
|
|
20587
20565
|
const container = this._widgetContainers.get(makeContainerId(id));
|
|
20588
|
-
container
|
|
20566
|
+
container == null || container.replaceWidgetProps(this.getWidgetProps(id));
|
|
20589
20567
|
});
|
|
20590
20568
|
if (this.props.serializedState && !_.isEqual(this.props.serializedState, this.getSerializedState())) {
|
|
20591
20569
|
this.restoreSerializedState(this.props.serializedState);
|
|
@@ -20614,6 +20592,42 @@ class Renderer extends React.Component {
|
|
|
20614
20592
|
}
|
|
20615
20593
|
this._isMounted = false;
|
|
20616
20594
|
}
|
|
20595
|
+
getWidgetProps(widgetId) {
|
|
20596
|
+
const apiOptions = this.getApiOptions();
|
|
20597
|
+
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
20598
|
+
|
|
20599
|
+
// The widget needs access to its "rubric" at all times when in review
|
|
20600
|
+
// mode (which is really just part of its widget info).
|
|
20601
|
+
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
20602
|
+
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
20603
|
+
if (!this._interactionTrackers) {
|
|
20604
|
+
this._interactionTrackers = {};
|
|
20605
|
+
}
|
|
20606
|
+
let interactionTracker = this._interactionTrackers[widgetId];
|
|
20607
|
+
if (!interactionTracker) {
|
|
20608
|
+
interactionTracker = this._interactionTrackers[widgetId] = new InteractionTracker(apiOptions.trackInteraction, widgetInfo && widgetInfo.type, widgetId, getTracking(widgetInfo && widgetInfo.type));
|
|
20609
|
+
}
|
|
20610
|
+
return _extends({}, widgetProps, {
|
|
20611
|
+
widgetId: widgetId,
|
|
20612
|
+
alignment: widgetInfo && widgetInfo.alignment,
|
|
20613
|
+
static: widgetInfo == null ? void 0 : widgetInfo.static,
|
|
20614
|
+
problemNum: this.props.problemNum,
|
|
20615
|
+
apiOptions: this.getApiOptions(),
|
|
20616
|
+
keypadElement: this.props.keypadElement,
|
|
20617
|
+
questionCompleted: this.props.questionCompleted,
|
|
20618
|
+
showSolutions: this.props.showSolutions,
|
|
20619
|
+
onFocus: _.partial(this._onWidgetFocus, widgetId),
|
|
20620
|
+
onBlur: _.partial(this._onWidgetBlur, widgetId),
|
|
20621
|
+
findWidgets: this.findWidgets,
|
|
20622
|
+
reviewModeRubric: reviewModeRubric,
|
|
20623
|
+
reviewMode: this.props.reviewMode,
|
|
20624
|
+
onChange: (newProps, cb, silent = false) => {
|
|
20625
|
+
this._setWidgetProps(widgetId, newProps, cb, silent);
|
|
20626
|
+
},
|
|
20627
|
+
trackInteraction: interactionTracker.track,
|
|
20628
|
+
isLastUsedWidget: widgetId === this.state.lastUsedWidgetId
|
|
20629
|
+
});
|
|
20630
|
+
}
|
|
20617
20631
|
emptyWidgets() {
|
|
20618
20632
|
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20619
20633
|
}
|
|
@@ -20650,6 +20664,25 @@ class Renderer extends React.Component {
|
|
|
20650
20664
|
});
|
|
20651
20665
|
return userInputMap;
|
|
20652
20666
|
}
|
|
20667
|
+
/**
|
|
20668
|
+
* Returns a JSON representation of the content and widgets
|
|
20669
|
+
* that can be passed to an LLM for prompt context.
|
|
20670
|
+
*/
|
|
20671
|
+
getPromptJSON() {
|
|
20672
|
+
const {
|
|
20673
|
+
content
|
|
20674
|
+
} = this.props;
|
|
20675
|
+
const widgetJSON = {};
|
|
20676
|
+
this.widgetIds.forEach(id => {
|
|
20677
|
+
const widget = this.getWidgetInstance(id);
|
|
20678
|
+
widgetJSON[id] = (widget == null || widget.getPromptJSON == null ? void 0 : widget.getPromptJSON()) || {};
|
|
20679
|
+
});
|
|
20680
|
+
return {
|
|
20681
|
+
content,
|
|
20682
|
+
widgets: widgetJSON
|
|
20683
|
+
};
|
|
20684
|
+
}
|
|
20685
|
+
|
|
20653
20686
|
/**
|
|
20654
20687
|
* Returns an object mapping from widget ID to perseus-style score.
|
|
20655
20688
|
* The keys of this object are the values of the array returned
|
|
@@ -21389,6 +21422,19 @@ SimpleKeypadInput.propTypes = {
|
|
|
21389
21422
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
21390
21423
|
};
|
|
21391
21424
|
|
|
21425
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
21426
|
+
return {
|
|
21427
|
+
type: "input-number",
|
|
21428
|
+
options: {
|
|
21429
|
+
simplify: renderProps.simplify,
|
|
21430
|
+
answerType: renderProps.answerType
|
|
21431
|
+
},
|
|
21432
|
+
userInput: {
|
|
21433
|
+
value: userInput.currentValue
|
|
21434
|
+
}
|
|
21435
|
+
};
|
|
21436
|
+
};
|
|
21437
|
+
|
|
21392
21438
|
/*
|
|
21393
21439
|
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21394
21440
|
* curly brackets.
|
|
@@ -21579,7 +21625,7 @@ const answerTypes = {
|
|
|
21579
21625
|
forms: "pi"
|
|
21580
21626
|
}
|
|
21581
21627
|
};
|
|
21582
|
-
function
|
|
21628
|
+
function scoreInputNumber(userInput, rubric, strings) {
|
|
21583
21629
|
if (rubric.answerType == null) {
|
|
21584
21630
|
rubric.answerType = "number";
|
|
21585
21631
|
}
|
|
@@ -21713,6 +21759,9 @@ class InputNumber extends React.Component {
|
|
|
21713
21759
|
getUserInput() {
|
|
21714
21760
|
return InputNumber.getUserInputFromProps(this.props);
|
|
21715
21761
|
}
|
|
21762
|
+
getPromptJSON() {
|
|
21763
|
+
return getPromptJSON$s(this.props, this.getUserInput());
|
|
21764
|
+
}
|
|
21716
21765
|
render() {
|
|
21717
21766
|
if (this.props.apiOptions.customKeypad) {
|
|
21718
21767
|
// TODO(charlie): Support "Review Mode".
|
|
@@ -21814,7 +21863,7 @@ var InputNumber$1 = {
|
|
|
21814
21863
|
widget: InputNumber,
|
|
21815
21864
|
transform: propTransform$3,
|
|
21816
21865
|
isLintable: true,
|
|
21817
|
-
|
|
21866
|
+
scorer: scoreInputNumber,
|
|
21818
21867
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
21819
21868
|
if (rubric.value == null) {
|
|
21820
21869
|
return;
|
|
@@ -21827,6 +21876,16 @@ var InputNumber$1 = {
|
|
|
21827
21876
|
}
|
|
21828
21877
|
};
|
|
21829
21878
|
|
|
21879
|
+
const getPromptJSON$r = (renderProps, userInput) => {
|
|
21880
|
+
return {
|
|
21881
|
+
type: "numeric-input",
|
|
21882
|
+
label: renderProps.labelText,
|
|
21883
|
+
userInput: {
|
|
21884
|
+
value: userInput.currentValue
|
|
21885
|
+
}
|
|
21886
|
+
};
|
|
21887
|
+
};
|
|
21888
|
+
|
|
21830
21889
|
const ParseTex = TexWrangler.parseTex;
|
|
21831
21890
|
const answerFormButtons = [{
|
|
21832
21891
|
title: "Integers",
|
|
@@ -21885,7 +21944,7 @@ function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
|
21885
21944
|
// Otherwise, we return input valuåe (number) stripped of the "%".
|
|
21886
21945
|
return value;
|
|
21887
21946
|
}
|
|
21888
|
-
function
|
|
21947
|
+
function scoreNumericInput(userInput, rubric, strings) {
|
|
21889
21948
|
var _matchedAnswer$messag;
|
|
21890
21949
|
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
21891
21950
|
// Don't default to validating the answer as a pi answer
|
|
@@ -22056,6 +22115,9 @@ class NumericInput extends React.Component {
|
|
|
22056
22115
|
getUserInput() {
|
|
22057
22116
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22058
22117
|
}
|
|
22118
|
+
getPromptJSON() {
|
|
22119
|
+
return getPromptJSON$r(this.props, this.getUserInput());
|
|
22120
|
+
}
|
|
22059
22121
|
render() {
|
|
22060
22122
|
let labelText = this.props.labelText;
|
|
22061
22123
|
if (labelText == null || labelText === "") {
|
|
@@ -22180,7 +22242,7 @@ var NumericInput$1 = {
|
|
|
22180
22242
|
widget: NumericInput,
|
|
22181
22243
|
transform: propsTransform,
|
|
22182
22244
|
isLintable: true,
|
|
22183
|
-
|
|
22245
|
+
scorer: scoreNumericInput,
|
|
22184
22246
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22185
22247
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22186
22248
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -22211,15 +22273,43 @@ var NumericInput$1 = {
|
|
|
22211
22273
|
}
|
|
22212
22274
|
};
|
|
22213
22275
|
|
|
22276
|
+
const getPromptJSON$q = (renderProps, userInput) => {
|
|
22277
|
+
const choices = renderProps.choices || [];
|
|
22278
|
+
const options = choices.map(choice => {
|
|
22279
|
+
return {
|
|
22280
|
+
value: choice.content
|
|
22281
|
+
};
|
|
22282
|
+
});
|
|
22283
|
+
return {
|
|
22284
|
+
type: "radio",
|
|
22285
|
+
hasNoneOfTheAbove: !!renderProps.hasNoneOfTheAbove,
|
|
22286
|
+
options,
|
|
22287
|
+
userInput: {
|
|
22288
|
+
selectedOptions: userInput.choicesSelected.slice()
|
|
22289
|
+
}
|
|
22290
|
+
};
|
|
22291
|
+
};
|
|
22292
|
+
|
|
22293
|
+
const getPromptJSON$p = renderProps => {
|
|
22294
|
+
return {
|
|
22295
|
+
type: "passage-ref",
|
|
22296
|
+
options: {
|
|
22297
|
+
passageNumber: renderProps.passageNumber,
|
|
22298
|
+
referenceNumber: renderProps.referenceNumber,
|
|
22299
|
+
summaryText: renderProps.summaryText
|
|
22300
|
+
}
|
|
22301
|
+
};
|
|
22302
|
+
};
|
|
22303
|
+
|
|
22214
22304
|
/**
|
|
22215
|
-
* Several widgets don't have "right"/"wrong"
|
|
22305
|
+
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
22216
22306
|
* so this just says to move on past those widgets
|
|
22217
22307
|
*
|
|
22218
22308
|
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
22219
|
-
*
|
|
22309
|
+
* scoring logic and the thing scoring an exercise
|
|
22220
22310
|
* should just know to skip these
|
|
22221
22311
|
*/
|
|
22222
|
-
function
|
|
22312
|
+
function scoreNoop(points = 0) {
|
|
22223
22313
|
return {
|
|
22224
22314
|
type: "points",
|
|
22225
22315
|
earned: points,
|
|
@@ -23603,6 +23693,17 @@ const styles$x = StyleSheet.create({
|
|
|
23603
23693
|
}
|
|
23604
23694
|
});
|
|
23605
23695
|
|
|
23696
|
+
const getPromptJSON$o = renderProps => {
|
|
23697
|
+
return {
|
|
23698
|
+
type: "passage",
|
|
23699
|
+
options: {
|
|
23700
|
+
passageTitle: renderProps.passageTitle,
|
|
23701
|
+
passageText: renderProps.passageText,
|
|
23702
|
+
footnotes: renderProps.footnotes
|
|
23703
|
+
}
|
|
23704
|
+
};
|
|
23705
|
+
};
|
|
23706
|
+
|
|
23606
23707
|
function getInitialParseState() {
|
|
23607
23708
|
return {
|
|
23608
23709
|
currentRef: [],
|
|
@@ -23652,12 +23753,12 @@ const AltText = ({
|
|
|
23652
23753
|
}));
|
|
23653
23754
|
};
|
|
23654
23755
|
const rules = {
|
|
23655
|
-
newline: SimpleMarkdown.defaultRules.newline,
|
|
23656
|
-
paragraph: SimpleMarkdown.defaultRules.paragraph,
|
|
23657
|
-
escape: SimpleMarkdown.defaultRules.escape,
|
|
23756
|
+
newline: SimpleMarkdown$1.defaultRules.newline,
|
|
23757
|
+
paragraph: SimpleMarkdown$1.defaultRules.paragraph,
|
|
23758
|
+
escape: SimpleMarkdown$1.defaultRules.escape,
|
|
23658
23759
|
passageFootnote: {
|
|
23659
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.1,
|
|
23660
|
-
match: SimpleMarkdown.inlineRegex(/^\^/),
|
|
23760
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.1,
|
|
23761
|
+
match: SimpleMarkdown$1.inlineRegex(/^\^/),
|
|
23661
23762
|
parse: (capture, parse, state) => {
|
|
23662
23763
|
// if no footnotes have been seen, we're id 1. otherwise,
|
|
23663
23764
|
// we're the next subsequent id
|
|
@@ -23686,7 +23787,7 @@ const rules = {
|
|
|
23686
23787
|
}
|
|
23687
23788
|
},
|
|
23688
23789
|
refStart: {
|
|
23689
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.2,
|
|
23790
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.2,
|
|
23690
23791
|
match: function (source) {
|
|
23691
23792
|
const capture = /^\{\{/.exec(source);
|
|
23692
23793
|
if (capture) {
|
|
@@ -23772,8 +23873,8 @@ const rules = {
|
|
|
23772
23873
|
}
|
|
23773
23874
|
},
|
|
23774
23875
|
refEnd: {
|
|
23775
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.3,
|
|
23776
|
-
match: SimpleMarkdown.inlineRegex(/^\}\}/),
|
|
23876
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.3,
|
|
23877
|
+
match: SimpleMarkdown$1.inlineRegex(/^\}\}/),
|
|
23777
23878
|
parse: (capture, parse, state) => {
|
|
23778
23879
|
if (!state.useRefs) {
|
|
23779
23880
|
return {
|
|
@@ -23801,8 +23902,8 @@ const rules = {
|
|
|
23801
23902
|
}
|
|
23802
23903
|
},
|
|
23803
23904
|
squareLabel: {
|
|
23804
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.4,
|
|
23805
|
-
match: SimpleMarkdown.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
23905
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.4,
|
|
23906
|
+
match: SimpleMarkdown$1.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
23806
23907
|
parse: (capture, parse, state) => {
|
|
23807
23908
|
if (!state.firstQuestionRef) {
|
|
23808
23909
|
state.firstQuestionRef = capture[1];
|
|
@@ -23828,8 +23929,8 @@ const rules = {
|
|
|
23828
23929
|
}
|
|
23829
23930
|
},
|
|
23830
23931
|
circleLabel: {
|
|
23831
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.5,
|
|
23832
|
-
match: SimpleMarkdown.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
23932
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.5,
|
|
23933
|
+
match: SimpleMarkdown$1.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
23833
23934
|
parse: (capture, parse, state) => {
|
|
23834
23935
|
return {
|
|
23835
23936
|
content: capture[1],
|
|
@@ -23853,8 +23954,8 @@ const rules = {
|
|
|
23853
23954
|
}
|
|
23854
23955
|
},
|
|
23855
23956
|
squareBracketRef: {
|
|
23856
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.6,
|
|
23857
|
-
match: SimpleMarkdown.inlineRegex(/^\[(\d+)\]( *)/),
|
|
23957
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.6,
|
|
23958
|
+
match: SimpleMarkdown$1.inlineRegex(/^\[(\d+)\]( *)/),
|
|
23858
23959
|
parse: (capture, parse, state) => {
|
|
23859
23960
|
if (!state.firstSentenceRef) {
|
|
23860
23961
|
state.firstSentenceRef = capture[1];
|
|
@@ -23877,8 +23978,8 @@ const rules = {
|
|
|
23877
23978
|
}
|
|
23878
23979
|
},
|
|
23879
23980
|
highlight: {
|
|
23880
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
23881
|
-
match: SimpleMarkdown.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
23981
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.7,
|
|
23982
|
+
match: SimpleMarkdown$1.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
23882
23983
|
parse: (capture, parse, state) => {
|
|
23883
23984
|
return {
|
|
23884
23985
|
content: capture[1]
|
|
@@ -23892,8 +23993,8 @@ const rules = {
|
|
|
23892
23993
|
}
|
|
23893
23994
|
},
|
|
23894
23995
|
reviewHighlight: {
|
|
23895
|
-
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
23896
|
-
match: SimpleMarkdown.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
23996
|
+
order: SimpleMarkdown$1.defaultRules.escape.order + 0.7,
|
|
23997
|
+
match: SimpleMarkdown$1.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
23897
23998
|
parse: (capture, parse, state) => {
|
|
23898
23999
|
return {
|
|
23899
24000
|
content: capture[1]
|
|
@@ -23906,11 +24007,11 @@ const rules = {
|
|
|
23906
24007
|
}, node.content)];
|
|
23907
24008
|
}
|
|
23908
24009
|
},
|
|
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, {
|
|
24010
|
+
strong: SimpleMarkdown$1.defaultRules.strong,
|
|
24011
|
+
u: SimpleMarkdown$1.defaultRules.u,
|
|
24012
|
+
em: SimpleMarkdown$1.defaultRules.em,
|
|
24013
|
+
del: SimpleMarkdown$1.defaultRules.del,
|
|
24014
|
+
text: _extends({}, SimpleMarkdown$1.defaultRules.text, {
|
|
23914
24015
|
react(node, output, state) {
|
|
23915
24016
|
return /*#__PURE__*/React.createElement("span", {
|
|
23916
24017
|
key: state.key
|
|
@@ -23951,7 +24052,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
23951
24052
|
borderRadius: "50%",
|
|
23952
24053
|
textAlign: "center"
|
|
23953
24054
|
};
|
|
23954
|
-
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24055
|
+
const builtParser = SimpleMarkdown$1.parserFor(rules);
|
|
23955
24056
|
const parse$2 = (source, state) => {
|
|
23956
24057
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
23957
24058
|
state = state || {};
|
|
@@ -23960,7 +24061,7 @@ const parse$2 = (source, state) => {
|
|
|
23960
24061
|
};
|
|
23961
24062
|
|
|
23962
24063
|
// @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"));
|
|
24064
|
+
const output = SimpleMarkdown$1.reactFor(SimpleMarkdown$1.ruleOutput(rules, "react"));
|
|
23964
24065
|
var PassageMarkdown = {
|
|
23965
24066
|
parse: parse$2,
|
|
23966
24067
|
output,
|
|
@@ -24233,6 +24334,9 @@ class Passage extends React.Component {
|
|
|
24233
24334
|
content: refContent
|
|
24234
24335
|
};
|
|
24235
24336
|
}
|
|
24337
|
+
getPromptJSON() {
|
|
24338
|
+
return getPromptJSON$o(this.props);
|
|
24339
|
+
}
|
|
24236
24340
|
|
|
24237
24341
|
/**
|
|
24238
24342
|
* Rendering
|
|
@@ -24387,7 +24491,7 @@ var Passage$1 = {
|
|
|
24387
24491
|
return _.pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
24388
24492
|
},
|
|
24389
24493
|
isLintable: true,
|
|
24390
|
-
|
|
24494
|
+
scorer: () => scoreNoop()
|
|
24391
24495
|
};
|
|
24392
24496
|
|
|
24393
24497
|
/**
|
|
@@ -24462,6 +24566,9 @@ class PassageRef extends React.Component {
|
|
|
24462
24566
|
window.removeEventListener("resize", this._throttledUpdateRange);
|
|
24463
24567
|
this._isMounted = false;
|
|
24464
24568
|
}
|
|
24569
|
+
getPromptJSON() {
|
|
24570
|
+
return getPromptJSON$p(this.props);
|
|
24571
|
+
}
|
|
24465
24572
|
render() {
|
|
24466
24573
|
const {
|
|
24467
24574
|
strings
|
|
@@ -24517,8 +24624,8 @@ var PassageRef$1 = {
|
|
|
24517
24624
|
major: 0,
|
|
24518
24625
|
minor: 1
|
|
24519
24626
|
},
|
|
24520
|
-
// TODO: things that aren't interactive shouldn't need
|
|
24521
|
-
|
|
24627
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
24628
|
+
scorer: () => scoreNoop()
|
|
24522
24629
|
};
|
|
24523
24630
|
|
|
24524
24631
|
/**
|
|
@@ -25876,7 +25983,7 @@ const styles$p = StyleSheet.create({
|
|
|
25876
25983
|
}
|
|
25877
25984
|
});
|
|
25878
25985
|
|
|
25879
|
-
function
|
|
25986
|
+
function scoreRadio(userInput, rubric, strings) {
|
|
25880
25987
|
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25881
25988
|
return sum + (selected ? 1 : 0);
|
|
25882
25989
|
}, 0);
|
|
@@ -25886,10 +25993,10 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25886
25993
|
message: null
|
|
25887
25994
|
};
|
|
25888
25995
|
}
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
if (
|
|
25996
|
+
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
25997
|
+
return currentChoice.correct ? sum + 1 : sum;
|
|
25998
|
+
}, 0);
|
|
25999
|
+
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
25893
26000
|
return {
|
|
25894
26001
|
type: "invalid",
|
|
25895
26002
|
message: strings.chooseCorrectNum
|
|
@@ -25897,9 +26004,8 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25897
26004
|
// If NOTA and some other answer are checked, ...
|
|
25898
26005
|
}
|
|
25899
26006
|
|
|
25900
|
-
|
|
25901
|
-
|
|
25902
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
26007
|
+
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
26008
|
+
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
25903
26009
|
return {
|
|
25904
26010
|
type: "invalid",
|
|
25905
26011
|
message: strings.notNoneOfTheAbove
|
|
@@ -25907,9 +26013,7 @@ function radioValidator(userInput, rubric, strings) {
|
|
|
25907
26013
|
}
|
|
25908
26014
|
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
25909
26015
|
let isCorrect;
|
|
25910
|
-
|
|
25911
|
-
// instead of the userInput?
|
|
25912
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
26016
|
+
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
25913
26017
|
isCorrect = rubric.choices.every((choice, j) => {
|
|
25914
26018
|
return i === j || !choice.correct;
|
|
25915
26019
|
});
|
|
@@ -26031,7 +26135,7 @@ class Radio$1 extends React.Component {
|
|
|
26031
26135
|
choiceStates
|
|
26032
26136
|
} = this.props;
|
|
26033
26137
|
if (choiceStates) {
|
|
26034
|
-
const score =
|
|
26138
|
+
const score = scoreRadio(this.getUserInput(), rubric, this.context.strings);
|
|
26035
26139
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26036
26140
|
const newStates = choiceStates.map(state => _extends({}, state, {
|
|
26037
26141
|
highlighted: state.selected,
|
|
@@ -26079,32 +26183,19 @@ class Radio$1 extends React.Component {
|
|
|
26079
26183
|
}
|
|
26080
26184
|
};
|
|
26081
26185
|
}
|
|
26082
|
-
static getUserInputFromProps(props) {
|
|
26186
|
+
static getUserInputFromProps(props, unshuffle = true) {
|
|
26083
26187
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
26084
26188
|
// future timeline implementers: this used to be {value: i} before
|
|
26085
26189
|
// multiple select was added)
|
|
26086
26190
|
if (props.choiceStates) {
|
|
26087
|
-
let noneOfTheAboveIndex = null;
|
|
26088
|
-
let noneOfTheAboveSelected = false;
|
|
26089
26191
|
const choiceStates = props.choiceStates;
|
|
26090
26192
|
const choicesSelected = choiceStates.map(() => false);
|
|
26091
|
-
const numCorrect = props.numCorrect;
|
|
26092
26193
|
for (let i = 0; i < choicesSelected.length; i++) {
|
|
26093
|
-
const index = props.choices[i].originalIndex;
|
|
26194
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26094
26195
|
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
26196
|
}
|
|
26103
26197
|
return {
|
|
26104
|
-
choicesSelected
|
|
26105
|
-
numCorrect,
|
|
26106
|
-
noneOfTheAboveIndex,
|
|
26107
|
-
noneOfTheAboveSelected
|
|
26198
|
+
choicesSelected
|
|
26108
26199
|
};
|
|
26109
26200
|
// Support legacy choiceState implementation
|
|
26110
26201
|
}
|
|
@@ -26113,27 +26204,14 @@ class Radio$1 extends React.Component {
|
|
|
26113
26204
|
values
|
|
26114
26205
|
} = props;
|
|
26115
26206
|
if (values) {
|
|
26116
|
-
let noneOfTheAboveIndex = null;
|
|
26117
|
-
let noneOfTheAboveSelected = false;
|
|
26118
26207
|
const choicesSelected = [...values];
|
|
26119
|
-
const numCorrect = props.numCorrect;
|
|
26120
26208
|
const valuesLength = values.length;
|
|
26121
26209
|
for (let i = 0; i < valuesLength; i++) {
|
|
26122
|
-
const index = props.choices[i].originalIndex;
|
|
26210
|
+
const index = unshuffle ? props.choices[i].originalIndex : i;
|
|
26123
26211
|
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
26212
|
}
|
|
26132
26213
|
return {
|
|
26133
|
-
choicesSelected
|
|
26134
|
-
noneOfTheAboveIndex,
|
|
26135
|
-
noneOfTheAboveSelected,
|
|
26136
|
-
numCorrect
|
|
26214
|
+
choicesSelected
|
|
26137
26215
|
};
|
|
26138
26216
|
}
|
|
26139
26217
|
// Nothing checked
|
|
@@ -26165,6 +26243,10 @@ class Radio$1 extends React.Component {
|
|
|
26165
26243
|
getUserInput() {
|
|
26166
26244
|
return Radio$1.getUserInputFromProps(this.props);
|
|
26167
26245
|
}
|
|
26246
|
+
getPromptJSON() {
|
|
26247
|
+
const userInput = Radio$1.getUserInputFromProps(this.props, false);
|
|
26248
|
+
return getPromptJSON$q(this.props, userInput);
|
|
26249
|
+
}
|
|
26168
26250
|
render() {
|
|
26169
26251
|
const {
|
|
26170
26252
|
choices
|
|
@@ -26402,13 +26484,26 @@ var Radio = {
|
|
|
26402
26484
|
},
|
|
26403
26485
|
propUpgrades: propUpgrades$1,
|
|
26404
26486
|
isLintable: true,
|
|
26405
|
-
|
|
26487
|
+
scorer: scoreRadio
|
|
26406
26488
|
};
|
|
26407
26489
|
|
|
26408
26490
|
/* eslint-disable import/no-named-as-default */
|
|
26409
26491
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
26410
26492
|
|
|
26411
|
-
|
|
26493
|
+
const getPromptJSON$n = (renderProps, userInput) => {
|
|
26494
|
+
return {
|
|
26495
|
+
type: "categorizer",
|
|
26496
|
+
options: {
|
|
26497
|
+
items: renderProps.items,
|
|
26498
|
+
categories: renderProps.categories
|
|
26499
|
+
},
|
|
26500
|
+
userInput: {
|
|
26501
|
+
itemToCategoryMapping: userInput.values
|
|
26502
|
+
}
|
|
26503
|
+
};
|
|
26504
|
+
};
|
|
26505
|
+
|
|
26506
|
+
function scoreCategorizer(userInput, rubric, strings) {
|
|
26412
26507
|
let completed = true;
|
|
26413
26508
|
let allCorrect = true;
|
|
26414
26509
|
rubric.values.forEach((value, i) => {
|
|
@@ -26453,6 +26548,9 @@ class Categorizer extends React.Component {
|
|
|
26453
26548
|
getUserInput() {
|
|
26454
26549
|
return Categorizer.getUserInputFromProps(this.props);
|
|
26455
26550
|
}
|
|
26551
|
+
getPromptJSON() {
|
|
26552
|
+
return getPromptJSON$n(this.props, this.getUserInput());
|
|
26553
|
+
}
|
|
26456
26554
|
onChange(itemNum, catNum) {
|
|
26457
26555
|
const values = [...this.props.values];
|
|
26458
26556
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -26606,7 +26704,7 @@ var Categorizer$1 = {
|
|
|
26606
26704
|
return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
26607
26705
|
},
|
|
26608
26706
|
isLintable: true,
|
|
26609
|
-
|
|
26707
|
+
scorer: scoreCategorizer
|
|
26610
26708
|
};
|
|
26611
26709
|
|
|
26612
26710
|
/**
|
|
@@ -26656,7 +26754,19 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
26656
26754
|
*/
|
|
26657
26755
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
26658
26756
|
|
|
26659
|
-
|
|
26757
|
+
const getUnsupportedPromptJSON = (widgetType, message = "") => {
|
|
26758
|
+
return {
|
|
26759
|
+
type: widgetType,
|
|
26760
|
+
isSupported: false,
|
|
26761
|
+
message
|
|
26762
|
+
};
|
|
26763
|
+
};
|
|
26764
|
+
|
|
26765
|
+
const getPromptJSON$m = () => {
|
|
26766
|
+
return getUnsupportedPromptJSON("cs-program");
|
|
26767
|
+
};
|
|
26768
|
+
|
|
26769
|
+
function scoreCSProgram(state) {
|
|
26660
26770
|
// The iframe can tell us whether it's correct or incorrect,
|
|
26661
26771
|
// and pass an optional message
|
|
26662
26772
|
if (state.status === "correct") {
|
|
@@ -26742,6 +26852,9 @@ class CSProgram extends React.Component {
|
|
|
26742
26852
|
message: this.props.message
|
|
26743
26853
|
};
|
|
26744
26854
|
}
|
|
26855
|
+
getPromptJSON() {
|
|
26856
|
+
return getPromptJSON$m();
|
|
26857
|
+
}
|
|
26745
26858
|
render() {
|
|
26746
26859
|
if (!this.props.programID) {
|
|
26747
26860
|
return /*#__PURE__*/React.createElement("div", null);
|
|
@@ -26824,7 +26937,15 @@ var CSProgram$1 = {
|
|
|
26824
26937
|
supportedAlignments: ["block", "full-width"],
|
|
26825
26938
|
widget: CSProgram,
|
|
26826
26939
|
hidden: true,
|
|
26827
|
-
|
|
26940
|
+
scorer: scoreCSProgram
|
|
26941
|
+
};
|
|
26942
|
+
|
|
26943
|
+
const getPromptJSON$l = renderProps => {
|
|
26944
|
+
return {
|
|
26945
|
+
type: "definition",
|
|
26946
|
+
definition: renderProps.definition,
|
|
26947
|
+
togglePrompt: renderProps.togglePrompt
|
|
26948
|
+
};
|
|
26828
26949
|
};
|
|
26829
26950
|
|
|
26830
26951
|
class Definition extends React.Component {
|
|
@@ -26834,6 +26955,9 @@ class Definition extends React.Component {
|
|
|
26834
26955
|
// doesn't implement any Widget methods
|
|
26835
26956
|
this.isWidget = true;
|
|
26836
26957
|
}
|
|
26958
|
+
getPromptJSON() {
|
|
26959
|
+
return getPromptJSON$l(this.props);
|
|
26960
|
+
}
|
|
26837
26961
|
render() {
|
|
26838
26962
|
return /*#__PURE__*/React.createElement(DefinitionConsumer, null, ({
|
|
26839
26963
|
activeDefinitionId,
|
|
@@ -26889,8 +27013,8 @@ var Definition$1 = {
|
|
|
26889
27013
|
defaultAlignment: "inline",
|
|
26890
27014
|
widget: Definition,
|
|
26891
27015
|
transform: x => x,
|
|
26892
|
-
// TODO: things that aren't interactive shouldn't need
|
|
26893
|
-
|
|
27016
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27017
|
+
scorer: () => scoreNoop()
|
|
26894
27018
|
};
|
|
26895
27019
|
|
|
26896
27020
|
// The props are type `any` on purpose so that this can receive props
|
|
@@ -26921,11 +27045,23 @@ var DeprecatedStandin$1 = {
|
|
|
26921
27045
|
displayName: "Deprecated Standin",
|
|
26922
27046
|
widget: DeprecatedStandin,
|
|
26923
27047
|
hidden: true,
|
|
26924
|
-
// TODO: things that aren't interactive shouldn't need
|
|
26925
|
-
|
|
27048
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27049
|
+
scorer: () => scoreNoop(1)
|
|
27050
|
+
};
|
|
27051
|
+
|
|
27052
|
+
const getPromptJSON$k = (renderProps, userInput) => {
|
|
27053
|
+
return {
|
|
27054
|
+
type: "dropdown",
|
|
27055
|
+
options: {
|
|
27056
|
+
items: renderProps.choices
|
|
27057
|
+
},
|
|
27058
|
+
userInput: {
|
|
27059
|
+
selectedIndex: userInput.value - 1 // Offset to account for placeholder
|
|
27060
|
+
}
|
|
27061
|
+
};
|
|
26926
27062
|
};
|
|
26927
27063
|
|
|
26928
|
-
function
|
|
27064
|
+
function scoreDropdown(userInput, rubric) {
|
|
26929
27065
|
const selected = userInput.value;
|
|
26930
27066
|
if (selected === 0) {
|
|
26931
27067
|
return {
|
|
@@ -26968,6 +27104,9 @@ class Dropdown extends React.Component {
|
|
|
26968
27104
|
value: this.props.selected
|
|
26969
27105
|
};
|
|
26970
27106
|
}
|
|
27107
|
+
getPromptJSON() {
|
|
27108
|
+
return getPromptJSON$k(this.props, this.getUserInput());
|
|
27109
|
+
}
|
|
26971
27110
|
render() {
|
|
26972
27111
|
const children = [/*#__PURE__*/React.createElement(OptionItem, {
|
|
26973
27112
|
key: "placeholder",
|
|
@@ -27015,7 +27154,15 @@ var Dropdown$1 = {
|
|
|
27015
27154
|
accessible: true,
|
|
27016
27155
|
widget: Dropdown,
|
|
27017
27156
|
transform: optionsTransform,
|
|
27018
|
-
|
|
27157
|
+
scorer: scoreDropdown
|
|
27158
|
+
};
|
|
27159
|
+
|
|
27160
|
+
const getPromptJSON$j = renderProps => {
|
|
27161
|
+
return {
|
|
27162
|
+
type: "explanation",
|
|
27163
|
+
showPrompt: renderProps.showPrompt,
|
|
27164
|
+
explanation: renderProps.explanation
|
|
27165
|
+
};
|
|
27019
27166
|
};
|
|
27020
27167
|
|
|
27021
27168
|
// transform = _.identity
|
|
@@ -27052,6 +27199,9 @@ class Explanation extends React.Component {
|
|
|
27052
27199
|
componentWillUnmount() {
|
|
27053
27200
|
this._mounted = false;
|
|
27054
27201
|
}
|
|
27202
|
+
getPromptJSON() {
|
|
27203
|
+
return getPromptJSON$j(this.props);
|
|
27204
|
+
}
|
|
27055
27205
|
render() {
|
|
27056
27206
|
const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
|
|
27057
27207
|
const caretIcon = this.state.expanded ? caretUp : caretDown;
|
|
@@ -27166,8 +27316,25 @@ var Explanation$1 = {
|
|
|
27166
27316
|
widget: Explanation,
|
|
27167
27317
|
transform: _.identity,
|
|
27168
27318
|
isLintable: true,
|
|
27169
|
-
// TODO: things that aren't interactive shouldn't need
|
|
27170
|
-
|
|
27319
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27320
|
+
scorer: () => scoreNoop()
|
|
27321
|
+
};
|
|
27322
|
+
|
|
27323
|
+
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
27324
|
+
if (!rendererJSON) {
|
|
27325
|
+
return {
|
|
27326
|
+
type: "graded-group",
|
|
27327
|
+
title,
|
|
27328
|
+
content: "",
|
|
27329
|
+
widgets: {},
|
|
27330
|
+
hint: hintRendererJSON
|
|
27331
|
+
};
|
|
27332
|
+
}
|
|
27333
|
+
return _extends({}, rendererJSON, {
|
|
27334
|
+
title,
|
|
27335
|
+
type: "graded-group",
|
|
27336
|
+
hint: hintRendererJSON
|
|
27337
|
+
});
|
|
27171
27338
|
};
|
|
27172
27339
|
|
|
27173
27340
|
class GradedGroupAnswerBar extends React.Component {
|
|
@@ -27302,6 +27469,8 @@ class GradedGroup extends React.Component {
|
|
|
27302
27469
|
message: "",
|
|
27303
27470
|
answerBarState: "HIDDEN"
|
|
27304
27471
|
};
|
|
27472
|
+
this.rendererRef = /*#__PURE__*/React.createRef();
|
|
27473
|
+
this.hintRendererRef = /*#__PURE__*/React.createRef();
|
|
27305
27474
|
this.change = (...args) => {
|
|
27306
27475
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
27307
27476
|
return change.apply(this, args);
|
|
@@ -27317,13 +27486,9 @@ class GradedGroup extends React.Component {
|
|
|
27317
27486
|
status: GRADING_STATUSES.ungraded,
|
|
27318
27487
|
message: ""
|
|
27319
27488
|
});
|
|
27320
|
-
|
|
27321
|
-
// eslint-disable-next-line react/no-string-refs
|
|
27322
|
-
if (this.refs.renderer) {
|
|
27489
|
+
if (this.rendererRef.current) {
|
|
27323
27490
|
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();
|
|
27491
|
+
const emptyWidgets = this.rendererRef.current.emptyWidgets();
|
|
27327
27492
|
const answerable = emptyWidgets.length === 0;
|
|
27328
27493
|
const answerBarState = this.state.answerBarState;
|
|
27329
27494
|
this.setState({
|
|
@@ -27332,12 +27497,11 @@ class GradedGroup extends React.Component {
|
|
|
27332
27497
|
}
|
|
27333
27498
|
};
|
|
27334
27499
|
this._checkAnswer = () => {
|
|
27335
|
-
|
|
27336
|
-
|
|
27337
|
-
this.
|
|
27338
|
-
|
|
27339
|
-
|
|
27340
|
-
const score = this.refs.renderer.score();
|
|
27500
|
+
var _this$rendererRef$cur, _this$rendererRef$cur2;
|
|
27501
|
+
(_this$rendererRef$cur = this.rendererRef.current) == null || _this$rendererRef$cur.showRationalesForCurrentlySelectedChoices();
|
|
27502
|
+
const score = ((_this$rendererRef$cur2 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur2.score()) || {
|
|
27503
|
+
type: "invalid"
|
|
27504
|
+
};
|
|
27341
27505
|
const {
|
|
27342
27506
|
INVALID_MESSAGE_PREFIX,
|
|
27343
27507
|
DEFAULT_INVALID_MESSAGE_1,
|
|
@@ -27357,34 +27521,39 @@ class GradedGroup extends React.Component {
|
|
|
27357
27521
|
};
|
|
27358
27522
|
// Mobile API
|
|
27359
27523
|
this.getInputPaths = () => {
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
return this.refs.renderer.getInputPaths();
|
|
27524
|
+
var _this$rendererRef$cur3;
|
|
27525
|
+
return ((_this$rendererRef$cur3 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur3.getInputPaths()) || [];
|
|
27363
27526
|
};
|
|
27364
27527
|
this.setInputValue = (path, newValue, cb) => {
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
return this.refs.renderer.setInputValue(path, newValue, cb);
|
|
27528
|
+
var _this$rendererRef$cur4;
|
|
27529
|
+
return (_this$rendererRef$cur4 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur4.setInputValue(path, newValue, cb);
|
|
27368
27530
|
};
|
|
27369
27531
|
this.focus = () => {
|
|
27370
|
-
|
|
27371
|
-
|
|
27372
|
-
return this.refs.renderer.focus();
|
|
27532
|
+
var _this$rendererRef$cur5;
|
|
27533
|
+
return !!((_this$rendererRef$cur5 = this.rendererRef.current) != null && _this$rendererRef$cur5.focus());
|
|
27373
27534
|
};
|
|
27374
27535
|
this.focusInputPath = path => {
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
this.refs.renderer.focusPath(path);
|
|
27536
|
+
var _this$rendererRef$cur6;
|
|
27537
|
+
(_this$rendererRef$cur6 = this.rendererRef.current) == null || _this$rendererRef$cur6.focusPath(path);
|
|
27378
27538
|
};
|
|
27379
27539
|
this.blurInputPath = path => {
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
this.refs.renderer.blurPath(path);
|
|
27540
|
+
var _this$rendererRef$cur7;
|
|
27541
|
+
(_this$rendererRef$cur7 = this.rendererRef.current) == null || _this$rendererRef$cur7.blurPath(path);
|
|
27383
27542
|
};
|
|
27384
27543
|
}
|
|
27385
27544
|
shouldComponentUpdate(nextProps, nextState) {
|
|
27386
27545
|
return nextProps !== this.props || nextState !== this.state;
|
|
27387
27546
|
}
|
|
27547
|
+
getPromptJSON() {
|
|
27548
|
+
var _this$hintRendererRef, _this$props$hint, _this$rendererRef$cur8;
|
|
27549
|
+
// If the hint isn't expanded, we can't get the prompt JSON from the rendered widgets.
|
|
27550
|
+
// We'll just pass in the hint content as a string instead.
|
|
27551
|
+
const hint = ((_this$hintRendererRef = this.hintRendererRef.current) == null ? void 0 : _this$hintRendererRef.getPromptJSON()) || {
|
|
27552
|
+
content: ((_this$props$hint = this.props.hint) == null ? void 0 : _this$props$hint.content) || "",
|
|
27553
|
+
widgets: {}
|
|
27554
|
+
};
|
|
27555
|
+
return getPromptJSON$i(this.props.title, (_this$rendererRef$cur8 = this.rendererRef.current) == null ? void 0 : _this$rendererRef$cur8.getPromptJSON(), hint);
|
|
27556
|
+
}
|
|
27388
27557
|
render() {
|
|
27389
27558
|
const apiOptions = _.extend({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
27390
27559
|
// Api Rewriting to support correct onFocus/onBlur
|
|
@@ -27441,8 +27610,7 @@ class GradedGroup extends React.Component {
|
|
|
27441
27610
|
}, !!this.props.title && /*#__PURE__*/React.createElement("div", {
|
|
27442
27611
|
className: css(styles$j.title)
|
|
27443
27612
|
}, this.props.title), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props, {
|
|
27444
|
-
|
|
27445
|
-
ref: "renderer",
|
|
27613
|
+
ref: this.rendererRef,
|
|
27446
27614
|
apiOptions: _extends({}, apiOptions, {
|
|
27447
27615
|
readOnly
|
|
27448
27616
|
}),
|
|
@@ -27484,8 +27652,7 @@ class GradedGroup extends React.Component {
|
|
|
27484
27652
|
});
|
|
27485
27653
|
}
|
|
27486
27654
|
}, this.context.strings.hideExplanation), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props.hint, {
|
|
27487
|
-
|
|
27488
|
-
ref: "hints-renderer",
|
|
27655
|
+
ref: this.hintRendererRef,
|
|
27489
27656
|
apiOptions: apiOptions,
|
|
27490
27657
|
linterContext: this.props.linterContext,
|
|
27491
27658
|
strings: this.context.strings
|
|
@@ -27587,6 +27754,16 @@ var GradedGroup$1 = {
|
|
|
27587
27754
|
isLintable: true
|
|
27588
27755
|
};
|
|
27589
27756
|
|
|
27757
|
+
const getPromptJSON$h = (renderProps, activeGroupJSON) => {
|
|
27758
|
+
return {
|
|
27759
|
+
type: "graded-group-set",
|
|
27760
|
+
options: {
|
|
27761
|
+
groupCount: renderProps.gradedGroups.length,
|
|
27762
|
+
currentGroup: activeGroupJSON
|
|
27763
|
+
}
|
|
27764
|
+
};
|
|
27765
|
+
};
|
|
27766
|
+
|
|
27590
27767
|
class Indicators extends React.Component {
|
|
27591
27768
|
constructor(...args) {
|
|
27592
27769
|
super(...args);
|
|
@@ -27674,6 +27851,10 @@ class GradedGroupSet extends React.Component {
|
|
|
27674
27851
|
nextProps.gradedGroups;
|
|
27675
27852
|
return nextProps !== this.props || nextState !== this.state;
|
|
27676
27853
|
}
|
|
27854
|
+
getPromptJSON() {
|
|
27855
|
+
const activeGroupPromptJSON = this._childGroup.getPromptJSON();
|
|
27856
|
+
return getPromptJSON$h(this.props, activeGroupPromptJSON);
|
|
27857
|
+
}
|
|
27677
27858
|
render() {
|
|
27678
27859
|
// When used in the context of TranslationEditor, render the
|
|
27679
27860
|
// GradedGroup widget one below another instead of using an indicator
|
|
@@ -27911,6 +28092,33 @@ const styles$h = StyleSheet.create({
|
|
|
27911
28092
|
}
|
|
27912
28093
|
});
|
|
27913
28094
|
|
|
28095
|
+
const getPromptJSON$g = (renderProps, userInput) => {
|
|
28096
|
+
const {
|
|
28097
|
+
type,
|
|
28098
|
+
coords
|
|
28099
|
+
} = userInput;
|
|
28100
|
+
const input = {
|
|
28101
|
+
type,
|
|
28102
|
+
coords
|
|
28103
|
+
};
|
|
28104
|
+
if (userInput.type === "logarithm" || userInput.type === "exponential") {
|
|
28105
|
+
input["asymptote"] = userInput.asymptote;
|
|
28106
|
+
}
|
|
28107
|
+
return {
|
|
28108
|
+
type: "grapher",
|
|
28109
|
+
options: {
|
|
28110
|
+
availableTypes: renderProps.availableTypes,
|
|
28111
|
+
range: renderProps.graph.range,
|
|
28112
|
+
labels: renderProps.graph.labels,
|
|
28113
|
+
tickStep: renderProps.graph.step,
|
|
28114
|
+
gridStep: renderProps.graph.gridStep,
|
|
28115
|
+
snapStep: renderProps.graph.snapStep,
|
|
28116
|
+
backgroundImageUrl: renderProps.graph.backgroundImage.url
|
|
28117
|
+
},
|
|
28118
|
+
userInput: input
|
|
28119
|
+
};
|
|
28120
|
+
};
|
|
28121
|
+
|
|
27914
28122
|
/* eslint-disable one-var */
|
|
27915
28123
|
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
27916
28124
|
const Plot$1 = Graphie.Plot;
|
|
@@ -28434,7 +28642,7 @@ function getCoefficientsByType(data) {
|
|
|
28434
28642
|
throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
|
|
28435
28643
|
}
|
|
28436
28644
|
}
|
|
28437
|
-
function
|
|
28645
|
+
function scoreGrapher(userInput, rubric) {
|
|
28438
28646
|
if (userInput.type !== rubric.correct.type) {
|
|
28439
28647
|
return {
|
|
28440
28648
|
type: "points",
|
|
@@ -28583,7 +28791,7 @@ class FunctionGrapher extends React.Component {
|
|
|
28583
28791
|
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
28584
28792
|
// Always enforce that this is a function
|
|
28585
28793
|
const isFunction = _.all(this._coords(), (otherCoord, j) => {
|
|
28586
|
-
return i === j || !otherCoord || !number.equal(coord[0], otherCoord[0]);
|
|
28794
|
+
return i === j || !otherCoord || !number$1.equal(coord[0], otherCoord[0]);
|
|
28587
28795
|
});
|
|
28588
28796
|
|
|
28589
28797
|
// Evaluate this criteria before per-point
|
|
@@ -28783,6 +28991,9 @@ class Grapher extends React.Component {
|
|
|
28783
28991
|
getUserInput() {
|
|
28784
28992
|
return Grapher.getUserInputFromProps(this.props);
|
|
28785
28993
|
}
|
|
28994
|
+
getPromptJSON() {
|
|
28995
|
+
return getPromptJSON$g(this.props, this.getUserInput());
|
|
28996
|
+
}
|
|
28786
28997
|
render() {
|
|
28787
28998
|
const type = this.props.plot.type;
|
|
28788
28999
|
const coords = this.props.plot.coords;
|
|
@@ -28868,7 +29079,20 @@ var Grapher$1 = {
|
|
|
28868
29079
|
widget: Grapher,
|
|
28869
29080
|
transform: propTransform$2,
|
|
28870
29081
|
staticTransform: staticTransform$4,
|
|
28871
|
-
|
|
29082
|
+
scorer: scoreGrapher
|
|
29083
|
+
};
|
|
29084
|
+
|
|
29085
|
+
const getPromptJSON$f = rendererJSON => {
|
|
29086
|
+
if (!rendererJSON) {
|
|
29087
|
+
return {
|
|
29088
|
+
type: "group",
|
|
29089
|
+
content: "",
|
|
29090
|
+
widgets: {}
|
|
29091
|
+
};
|
|
29092
|
+
}
|
|
29093
|
+
return _extends({}, rendererJSON, {
|
|
29094
|
+
type: "group"
|
|
29095
|
+
});
|
|
28872
29096
|
};
|
|
28873
29097
|
|
|
28874
29098
|
// exports has no 'transform'
|
|
@@ -28926,17 +29150,21 @@ class Group extends React.Component {
|
|
|
28926
29150
|
var _this$rendererRef8;
|
|
28927
29151
|
return (_this$rendererRef8 = this.rendererRef) == null ? void 0 : _this$rendererRef8.getUserInput();
|
|
28928
29152
|
}
|
|
29153
|
+
getPromptJSON() {
|
|
29154
|
+
var _this$rendererRef9;
|
|
29155
|
+
return getPromptJSON$f((_this$rendererRef9 = this.rendererRef) == null ? void 0 : _this$rendererRef9.getPromptJSON());
|
|
29156
|
+
}
|
|
28929
29157
|
// Mobile API:
|
|
28930
29158
|
getInputPaths() {
|
|
28931
|
-
var _this$rendererRef$get, _this$
|
|
28932
|
-
return (_this$rendererRef$get = (_this$
|
|
29159
|
+
var _this$rendererRef$get, _this$rendererRef10;
|
|
29160
|
+
return (_this$rendererRef$get = (_this$rendererRef10 = this.rendererRef) == null ? void 0 : _this$rendererRef10.getInputPaths()) != null ? _this$rendererRef$get : [];
|
|
28933
29161
|
}
|
|
28934
29162
|
focus() {
|
|
28935
|
-
var _this$rendererRef$foc, _this$
|
|
28936
|
-
return (_this$rendererRef$foc = (_this$
|
|
29163
|
+
var _this$rendererRef$foc, _this$rendererRef11;
|
|
29164
|
+
return (_this$rendererRef$foc = (_this$rendererRef11 = this.rendererRef) == null ? void 0 : _this$rendererRef11.focus()) != null ? _this$rendererRef$foc : false;
|
|
28937
29165
|
}
|
|
28938
29166
|
render() {
|
|
28939
|
-
var _this$
|
|
29167
|
+
var _this$rendererRef12;
|
|
28940
29168
|
const apiOptions = _extends({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
28941
29169
|
// Api Rewriting to support correct onFocus/onBlur
|
|
28942
29170
|
// events for the mobile API
|
|
@@ -28972,7 +29200,7 @@ class Group extends React.Component {
|
|
|
28972
29200
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
28973
29201
|
}
|
|
28974
29202
|
};
|
|
28975
|
-
const score = (_this$
|
|
29203
|
+
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
28976
29204
|
const isValid = score && score.type !== "invalid";
|
|
28977
29205
|
const isInvalid = score && score.type === "invalid";
|
|
28978
29206
|
|
|
@@ -29022,7 +29250,11 @@ var Group$1 = {
|
|
|
29022
29250
|
isLintable: true
|
|
29023
29251
|
};
|
|
29024
29252
|
|
|
29025
|
-
|
|
29253
|
+
const getPromptJSON$e = () => {
|
|
29254
|
+
return getUnsupportedPromptJSON("iframe");
|
|
29255
|
+
};
|
|
29256
|
+
|
|
29257
|
+
function scoreIframe(userInput) {
|
|
29026
29258
|
// The iframe can tell us whether it's correct or incorrect,
|
|
29027
29259
|
// and pass an optional message
|
|
29028
29260
|
if (userInput.status === "correct") {
|
|
@@ -29098,6 +29330,9 @@ class Iframe extends React.Component {
|
|
|
29098
29330
|
message: this.props.message
|
|
29099
29331
|
};
|
|
29100
29332
|
}
|
|
29333
|
+
getPromptJSON() {
|
|
29334
|
+
return getPromptJSON$e();
|
|
29335
|
+
}
|
|
29101
29336
|
render() {
|
|
29102
29337
|
const style = {
|
|
29103
29338
|
width: String(this.props.width),
|
|
@@ -29172,7 +29407,19 @@ var Iframe$1 = {
|
|
|
29172
29407
|
widget: Iframe,
|
|
29173
29408
|
// Let's not expose it to all content creators yet
|
|
29174
29409
|
hidden: true,
|
|
29175
|
-
|
|
29410
|
+
scorer: scoreIframe
|
|
29411
|
+
};
|
|
29412
|
+
|
|
29413
|
+
const getPromptJSON$d = renderProps => {
|
|
29414
|
+
return {
|
|
29415
|
+
type: "image",
|
|
29416
|
+
options: {
|
|
29417
|
+
altText: renderProps.alt,
|
|
29418
|
+
title: renderProps.title,
|
|
29419
|
+
caption: renderProps.caption,
|
|
29420
|
+
imageUrl: renderProps.backgroundImage.url
|
|
29421
|
+
}
|
|
29422
|
+
};
|
|
29176
29423
|
};
|
|
29177
29424
|
|
|
29178
29425
|
const defaultBoxSize = 400;
|
|
@@ -29197,6 +29444,9 @@ class ImageWidget extends React.Component {
|
|
|
29197
29444
|
return change.apply(this, args);
|
|
29198
29445
|
};
|
|
29199
29446
|
}
|
|
29447
|
+
getPromptJSON() {
|
|
29448
|
+
return getPromptJSON$d(this.props);
|
|
29449
|
+
}
|
|
29200
29450
|
render() {
|
|
29201
29451
|
let image;
|
|
29202
29452
|
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
@@ -29352,8 +29602,12 @@ var Image$1 = {
|
|
|
29352
29602
|
displayName: "Image",
|
|
29353
29603
|
widget: ImageWidget,
|
|
29354
29604
|
isLintable: true,
|
|
29355
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29356
|
-
|
|
29605
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
29606
|
+
scorer: () => scoreNoop()
|
|
29607
|
+
};
|
|
29608
|
+
|
|
29609
|
+
const getPromptJSON$c = () => {
|
|
29610
|
+
return getUnsupportedPromptJSON("interaction");
|
|
29357
29611
|
};
|
|
29358
29612
|
|
|
29359
29613
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -29526,6 +29780,9 @@ class Interaction extends React.Component {
|
|
|
29526
29780
|
});
|
|
29527
29781
|
}
|
|
29528
29782
|
}
|
|
29783
|
+
getPromptJSON() {
|
|
29784
|
+
return getPromptJSON$c();
|
|
29785
|
+
}
|
|
29529
29786
|
render() {
|
|
29530
29787
|
const range = this.props.graph.range;
|
|
29531
29788
|
let labels = this.props.graph.labels;
|
|
@@ -29945,8 +30202,8 @@ var Interactive = {
|
|
|
29945
30202
|
widget: Interaction,
|
|
29946
30203
|
transform: _.identity,
|
|
29947
30204
|
hidden: true,
|
|
29948
|
-
// TODO: things that aren't interactive shouldn't need
|
|
29949
|
-
|
|
30205
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
30206
|
+
scorer: () => scoreNoop()
|
|
29950
30207
|
};
|
|
29951
30208
|
|
|
29952
30209
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -30249,6 +30506,139 @@ Graph.defaultProps = {
|
|
|
30249
30506
|
isMobile: false
|
|
30250
30507
|
};
|
|
30251
30508
|
|
|
30509
|
+
const getPromptJSON$b = (props, userInput) => {
|
|
30510
|
+
var _props$backgroundImag;
|
|
30511
|
+
return {
|
|
30512
|
+
type: "interactive-graph",
|
|
30513
|
+
options: {
|
|
30514
|
+
graph: getGraphOptionsForProps(props),
|
|
30515
|
+
backgroundImageUrl: (_props$backgroundImag = props.backgroundImage) == null ? void 0 : _props$backgroundImag.url,
|
|
30516
|
+
range: props.range,
|
|
30517
|
+
labels: props.labels
|
|
30518
|
+
},
|
|
30519
|
+
userInput: getUserInput(userInput)
|
|
30520
|
+
};
|
|
30521
|
+
};
|
|
30522
|
+
const getGraphOptionsForProps = props => {
|
|
30523
|
+
var _props$graph$startCoo, _props$graph$startCoo2;
|
|
30524
|
+
const type = props.graph.type;
|
|
30525
|
+
switch (type) {
|
|
30526
|
+
case "angle":
|
|
30527
|
+
return {
|
|
30528
|
+
type: props.graph.type,
|
|
30529
|
+
angleOffsetDegrees: props.graph.angleOffsetDeg,
|
|
30530
|
+
startCoords: props.graph.startCoords
|
|
30531
|
+
};
|
|
30532
|
+
case "circle":
|
|
30533
|
+
return {
|
|
30534
|
+
type: props.graph.type,
|
|
30535
|
+
startParams: {
|
|
30536
|
+
center: (_props$graph$startCoo = props.graph.startCoords) == null ? void 0 : _props$graph$startCoo.center,
|
|
30537
|
+
radius: (_props$graph$startCoo2 = props.graph.startCoords) == null ? void 0 : _props$graph$startCoo2.radius
|
|
30538
|
+
}
|
|
30539
|
+
};
|
|
30540
|
+
case "linear":
|
|
30541
|
+
return {
|
|
30542
|
+
type: props.graph.type,
|
|
30543
|
+
startCoords: props.graph.startCoords
|
|
30544
|
+
};
|
|
30545
|
+
case "linear-system":
|
|
30546
|
+
return {
|
|
30547
|
+
type: props.graph.type,
|
|
30548
|
+
startCoords: props.graph.startCoords
|
|
30549
|
+
};
|
|
30550
|
+
case "point":
|
|
30551
|
+
return {
|
|
30552
|
+
type: props.graph.type,
|
|
30553
|
+
numPoints: props.graph.numPoints,
|
|
30554
|
+
startCoords: props.graph.startCoords
|
|
30555
|
+
};
|
|
30556
|
+
case "polygon":
|
|
30557
|
+
return {
|
|
30558
|
+
type: props.graph.type,
|
|
30559
|
+
match: props.graph.match,
|
|
30560
|
+
numSides: props.graph.numSides,
|
|
30561
|
+
startCoords: props.graph.startCoords
|
|
30562
|
+
};
|
|
30563
|
+
case "quadratic":
|
|
30564
|
+
return {
|
|
30565
|
+
type: props.graph.type,
|
|
30566
|
+
startCoords: props.graph.startCoords
|
|
30567
|
+
};
|
|
30568
|
+
case "ray":
|
|
30569
|
+
return {
|
|
30570
|
+
type: props.graph.type,
|
|
30571
|
+
startCoords: props.graph.startCoords
|
|
30572
|
+
};
|
|
30573
|
+
case "segment":
|
|
30574
|
+
return {
|
|
30575
|
+
type: props.graph.type,
|
|
30576
|
+
numSegments: props.graph.numSegments,
|
|
30577
|
+
startCoords: props.graph.startCoords
|
|
30578
|
+
};
|
|
30579
|
+
case "sinusoid":
|
|
30580
|
+
return {
|
|
30581
|
+
type: props.graph.type,
|
|
30582
|
+
startCoords: props.graph.startCoords
|
|
30583
|
+
};
|
|
30584
|
+
case "none":
|
|
30585
|
+
return {};
|
|
30586
|
+
default:
|
|
30587
|
+
throw new UnreachableCaseError(type);
|
|
30588
|
+
}
|
|
30589
|
+
};
|
|
30590
|
+
const getUserInput = userInput => {
|
|
30591
|
+
const type = userInput.type;
|
|
30592
|
+
switch (type) {
|
|
30593
|
+
case "angle":
|
|
30594
|
+
return {
|
|
30595
|
+
coords: userInput.coords,
|
|
30596
|
+
angleOffsetDegrees: userInput.angleOffsetDeg
|
|
30597
|
+
};
|
|
30598
|
+
case "circle":
|
|
30599
|
+
return {
|
|
30600
|
+
center: userInput.center,
|
|
30601
|
+
radius: userInput.radius
|
|
30602
|
+
};
|
|
30603
|
+
case "linear":
|
|
30604
|
+
return {
|
|
30605
|
+
coords: userInput.coords
|
|
30606
|
+
};
|
|
30607
|
+
case "linear-system":
|
|
30608
|
+
return {
|
|
30609
|
+
coords: userInput.coords
|
|
30610
|
+
};
|
|
30611
|
+
case "point":
|
|
30612
|
+
return {
|
|
30613
|
+
coords: userInput.coords
|
|
30614
|
+
};
|
|
30615
|
+
case "polygon":
|
|
30616
|
+
return {
|
|
30617
|
+
coords: userInput.coords
|
|
30618
|
+
};
|
|
30619
|
+
case "quadratic":
|
|
30620
|
+
return {
|
|
30621
|
+
coords: userInput.coords
|
|
30622
|
+
};
|
|
30623
|
+
case "ray":
|
|
30624
|
+
return {
|
|
30625
|
+
coords: userInput.coords
|
|
30626
|
+
};
|
|
30627
|
+
case "segment":
|
|
30628
|
+
return {
|
|
30629
|
+
coords: userInput.coords
|
|
30630
|
+
};
|
|
30631
|
+
case "sinusoid":
|
|
30632
|
+
return {
|
|
30633
|
+
coords: userInput.coords
|
|
30634
|
+
};
|
|
30635
|
+
case "none":
|
|
30636
|
+
return {};
|
|
30637
|
+
default:
|
|
30638
|
+
throw new UnreachableCaseError(type);
|
|
30639
|
+
}
|
|
30640
|
+
};
|
|
30641
|
+
|
|
30252
30642
|
function pathBuilder() {
|
|
30253
30643
|
return new PathBuilder();
|
|
30254
30644
|
}
|
|
@@ -31313,7 +31703,7 @@ class NumberInput extends React.Component {
|
|
|
31313
31703
|
};
|
|
31314
31704
|
}
|
|
31315
31705
|
componentDidUpdate(prevProps) {
|
|
31316
|
-
if (!number.equal(this.getValue(), this.props.value)) {
|
|
31706
|
+
if (!number$1.equal(this.getValue(), this.props.value)) {
|
|
31317
31707
|
this._setValue(this.props.value, this.state.format);
|
|
31318
31708
|
}
|
|
31319
31709
|
}
|
|
@@ -31482,6 +31872,10 @@ const LegacyGrid = ({
|
|
|
31482
31872
|
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
31483
31873
|
* area.
|
|
31484
31874
|
*/
|
|
31875
|
+
/**
|
|
31876
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
31877
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
31878
|
+
*/
|
|
31485
31879
|
/**
|
|
31486
31880
|
* A "MultiItem" is an advanced Perseus item. It is rendered by the
|
|
31487
31881
|
* `MultiRenderer` and you can control the layout of individual parts of the
|
|
@@ -31578,6 +31972,10 @@ const ItemExtras = [
|
|
|
31578
31972
|
// A background image applied to various widgets.
|
|
31579
31973
|
|
|
31580
31974
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
31975
|
+
|
|
31976
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
31977
|
+
|
|
31978
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
31581
31979
|
const lockedFigureColors = {
|
|
31582
31980
|
blue: "#3D7586",
|
|
31583
31981
|
green: "#447A53",
|
|
@@ -31598,6 +31996,104 @@ const lockedFigureFillStyles = {
|
|
|
31598
31996
|
|
|
31599
31997
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
31600
31998
|
|
|
31999
|
+
/**
|
|
32000
|
+
* 44 is touch best practice and AAA compliant for WCAG
|
|
32001
|
+
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
32002
|
+
*/
|
|
32003
|
+
const TARGET_SIZE = 44;
|
|
32004
|
+
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32005
|
+
|
|
32006
|
+
// same as pointsFromNormalized in interactive-graph.tsx
|
|
32007
|
+
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
32008
|
+
// maps over [x, y]
|
|
32009
|
+
coords.map((coord, i) => {
|
|
32010
|
+
const axisRange = range[i];
|
|
32011
|
+
if (noSnap) {
|
|
32012
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
32013
|
+
}
|
|
32014
|
+
const axisStep = step[i];
|
|
32015
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
32016
|
+
const tick = Math.round(coord * nSteps);
|
|
32017
|
+
return axisRange[MIN] + axisStep * tick;
|
|
32018
|
+
}));
|
|
32019
|
+
|
|
32020
|
+
// same as normalizeCoords in interactive-graph.tsx
|
|
32021
|
+
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
32022
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
32023
|
+
}));
|
|
32024
|
+
|
|
32025
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
32026
|
+
// bounds given in `state`.
|
|
32027
|
+
function bound$1({
|
|
32028
|
+
snapStep,
|
|
32029
|
+
range,
|
|
32030
|
+
point
|
|
32031
|
+
}) {
|
|
32032
|
+
const boundingBox = inset(snapStep, range);
|
|
32033
|
+
return clampToBox(boundingBox, point);
|
|
32034
|
+
}
|
|
32035
|
+
function isUnlimitedGraphState(state) {
|
|
32036
|
+
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
32037
|
+
}
|
|
32038
|
+
|
|
32039
|
+
/**
|
|
32040
|
+
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
32041
|
+
* This way, the entire resulting string can be rendered within <TeX>
|
|
32042
|
+
* and the text outside of the $ blocks will be non-TeX text.
|
|
32043
|
+
*/
|
|
32044
|
+
function replaceOutsideTeX(mathString) {
|
|
32045
|
+
// All the information we need is in the first section,
|
|
32046
|
+
// whether it's typed as "blockmath" or "paragraph"
|
|
32047
|
+
const firstSection = SimpleMarkdown.parse(mathString)[0];
|
|
32048
|
+
|
|
32049
|
+
// If it's blockMath, the outer level has the full math content.
|
|
32050
|
+
if (firstSection.type === "blockMath") {
|
|
32051
|
+
return firstSection.content;
|
|
32052
|
+
}
|
|
32053
|
+
|
|
32054
|
+
// If it's a paragraph, we need to iterate through the sections
|
|
32055
|
+
// to look for individual math blocks.
|
|
32056
|
+
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
32057
|
+
let result = "";
|
|
32058
|
+
for (const piece of condensedNodes) {
|
|
32059
|
+
piece.type === "math" ? result += piece.content : result += `\\text{${escapeSpecialChars(piece.content)}}`;
|
|
32060
|
+
}
|
|
32061
|
+
return result;
|
|
32062
|
+
}
|
|
32063
|
+
// Helper function for replaceOutsideTeX()
|
|
32064
|
+
// Condense adjacent text nodes into a single text node
|
|
32065
|
+
function condenseTextNodes(nodes) {
|
|
32066
|
+
const result = [];
|
|
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
|
+
}
|
|
32096
|
+
|
|
31601
32097
|
function LockedLabel(props) {
|
|
31602
32098
|
const {
|
|
31603
32099
|
coord,
|
|
@@ -31610,7 +32106,7 @@ function LockedLabel(props) {
|
|
|
31610
32106
|
TeX
|
|
31611
32107
|
} = getDependencies();
|
|
31612
32108
|
|
|
31613
|
-
//
|
|
32109
|
+
// Note: The TeX component cannot be rendered within an SVG
|
|
31614
32110
|
return /*#__PURE__*/React.createElement("span", {
|
|
31615
32111
|
className: "locked-label",
|
|
31616
32112
|
style: {
|
|
@@ -31622,7 +32118,7 @@ function LockedLabel(props) {
|
|
|
31622
32118
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
31623
32119
|
},
|
|
31624
32120
|
"aria-hidden": true
|
|
31625
|
-
}, /*#__PURE__*/React.createElement(TeX, null, text));
|
|
32121
|
+
}, /*#__PURE__*/React.createElement(TeX, null, replaceOutsideTeX(text)));
|
|
31626
32122
|
}
|
|
31627
32123
|
|
|
31628
32124
|
function GraphLockedLabelsLayer(props) {
|
|
@@ -32542,46 +33038,6 @@ function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, a
|
|
|
32542
33038
|
return scaledBisector;
|
|
32543
33039
|
}
|
|
32544
33040
|
|
|
32545
|
-
/**
|
|
32546
|
-
* 44 is touch best practice and AAA compliant for WCAG
|
|
32547
|
-
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
32548
|
-
*/
|
|
32549
|
-
const TARGET_SIZE = 44;
|
|
32550
|
-
const REMOVE_BUTTON_ID = "perseus_mafs_remove_button";
|
|
32551
|
-
|
|
32552
|
-
// same as pointsFromNormalized in interactive-graph.tsx
|
|
32553
|
-
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
32554
|
-
// maps over [x, y]
|
|
32555
|
-
coords.map((coord, i) => {
|
|
32556
|
-
const axisRange = range[i];
|
|
32557
|
-
if (noSnap) {
|
|
32558
|
-
return axisRange[MIN] + size(axisRange) * coord;
|
|
32559
|
-
}
|
|
32560
|
-
const axisStep = step[i];
|
|
32561
|
-
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
32562
|
-
const tick = Math.round(coord * nSteps);
|
|
32563
|
-
return axisRange[MIN] + axisStep * tick;
|
|
32564
|
-
}));
|
|
32565
|
-
|
|
32566
|
-
// same as normalizeCoords in interactive-graph.tsx
|
|
32567
|
-
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
32568
|
-
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
32569
|
-
}));
|
|
32570
|
-
|
|
32571
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
32572
|
-
// bounds given in `state`.
|
|
32573
|
-
function bound$1({
|
|
32574
|
-
snapStep,
|
|
32575
|
-
range,
|
|
32576
|
-
point
|
|
32577
|
-
}) {
|
|
32578
|
-
const boundingBox = inset(snapStep, range);
|
|
32579
|
-
return clampToBox(boundingBox, point);
|
|
32580
|
-
}
|
|
32581
|
-
function isUnlimitedGraphState(state) {
|
|
32582
|
-
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
32583
|
-
}
|
|
32584
|
-
|
|
32585
33041
|
var isProduction = process.env.NODE_ENV === 'production';
|
|
32586
33042
|
var prefix = 'Invariant failed';
|
|
32587
33043
|
function invariant(condition, message) {
|
|
@@ -35898,7 +36354,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
35898
36354
|
|
|
35899
36355
|
const eq$1 = Util.eq;
|
|
35900
36356
|
const deepEq$2 = Util.deepEq;
|
|
35901
|
-
function
|
|
36357
|
+
function scoreInteractiveGraph(userInput, rubric) {
|
|
35902
36358
|
// None-type graphs are not graded
|
|
35903
36359
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35904
36360
|
return {
|
|
@@ -36007,7 +36463,7 @@ function interactiveGraphValidator(userInput, rubric) {
|
|
|
36007
36463
|
if (rubric.correct.match === "similar") {
|
|
36008
36464
|
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36009
36465
|
} else if (rubric.correct.match === "congruent") {
|
|
36010
|
-
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
36466
|
+
match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
|
|
36011
36467
|
} else if (rubric.correct.match === "approx") {
|
|
36012
36468
|
match = similar(guess, correct, 0.1);
|
|
36013
36469
|
} else {
|
|
@@ -36316,7 +36772,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
36316
36772
|
};
|
|
36317
36773
|
this.setTrashCanVisibility = opacity => {
|
|
36318
36774
|
const graphie = this.graphie;
|
|
36319
|
-
if (number.equal(opacity, 0)) {
|
|
36775
|
+
if (number$1.equal(opacity, 0)) {
|
|
36320
36776
|
if (this.trashCan) {
|
|
36321
36777
|
this.trashCan.remove();
|
|
36322
36778
|
this.trashCan = null;
|
|
@@ -37410,6 +37866,9 @@ class InteractiveGraph extends React.Component {
|
|
|
37410
37866
|
}
|
|
37411
37867
|
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
37412
37868
|
}
|
|
37869
|
+
getPromptJSON() {
|
|
37870
|
+
return getPromptJSON$b(this.props, this.getUserInput());
|
|
37871
|
+
}
|
|
37413
37872
|
render() {
|
|
37414
37873
|
var _this$props$apiOption;
|
|
37415
37874
|
// Mafs shim
|
|
@@ -37788,7 +38247,7 @@ var InteractiveGraph$1 = {
|
|
|
37788
38247
|
displayName: "Interactive graph (Assessments only)",
|
|
37789
38248
|
widget: InteractiveGraph,
|
|
37790
38249
|
staticTransform: staticTransform$3,
|
|
37791
|
-
|
|
38250
|
+
scorer: scoreInteractiveGraph
|
|
37792
38251
|
};
|
|
37793
38252
|
|
|
37794
38253
|
// TODO: can we use WB typography?
|
|
@@ -37800,6 +38259,32 @@ const bodyXsmallBold = {
|
|
|
37800
38259
|
lineHeight: "22px"
|
|
37801
38260
|
};
|
|
37802
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
|
+
|
|
37803
38288
|
const AnswerChoices = props => {
|
|
37804
38289
|
const {
|
|
37805
38290
|
strings
|
|
@@ -37882,59 +38367,6 @@ const styles$d = StyleSheet.create({
|
|
|
37882
38367
|
}
|
|
37883
38368
|
});
|
|
37884
38369
|
|
|
37885
|
-
// Question state for marker as result of user selected answers.
|
|
37886
|
-
|
|
37887
|
-
function scoreMarker(marker) {
|
|
37888
|
-
const score = {
|
|
37889
|
-
hasAnswers: false,
|
|
37890
|
-
isCorrect: false
|
|
37891
|
-
};
|
|
37892
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
37893
|
-
score.hasAnswers = true;
|
|
37894
|
-
}
|
|
37895
|
-
if (marker.answers.length > 0) {
|
|
37896
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
37897
|
-
// All correct answers are selected by the user.
|
|
37898
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
37899
|
-
}
|
|
37900
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
37901
|
-
// Correct as no answers should be selected by the user.
|
|
37902
|
-
score.isCorrect = true;
|
|
37903
|
-
}
|
|
37904
|
-
return score;
|
|
37905
|
-
}
|
|
37906
|
-
|
|
37907
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
37908
|
-
function labelImageValidator(userInput, rubric) {
|
|
37909
|
-
let numAnswered = 0;
|
|
37910
|
-
let numCorrect = 0;
|
|
37911
|
-
for (const marker of userInput.markers) {
|
|
37912
|
-
const score = scoreMarker(marker);
|
|
37913
|
-
if (score.hasAnswers) {
|
|
37914
|
-
numAnswered++;
|
|
37915
|
-
}
|
|
37916
|
-
if (score.isCorrect) {
|
|
37917
|
-
numCorrect++;
|
|
37918
|
-
}
|
|
37919
|
-
}
|
|
37920
|
-
|
|
37921
|
-
// We expect all question markers to be answered before grading.
|
|
37922
|
-
if (numAnswered !== userInput.markers.length) {
|
|
37923
|
-
return {
|
|
37924
|
-
type: "invalid",
|
|
37925
|
-
message: null
|
|
37926
|
-
};
|
|
37927
|
-
}
|
|
37928
|
-
return {
|
|
37929
|
-
type: "points",
|
|
37930
|
-
// Markers with no expected answers are graded as correct if user
|
|
37931
|
-
// makes no answer selection.
|
|
37932
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
37933
|
-
total: 1,
|
|
37934
|
-
message: null
|
|
37935
|
-
};
|
|
37936
|
-
}
|
|
37937
|
-
|
|
37938
38370
|
const BringToFront = {
|
|
37939
38371
|
boxShadow: `0 8px 8px ${color.offBlack64}`,
|
|
37940
38372
|
zIndex: 1000
|
|
@@ -38188,6 +38620,59 @@ const styles$b = StyleSheet.create({
|
|
|
38188
38620
|
}
|
|
38189
38621
|
});
|
|
38190
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
|
+
|
|
38191
38676
|
/**
|
|
38192
38677
|
* Represents a direction vector.
|
|
38193
38678
|
* No diagonals allowed.
|
|
@@ -38396,6 +38881,9 @@ class LabelImage extends React.Component {
|
|
|
38396
38881
|
markers
|
|
38397
38882
|
};
|
|
38398
38883
|
}
|
|
38884
|
+
getPromptJSON() {
|
|
38885
|
+
return getPromptJSON$a(this.props, this.getUserInput());
|
|
38886
|
+
}
|
|
38399
38887
|
|
|
38400
38888
|
// TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
|
|
38401
38889
|
showRationalesForCurrentlySelectedChoices(rubric) {
|
|
@@ -38752,7 +39240,7 @@ var LabelImage$1 = {
|
|
|
38752
39240
|
widget: LabelImageWithDependencies,
|
|
38753
39241
|
accessible: true,
|
|
38754
39242
|
isLintable: true,
|
|
38755
|
-
|
|
39243
|
+
scorer: scoreLabelImage
|
|
38756
39244
|
};
|
|
38757
39245
|
|
|
38758
39246
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
@@ -39464,7 +39952,23 @@ const styles$9 = StyleSheet.create({
|
|
|
39464
39952
|
}
|
|
39465
39953
|
});
|
|
39466
39954
|
|
|
39467
|
-
|
|
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) {
|
|
39468
39972
|
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
39469
39973
|
return {
|
|
39470
39974
|
type: "points",
|
|
@@ -39537,6 +40041,9 @@ class Matcher extends React.Component {
|
|
|
39537
40041
|
this.refs.right.moveOptionToIndex(option, index);
|
|
39538
40042
|
};
|
|
39539
40043
|
}
|
|
40044
|
+
getPromptJSON() {
|
|
40045
|
+
return getPromptJSON$9(this.props, this.getUserInput());
|
|
40046
|
+
}
|
|
39540
40047
|
render() {
|
|
39541
40048
|
// To minimize layout shift, we display a spinner until our math
|
|
39542
40049
|
// renderer is ready to render the math inside the matcher. To
|
|
@@ -39675,10 +40182,23 @@ var Matcher$1 = {
|
|
|
39675
40182
|
displayName: "Matcher (two column)",
|
|
39676
40183
|
widget: Matcher,
|
|
39677
40184
|
isLintable: true,
|
|
39678
|
-
|
|
40185
|
+
scorer: scoreMatcher
|
|
40186
|
+
};
|
|
40187
|
+
|
|
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
|
+
};
|
|
39679
40199
|
};
|
|
39680
40200
|
|
|
39681
|
-
function
|
|
40201
|
+
function scoreMatrix(userInput, rubric, strings) {
|
|
39682
40202
|
const solution = rubric.answers;
|
|
39683
40203
|
const supplied = userInput.answers;
|
|
39684
40204
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -39948,6 +40468,9 @@ class Matrix extends React.Component {
|
|
|
39948
40468
|
answers: this.props.answers
|
|
39949
40469
|
};
|
|
39950
40470
|
}
|
|
40471
|
+
getPromptJSON() {
|
|
40472
|
+
return getPromptJSON$8(this.props, this.getUserInput());
|
|
40473
|
+
}
|
|
39951
40474
|
render() {
|
|
39952
40475
|
// Set the input sizes through JS so we can control the size of the
|
|
39953
40476
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40141,7 +40664,11 @@ var Matrix$1 = {
|
|
|
40141
40664
|
transform: propTransform$1,
|
|
40142
40665
|
staticTransform: staticTransform$2,
|
|
40143
40666
|
isLintable: true,
|
|
40144
|
-
|
|
40667
|
+
scorer: scoreMatrix
|
|
40668
|
+
};
|
|
40669
|
+
|
|
40670
|
+
const getPromptJSON$7 = () => {
|
|
40671
|
+
return getUnsupportedPromptJSON("measurer");
|
|
40145
40672
|
};
|
|
40146
40673
|
|
|
40147
40674
|
const defaultImage = {
|
|
@@ -40208,6 +40735,9 @@ class Measurer extends React.Component {
|
|
|
40208
40735
|
});
|
|
40209
40736
|
}
|
|
40210
40737
|
}
|
|
40738
|
+
getPromptJSON() {
|
|
40739
|
+
return getPromptJSON$7();
|
|
40740
|
+
}
|
|
40211
40741
|
render() {
|
|
40212
40742
|
const image = _.extend({}, defaultImage, this.props.image);
|
|
40213
40743
|
|
|
@@ -40267,8 +40797,8 @@ var Measurer$1 = {
|
|
|
40267
40797
|
minor: 0
|
|
40268
40798
|
},
|
|
40269
40799
|
propUpgrades: propUpgrades,
|
|
40270
|
-
// TODO: things that aren't interactive shouldn't need
|
|
40271
|
-
|
|
40800
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
40801
|
+
scorer: () => scoreNoop(1)
|
|
40272
40802
|
};
|
|
40273
40803
|
|
|
40274
40804
|
/**
|
|
@@ -41157,16 +41687,31 @@ var Molecule$1 = {
|
|
|
41157
41687
|
displayName: "Molecule renderer",
|
|
41158
41688
|
hidden: true,
|
|
41159
41689
|
widget: MoleculeWidget,
|
|
41160
|
-
|
|
41690
|
+
scorer: () => scoreNoop()
|
|
41161
41691
|
};
|
|
41162
41692
|
|
|
41163
|
-
|
|
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
|
+
};
|
|
41706
|
+
};
|
|
41707
|
+
|
|
41708
|
+
function scoreNumberLine(state, rubric) {
|
|
41164
41709
|
const range = rubric.range;
|
|
41165
41710
|
const divisionRange = state.divisionRange;
|
|
41166
41711
|
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
41167
41712
|
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
41168
41713
|
const correctRel = rubric.correctRel || "eq";
|
|
41169
|
-
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41714
|
+
const correctPos = number$1.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41170
41715
|
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
41171
41716
|
|
|
41172
41717
|
// TODO: I don't think isTickCrtl is a thing anymore
|
|
@@ -41295,7 +41840,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
41295
41840
|
const x = range[0] + i * tickStep;
|
|
41296
41841
|
fractions.push(x);
|
|
41297
41842
|
}
|
|
41298
|
-
const getDenom = x => number.toFraction(x)[1];
|
|
41843
|
+
const getDenom = x => number$1.toFraction(x)[1];
|
|
41299
41844
|
const denoms = _.map(fractions, getDenom);
|
|
41300
41845
|
base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
41301
41846
|
} else {
|
|
@@ -41358,7 +41903,7 @@ class NumberLine extends React.Component {
|
|
|
41358
41903
|
let initialX = this.props.numLinePosition;
|
|
41359
41904
|
const divisionRange = this.props.divisionRange;
|
|
41360
41905
|
initialX = initialX == null ? range[0] : initialX;
|
|
41361
|
-
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;
|
|
41362
41907
|
};
|
|
41363
41908
|
this.onNumDivisionsChange = (numDivisions, cb) => {
|
|
41364
41909
|
const divRange = this.props.divisionRange.slice();
|
|
@@ -41461,7 +42006,7 @@ class NumberLine extends React.Component {
|
|
|
41461
42006
|
const right = props.range[1];
|
|
41462
42007
|
const snapX = props.tickStep / props.snapDivisions;
|
|
41463
42008
|
let x = bound(numLinePosition, left, right);
|
|
41464
|
-
x = left + number.roundTo(x - left, snapX);
|
|
42009
|
+
x = left + number$1.roundTo(x - left, snapX);
|
|
41465
42010
|
assert$1(_.isFinite(x));
|
|
41466
42011
|
return x;
|
|
41467
42012
|
};
|
|
@@ -41628,6 +42173,9 @@ class NumberLine extends React.Component {
|
|
|
41628
42173
|
divisionRange: this.props.divisionRange
|
|
41629
42174
|
};
|
|
41630
42175
|
}
|
|
42176
|
+
getPromptJSON() {
|
|
42177
|
+
return getPromptJSON$6(this.props, this.getUserInput());
|
|
42178
|
+
}
|
|
41631
42179
|
render() {
|
|
41632
42180
|
const {
|
|
41633
42181
|
strings
|
|
@@ -41747,10 +42295,22 @@ var NumberLine$1 = {
|
|
|
41747
42295
|
widget: NumberLine,
|
|
41748
42296
|
transform: numberLineTransform,
|
|
41749
42297
|
staticTransform: staticTransform$1,
|
|
41750
|
-
|
|
42298
|
+
scorer: scoreNumberLine
|
|
42299
|
+
};
|
|
42300
|
+
|
|
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
|
+
};
|
|
41751
42311
|
};
|
|
41752
42312
|
|
|
41753
|
-
function
|
|
42313
|
+
function scoreOrderer(userInput, rubric) {
|
|
41754
42314
|
if (userInput.current.length === 0) {
|
|
41755
42315
|
return {
|
|
41756
42316
|
type: "invalid",
|
|
@@ -42194,6 +42754,9 @@ class Orderer extends React.Component {
|
|
|
42194
42754
|
})
|
|
42195
42755
|
};
|
|
42196
42756
|
}
|
|
42757
|
+
getPromptJSON() {
|
|
42758
|
+
return getPromptJSON$5(this.props, this.getUserInput());
|
|
42759
|
+
}
|
|
42197
42760
|
render() {
|
|
42198
42761
|
// This is the card we are currently dragging
|
|
42199
42762
|
const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
|
|
@@ -42312,7 +42875,7 @@ var Orderer$1 = {
|
|
|
42312
42875
|
hidden: true,
|
|
42313
42876
|
widget: Orderer,
|
|
42314
42877
|
isLintable: true,
|
|
42315
|
-
|
|
42878
|
+
scorer: scoreOrderer
|
|
42316
42879
|
};
|
|
42317
42880
|
|
|
42318
42881
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -42361,8 +42924,12 @@ var PassageRefTarget$1 = {
|
|
|
42361
42924
|
minor: 0
|
|
42362
42925
|
},
|
|
42363
42926
|
isLintable: true,
|
|
42364
|
-
// TODO: things that aren't interactive shouldn't need
|
|
42365
|
-
|
|
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");
|
|
42366
42933
|
};
|
|
42367
42934
|
|
|
42368
42935
|
/**
|
|
@@ -42420,6 +42987,9 @@ class PhetSimulation extends React.Component {
|
|
|
42420
42987
|
await this.updateSimState(this.props.url);
|
|
42421
42988
|
}
|
|
42422
42989
|
}
|
|
42990
|
+
getPromptJSON() {
|
|
42991
|
+
return getPromptJSON$4();
|
|
42992
|
+
}
|
|
42423
42993
|
async updateSimState(urlString) {
|
|
42424
42994
|
const url = makeSafeUrl(urlString, this.locale);
|
|
42425
42995
|
if (url === null) {
|
|
@@ -42568,10 +43138,14 @@ var PhetSimulation$1 = {
|
|
|
42568
43138
|
isLintable: true
|
|
42569
43139
|
};
|
|
42570
43140
|
|
|
43141
|
+
const getPromptJSON$3 = () => {
|
|
43142
|
+
return getUnsupportedPromptJSON("plotter");
|
|
43143
|
+
};
|
|
43144
|
+
|
|
42571
43145
|
const {
|
|
42572
43146
|
deepEq: deepEq$1
|
|
42573
43147
|
} = Util;
|
|
42574
|
-
function
|
|
43148
|
+
function scorePlotter(userInput, rubric) {
|
|
42575
43149
|
if (deepEq$1(userInput, rubric.starting)) {
|
|
42576
43150
|
return {
|
|
42577
43151
|
type: "invalid",
|
|
@@ -43344,6 +43918,9 @@ class Plotter extends React.Component {
|
|
|
43344
43918
|
getUserInput() {
|
|
43345
43919
|
return this.state.values;
|
|
43346
43920
|
}
|
|
43921
|
+
getPromptJSON() {
|
|
43922
|
+
return getPromptJSON$3();
|
|
43923
|
+
}
|
|
43347
43924
|
render() {
|
|
43348
43925
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
43349
43926
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -43384,7 +43961,11 @@ var Plotter$1 = {
|
|
|
43384
43961
|
hidden: true,
|
|
43385
43962
|
widget: Plotter,
|
|
43386
43963
|
staticTransform: staticTransform,
|
|
43387
|
-
|
|
43964
|
+
scorer: scorePlotter
|
|
43965
|
+
};
|
|
43966
|
+
|
|
43967
|
+
const getPromptJSON$2 = () => {
|
|
43968
|
+
return getUnsupportedPromptJSON("python-program");
|
|
43388
43969
|
};
|
|
43389
43970
|
|
|
43390
43971
|
/**
|
|
@@ -43406,6 +43987,9 @@ class PythonProgram extends React.Component {
|
|
|
43406
43987
|
getDOMNodeForPath(path) {
|
|
43407
43988
|
return null;
|
|
43408
43989
|
}
|
|
43990
|
+
getPromptJSON() {
|
|
43991
|
+
return getPromptJSON$2();
|
|
43992
|
+
}
|
|
43409
43993
|
render() {
|
|
43410
43994
|
const url = getUrlFromProgramID(this.props.programID);
|
|
43411
43995
|
const iframeStyle = {
|
|
@@ -43443,7 +44027,17 @@ var PythonProgram$1 = {
|
|
|
43443
44027
|
widget: PythonProgram
|
|
43444
44028
|
};
|
|
43445
44029
|
|
|
43446
|
-
|
|
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) {
|
|
43447
44041
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43448
44042
|
// prevents the "Check" button from becoming active. We want the user
|
|
43449
44043
|
// to make a change before trying to move forward. This makes an
|
|
@@ -43515,6 +44109,9 @@ class Sorter extends React.Component {
|
|
|
43515
44109
|
changed: this.state.changed
|
|
43516
44110
|
};
|
|
43517
44111
|
}
|
|
44112
|
+
getPromptJSON() {
|
|
44113
|
+
return getPromptJSON$1(this.getUserInput());
|
|
44114
|
+
}
|
|
43518
44115
|
render() {
|
|
43519
44116
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
43520
44117
|
const {
|
|
@@ -43549,10 +44146,10 @@ var Sorter$1 = {
|
|
|
43549
44146
|
displayName: "Sorter",
|
|
43550
44147
|
widget: Sorter,
|
|
43551
44148
|
isLintable: true,
|
|
43552
|
-
|
|
44149
|
+
scorer: scoreSorter
|
|
43553
44150
|
};
|
|
43554
44151
|
|
|
43555
|
-
function
|
|
44152
|
+
function scoreTable(state, rubric, strings) {
|
|
43556
44153
|
const filterNonEmpty = function filterNonEmpty(table) {
|
|
43557
44154
|
return _.filter(table, function (row) {
|
|
43558
44155
|
// Check if row has a cell that is nonempty
|
|
@@ -43829,7 +44426,11 @@ var Table$1 = {
|
|
|
43829
44426
|
transform: propTransform,
|
|
43830
44427
|
hidden: true,
|
|
43831
44428
|
isLintable: true,
|
|
43832
|
-
|
|
44429
|
+
scorer: scoreTable
|
|
44430
|
+
};
|
|
44431
|
+
|
|
44432
|
+
const getPromptJSON = () => {
|
|
44433
|
+
return getUnsupportedPromptJSON("video");
|
|
43833
44434
|
};
|
|
43834
44435
|
|
|
43835
44436
|
/**
|
|
@@ -43925,6 +44526,9 @@ class Video extends React.Component {
|
|
|
43925
44526
|
return change.apply(this, args);
|
|
43926
44527
|
};
|
|
43927
44528
|
}
|
|
44529
|
+
getPromptJSON() {
|
|
44530
|
+
return getPromptJSON();
|
|
44531
|
+
}
|
|
43928
44532
|
render() {
|
|
43929
44533
|
const {
|
|
43930
44534
|
InitialRequestUrl
|
|
@@ -43985,8 +44589,8 @@ var Video$1 = {
|
|
|
43985
44589
|
defaultAlignment: "block",
|
|
43986
44590
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
43987
44591
|
widget: Video,
|
|
43988
|
-
// TODO: things that aren't interactive shouldn't need
|
|
43989
|
-
|
|
44592
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44593
|
+
scorer: () => scoreNoop()
|
|
43990
44594
|
};
|
|
43991
44595
|
|
|
43992
44596
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -43996,41 +44600,16 @@ var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Defin
|
|
|
43996
44600
|
* This should be called by all clients, specifying whether extra widgets are
|
|
43997
44601
|
* needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
|
|
43998
44602
|
* call it multiple times.
|
|
43999
|
-
*
|
|
44000
|
-
* skipMathJax:
|
|
44001
|
-
* If false/undefined, MathJax will be configured, and the
|
|
44002
|
-
* promise will wait for MathJax to load (if it hasn't already).
|
|
44003
44603
|
*/
|
|
44004
|
-
const init = function init(
|
|
44604
|
+
const init = function init() {
|
|
44005
44605
|
registerWidgets(basicWidgets);
|
|
44006
44606
|
registerWidgets(extraWidgets);
|
|
44007
44607
|
replaceDeprecatedWidgets();
|
|
44008
|
-
|
|
44009
|
-
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
44010
|
-
const skipMathJax = options.skipMathJax;
|
|
44011
|
-
if (skipMathJax) {
|
|
44012
|
-
// @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
|
|
44013
|
-
return Promise.resolve();
|
|
44014
|
-
}
|
|
44015
|
-
return new Promise((resolve, reject) => {
|
|
44016
|
-
MathJax.Hub.Config({
|
|
44017
|
-
messageStyle: "none",
|
|
44018
|
-
skipStartupTypeset: "none",
|
|
44019
|
-
"HTML-CSS": {
|
|
44020
|
-
availableFonts: ["TeX"],
|
|
44021
|
-
imageFont: null,
|
|
44022
|
-
scale: 100,
|
|
44023
|
-
showMathMenu: false
|
|
44024
|
-
}
|
|
44025
|
-
});
|
|
44026
|
-
MathJax.Hub.Configured();
|
|
44027
|
-
MathJax.Hub.Queue(resolve);
|
|
44028
|
-
});
|
|
44029
44608
|
};
|
|
44030
44609
|
|
|
44031
44610
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44032
44611
|
const libName = "@khanacademy/perseus";
|
|
44033
|
-
const libVersion = "41.
|
|
44612
|
+
const libVersion = "41.3.0";
|
|
44034
44613
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44035
44614
|
|
|
44036
44615
|
const apiVersion = {
|
|
@@ -44767,6 +45346,9 @@ class ServerItemRenderer extends React.Component {
|
|
|
44767
45346
|
getNumHints() {
|
|
44768
45347
|
return this.props.item.hints.length;
|
|
44769
45348
|
}
|
|
45349
|
+
getPromptJSON() {
|
|
45350
|
+
return this.questionRenderer.getPromptJSON();
|
|
45351
|
+
}
|
|
44770
45352
|
|
|
44771
45353
|
/**
|
|
44772
45354
|
* Grades the item.
|
|
@@ -45414,7 +45996,7 @@ function treeToItem(node) {
|
|
|
45414
45996
|
};
|
|
45415
45997
|
}
|
|
45416
45998
|
|
|
45417
|
-
var isObject = function isObject(obj) {
|
|
45999
|
+
var isObject$1 = function isObject(obj) {
|
|
45418
46000
|
return obj === Object(obj);
|
|
45419
46001
|
};
|
|
45420
46002
|
var merge = function merge() {
|
|
@@ -45430,7 +46012,7 @@ var merge = function merge() {
|
|
|
45430
46012
|
return obj;
|
|
45431
46013
|
};
|
|
45432
46014
|
var clone = function clone(obj) {
|
|
45433
|
-
if (!isObject(obj)) {
|
|
46015
|
+
if (!isObject$1(obj)) {
|
|
45434
46016
|
return obj;
|
|
45435
46017
|
}
|
|
45436
46018
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45446,7 +46028,7 @@ var clone = function clone(obj) {
|
|
|
45446
46028
|
var dispatch = function dispatch(x) {
|
|
45447
46029
|
if (Array.isArray(x)) {
|
|
45448
46030
|
return arr;
|
|
45449
|
-
} else if (isObject(x)) {
|
|
46031
|
+
} else if (isObject$1(x)) {
|
|
45450
46032
|
return obj;
|
|
45451
46033
|
} else if (typeof x === "string") {
|
|
45452
46034
|
return str;
|
|
@@ -47367,6 +47949,690 @@ function buildTestData(testObject) {
|
|
|
47367
47949
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47368
47950
|
}
|
|
47369
47951
|
|
|
47952
|
+
function success(value) {
|
|
47953
|
+
return {
|
|
47954
|
+
type: "success",
|
|
47955
|
+
value
|
|
47956
|
+
};
|
|
47957
|
+
}
|
|
47958
|
+
function failure(detail) {
|
|
47959
|
+
return {
|
|
47960
|
+
type: "failure",
|
|
47961
|
+
detail
|
|
47962
|
+
};
|
|
47963
|
+
}
|
|
47964
|
+
function isFailure(result) {
|
|
47965
|
+
return result.type === "failure";
|
|
47966
|
+
}
|
|
47967
|
+
function isSuccess(result) {
|
|
47968
|
+
return result.type === "success";
|
|
47969
|
+
}
|
|
47970
|
+
|
|
47971
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
47972
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
47973
|
+
function all(results, combineFailureDetails = a => a) {
|
|
47974
|
+
const values = [];
|
|
47975
|
+
const failureDetails = [];
|
|
47976
|
+
for (const result of results) {
|
|
47977
|
+
if (result.type === "success") {
|
|
47978
|
+
values.push(result.value);
|
|
47979
|
+
} else {
|
|
47980
|
+
failureDetails.push(result.detail);
|
|
47981
|
+
}
|
|
47982
|
+
}
|
|
47983
|
+
if (failureDetails.length > 0) {
|
|
47984
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
47985
|
+
}
|
|
47986
|
+
return success(values);
|
|
47987
|
+
}
|
|
47988
|
+
|
|
47989
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
47990
|
+
|
|
47991
|
+
function array(elementParser) {
|
|
47992
|
+
return (rawValue, ctx) => {
|
|
47993
|
+
if (!Array.isArray(rawValue)) {
|
|
47994
|
+
return ctx.failure("array", rawValue);
|
|
47995
|
+
}
|
|
47996
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
47997
|
+
return all(elementResults, concat);
|
|
47998
|
+
};
|
|
47999
|
+
}
|
|
48000
|
+
function concat(a, b) {
|
|
48001
|
+
return [...a, ...b];
|
|
48002
|
+
}
|
|
48003
|
+
|
|
48004
|
+
function boolean(rawValue, ctx) {
|
|
48005
|
+
if (typeof rawValue === "boolean") {
|
|
48006
|
+
return ctx.success(rawValue);
|
|
48007
|
+
}
|
|
48008
|
+
return ctx.failure("boolean", rawValue);
|
|
48009
|
+
}
|
|
48010
|
+
|
|
48011
|
+
function constant(acceptedValue) {
|
|
48012
|
+
return (rawValue, ctx) => {
|
|
48013
|
+
if (rawValue !== acceptedValue) {
|
|
48014
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
48015
|
+
}
|
|
48016
|
+
return ctx.success(acceptedValue);
|
|
48017
|
+
};
|
|
48018
|
+
}
|
|
48019
|
+
|
|
48020
|
+
function enumeration(...acceptedValues) {
|
|
48021
|
+
return (rawValue, ctx) => {
|
|
48022
|
+
if (typeof rawValue === "string") {
|
|
48023
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
48024
|
+
if (index > -1) {
|
|
48025
|
+
return ctx.success(acceptedValues[index]);
|
|
48026
|
+
}
|
|
48027
|
+
}
|
|
48028
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
48029
|
+
return ctx.failure(expected, rawValue);
|
|
48030
|
+
};
|
|
48031
|
+
}
|
|
48032
|
+
|
|
48033
|
+
function isObject(x) {
|
|
48034
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
48035
|
+
}
|
|
48036
|
+
|
|
48037
|
+
function nullable(parseValue) {
|
|
48038
|
+
return (rawValue, ctx) => {
|
|
48039
|
+
if (rawValue === null) {
|
|
48040
|
+
return ctx.success(rawValue);
|
|
48041
|
+
}
|
|
48042
|
+
return parseValue(rawValue, ctx);
|
|
48043
|
+
};
|
|
48044
|
+
}
|
|
48045
|
+
|
|
48046
|
+
const number = (rawValue, ctx) => {
|
|
48047
|
+
if (typeof rawValue === "number") {
|
|
48048
|
+
return ctx.success(rawValue);
|
|
48049
|
+
}
|
|
48050
|
+
return ctx.failure("number", rawValue);
|
|
48051
|
+
};
|
|
48052
|
+
|
|
48053
|
+
function object(schema) {
|
|
48054
|
+
return (rawValue, ctx) => {
|
|
48055
|
+
if (!isObject(rawValue)) {
|
|
48056
|
+
return ctx.failure("object", rawValue);
|
|
48057
|
+
}
|
|
48058
|
+
const ret = _extends({}, rawValue);
|
|
48059
|
+
const mismatches = [];
|
|
48060
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
48061
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
48062
|
+
if (isSuccess(result)) {
|
|
48063
|
+
ret[prop] = result.value;
|
|
48064
|
+
} else {
|
|
48065
|
+
mismatches.push(...result.detail);
|
|
48066
|
+
}
|
|
48067
|
+
}
|
|
48068
|
+
if (mismatches.length > 0) {
|
|
48069
|
+
return failure(mismatches);
|
|
48070
|
+
}
|
|
48071
|
+
return ctx.success(ret);
|
|
48072
|
+
};
|
|
48073
|
+
}
|
|
48074
|
+
|
|
48075
|
+
function optional(parseValue) {
|
|
48076
|
+
return (rawValue, ctx) => {
|
|
48077
|
+
if (rawValue === undefined) {
|
|
48078
|
+
return ctx.success(rawValue);
|
|
48079
|
+
}
|
|
48080
|
+
return parseValue(rawValue, ctx);
|
|
48081
|
+
};
|
|
48082
|
+
}
|
|
48083
|
+
|
|
48084
|
+
function pair(parseA, parseB) {
|
|
48085
|
+
return (rawValue, ctx) => {
|
|
48086
|
+
if (!Array.isArray(rawValue)) {
|
|
48087
|
+
return ctx.failure("array", rawValue);
|
|
48088
|
+
}
|
|
48089
|
+
if (rawValue.length !== 2) {
|
|
48090
|
+
return ctx.failure("array of length 2", rawValue);
|
|
48091
|
+
}
|
|
48092
|
+
const [rawA, rawB] = rawValue;
|
|
48093
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
48094
|
+
if (isFailure(resultA)) {
|
|
48095
|
+
return resultA;
|
|
48096
|
+
}
|
|
48097
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
48098
|
+
if (isFailure(resultB)) {
|
|
48099
|
+
return resultB;
|
|
48100
|
+
}
|
|
48101
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
48102
|
+
};
|
|
48103
|
+
}
|
|
48104
|
+
|
|
48105
|
+
function record(parseKey, parseValue) {
|
|
48106
|
+
return (rawValue, ctx) => {
|
|
48107
|
+
if (!isObject(rawValue)) {
|
|
48108
|
+
return ctx.failure("object", rawValue);
|
|
48109
|
+
}
|
|
48110
|
+
const result = {};
|
|
48111
|
+
const mismatches = [];
|
|
48112
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
48113
|
+
const entryCtx = ctx.forSubtree(key);
|
|
48114
|
+
const keyResult = parseKey(key, entryCtx);
|
|
48115
|
+
if (isFailure(keyResult)) {
|
|
48116
|
+
mismatches.push(...keyResult.detail);
|
|
48117
|
+
}
|
|
48118
|
+
const valueResult = parseValue(value, entryCtx);
|
|
48119
|
+
if (isFailure(valueResult)) {
|
|
48120
|
+
mismatches.push(...valueResult.detail);
|
|
48121
|
+
}
|
|
48122
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
48123
|
+
result[keyResult.value] = valueResult.value;
|
|
48124
|
+
}
|
|
48125
|
+
}
|
|
48126
|
+
if (mismatches.length > 0) {
|
|
48127
|
+
return failure(mismatches);
|
|
48128
|
+
}
|
|
48129
|
+
return ctx.success(result);
|
|
48130
|
+
};
|
|
48131
|
+
}
|
|
48132
|
+
|
|
48133
|
+
const string = (rawValue, ctx) => {
|
|
48134
|
+
if (typeof rawValue === "string") {
|
|
48135
|
+
return ctx.success(rawValue);
|
|
48136
|
+
}
|
|
48137
|
+
return ctx.failure("string", rawValue);
|
|
48138
|
+
};
|
|
48139
|
+
|
|
48140
|
+
function trio(parseA, parseB, parseC) {
|
|
48141
|
+
return (rawValue, ctx) => {
|
|
48142
|
+
if (!Array.isArray(rawValue)) {
|
|
48143
|
+
return ctx.failure("array", rawValue);
|
|
48144
|
+
}
|
|
48145
|
+
if (rawValue.length !== 3) {
|
|
48146
|
+
return ctx.failure("array of length 3", rawValue);
|
|
48147
|
+
}
|
|
48148
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
48149
|
+
if (isFailure(resultA)) {
|
|
48150
|
+
return resultA;
|
|
48151
|
+
}
|
|
48152
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
48153
|
+
if (isFailure(resultB)) {
|
|
48154
|
+
return resultB;
|
|
48155
|
+
}
|
|
48156
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
48157
|
+
if (isFailure(resultC)) {
|
|
48158
|
+
return resultC;
|
|
48159
|
+
}
|
|
48160
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
48161
|
+
};
|
|
48162
|
+
}
|
|
48163
|
+
|
|
48164
|
+
function union(parseBranch) {
|
|
48165
|
+
return new UnionBuilder(parseBranch);
|
|
48166
|
+
}
|
|
48167
|
+
class UnionBuilder {
|
|
48168
|
+
constructor(parser) {
|
|
48169
|
+
this.parser = parser;
|
|
48170
|
+
}
|
|
48171
|
+
or(newBranch) {
|
|
48172
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
48173
|
+
}
|
|
48174
|
+
}
|
|
48175
|
+
function either(parseA, parseB) {
|
|
48176
|
+
return (rawValue, ctx) => {
|
|
48177
|
+
const resultA = parseA(rawValue, ctx);
|
|
48178
|
+
if (isSuccess(resultA)) {
|
|
48179
|
+
return resultA;
|
|
48180
|
+
}
|
|
48181
|
+
return parseB(rawValue, ctx);
|
|
48182
|
+
};
|
|
48183
|
+
}
|
|
48184
|
+
|
|
48185
|
+
function parseWidget(parseType, parseOptions) {
|
|
48186
|
+
return object({
|
|
48187
|
+
type: parseType,
|
|
48188
|
+
static: optional(boolean),
|
|
48189
|
+
graded: optional(boolean),
|
|
48190
|
+
alignment: optional(string),
|
|
48191
|
+
options: parseOptions,
|
|
48192
|
+
key: optional(number),
|
|
48193
|
+
version: object({
|
|
48194
|
+
major: number,
|
|
48195
|
+
minor: number
|
|
48196
|
+
})
|
|
48197
|
+
});
|
|
48198
|
+
}
|
|
48199
|
+
|
|
48200
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
48201
|
+
items: array(string),
|
|
48202
|
+
categories: array(string),
|
|
48203
|
+
randomizeItems: boolean,
|
|
48204
|
+
static: boolean,
|
|
48205
|
+
values: array(number),
|
|
48206
|
+
highlightLint: optional(boolean),
|
|
48207
|
+
linterContext: optional(object({
|
|
48208
|
+
contentType: string,
|
|
48209
|
+
paths: array(string),
|
|
48210
|
+
stack: array(string)
|
|
48211
|
+
}))
|
|
48212
|
+
}));
|
|
48213
|
+
|
|
48214
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
48215
|
+
programID: string,
|
|
48216
|
+
programType: any,
|
|
48217
|
+
settings: array(object({
|
|
48218
|
+
name: string,
|
|
48219
|
+
value: string
|
|
48220
|
+
})),
|
|
48221
|
+
showEditor: boolean,
|
|
48222
|
+
showButtons: boolean,
|
|
48223
|
+
width: number,
|
|
48224
|
+
height: number,
|
|
48225
|
+
static: boolean
|
|
48226
|
+
}));
|
|
48227
|
+
|
|
48228
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
48229
|
+
togglePrompt: string,
|
|
48230
|
+
definition: string,
|
|
48231
|
+
static: boolean
|
|
48232
|
+
}));
|
|
48233
|
+
|
|
48234
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
48235
|
+
placeholder: string,
|
|
48236
|
+
static: boolean,
|
|
48237
|
+
choices: array(object({
|
|
48238
|
+
content: string,
|
|
48239
|
+
correct: boolean
|
|
48240
|
+
}))
|
|
48241
|
+
}));
|
|
48242
|
+
|
|
48243
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
48244
|
+
showPrompt: string,
|
|
48245
|
+
hidePrompt: string,
|
|
48246
|
+
explanation: string,
|
|
48247
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
48248
|
+
// referenced before it is defined. There is an import cycle between
|
|
48249
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
48250
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
48251
|
+
static: boolean
|
|
48252
|
+
}));
|
|
48253
|
+
|
|
48254
|
+
const parseAnswerForm = object({
|
|
48255
|
+
value: string,
|
|
48256
|
+
form: boolean,
|
|
48257
|
+
simplify: boolean,
|
|
48258
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
48259
|
+
key: optional(string)
|
|
48260
|
+
});
|
|
48261
|
+
const parseExpressionWidget = parseWidget(constant("expression"), object({
|
|
48262
|
+
answerForms: array(parseAnswerForm),
|
|
48263
|
+
functions: array(string),
|
|
48264
|
+
times: boolean,
|
|
48265
|
+
visibleLabel: optional(string),
|
|
48266
|
+
ariaLabel: optional(string),
|
|
48267
|
+
buttonSets: array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")),
|
|
48268
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
48269
|
+
}));
|
|
48270
|
+
|
|
48271
|
+
const parsePerseusImageBackground = object({
|
|
48272
|
+
url: optional(nullable(string)),
|
|
48273
|
+
width: optional(number),
|
|
48274
|
+
height: optional(number),
|
|
48275
|
+
top: optional(number),
|
|
48276
|
+
left: optional(number),
|
|
48277
|
+
bottom: optional(number),
|
|
48278
|
+
// TODO(benchristel): convert scale to a number
|
|
48279
|
+
scale: optional(union(number).or(string).parser)
|
|
48280
|
+
});
|
|
48281
|
+
|
|
48282
|
+
// Used to represent 2-D points and ranges
|
|
48283
|
+
const pairOfNumbers = pair(number, number);
|
|
48284
|
+
const parsePerseusGraphTypeAngle = object({
|
|
48285
|
+
type: constant("angle"),
|
|
48286
|
+
showAngles: optional(boolean),
|
|
48287
|
+
allowReflexAngles: optional(boolean),
|
|
48288
|
+
angleOffsetDeg: optional(number),
|
|
48289
|
+
snapDegrees: optional(number),
|
|
48290
|
+
match: optional(constant("congruent")),
|
|
48291
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48292
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
48293
|
+
});
|
|
48294
|
+
const parsePerseusGraphTypeCircle = object({
|
|
48295
|
+
type: constant("circle"),
|
|
48296
|
+
center: optional(pairOfNumbers),
|
|
48297
|
+
radius: optional(number),
|
|
48298
|
+
startCoords: optional(object({
|
|
48299
|
+
center: pairOfNumbers,
|
|
48300
|
+
radius: number
|
|
48301
|
+
})),
|
|
48302
|
+
// TODO: remove coord? it's legacy.
|
|
48303
|
+
coord: optional(pairOfNumbers)
|
|
48304
|
+
});
|
|
48305
|
+
const parsePerseusGraphTypeLinear = object({
|
|
48306
|
+
type: constant("linear"),
|
|
48307
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48308
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48309
|
+
// TODO: remove coord? it's legacy.
|
|
48310
|
+
coord: optional(pairOfNumbers)
|
|
48311
|
+
});
|
|
48312
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
48313
|
+
type: constant("linear-system"),
|
|
48314
|
+
// TODO(benchristel): default coords to empty array?
|
|
48315
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48316
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48317
|
+
// TODO: remove coord? it's legacy.
|
|
48318
|
+
coord: optional(pairOfNumbers)
|
|
48319
|
+
});
|
|
48320
|
+
const parsePerseusGraphTypeNone = object({
|
|
48321
|
+
type: constant("none")
|
|
48322
|
+
});
|
|
48323
|
+
const parsePerseusGraphTypePoint = object({
|
|
48324
|
+
type: constant("point"),
|
|
48325
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
48326
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48327
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48328
|
+
// TODO: remove coord? it's legacy.
|
|
48329
|
+
coord: optional(pairOfNumbers)
|
|
48330
|
+
});
|
|
48331
|
+
const parsePerseusGraphTypePolygon = object({
|
|
48332
|
+
type: constant("polygon"),
|
|
48333
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
48334
|
+
showAngles: optional(boolean),
|
|
48335
|
+
showSides: optional(boolean),
|
|
48336
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
48337
|
+
match: optional(enumeration("similar", "congruent", "approx")),
|
|
48338
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48339
|
+
// TODO: remove coord? it's legacy.
|
|
48340
|
+
coord: optional(pairOfNumbers)
|
|
48341
|
+
});
|
|
48342
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
48343
|
+
type: constant("quadratic"),
|
|
48344
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
48345
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
48346
|
+
// TODO: remove coord? it's legacy.
|
|
48347
|
+
coord: optional(pairOfNumbers)
|
|
48348
|
+
});
|
|
48349
|
+
const parsePerseusGraphTypeRay = object({
|
|
48350
|
+
type: constant("ray"),
|
|
48351
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
48352
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
48353
|
+
// TODO: remove coord? it's legacy.
|
|
48354
|
+
coord: optional(pairOfNumbers)
|
|
48355
|
+
});
|
|
48356
|
+
const parsePerseusGraphTypeSegment = object({
|
|
48357
|
+
type: constant("segment"),
|
|
48358
|
+
// TODO(benchristel): default numSegments?
|
|
48359
|
+
numSegments: optional(number),
|
|
48360
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
48361
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
48362
|
+
// TODO: remove coord? it's legacy.
|
|
48363
|
+
coord: optional(pairOfNumbers)
|
|
48364
|
+
});
|
|
48365
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
48366
|
+
type: constant("sinusoid"),
|
|
48367
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
48368
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
48369
|
+
// TODO: remove coord? it's legacy.
|
|
48370
|
+
coord: optional(pairOfNumbers)
|
|
48371
|
+
});
|
|
48372
|
+
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;
|
|
48373
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
48374
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
48375
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
48376
|
+
const parseLockedLabelType = object({
|
|
48377
|
+
type: constant("label"),
|
|
48378
|
+
coord: pairOfNumbers,
|
|
48379
|
+
text: string,
|
|
48380
|
+
color: parseLockedFigureColor,
|
|
48381
|
+
size: enumeration("small", "medium", "large")
|
|
48382
|
+
});
|
|
48383
|
+
const parseLockedPointType = object({
|
|
48384
|
+
type: constant("point"),
|
|
48385
|
+
coord: pairOfNumbers,
|
|
48386
|
+
color: parseLockedFigureColor,
|
|
48387
|
+
filled: boolean,
|
|
48388
|
+
// TODO(benchristel): default labels to empty array?
|
|
48389
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48390
|
+
ariaLabel: optional(string)
|
|
48391
|
+
});
|
|
48392
|
+
const parseLockedLineType = object({
|
|
48393
|
+
type: constant("line"),
|
|
48394
|
+
kind: enumeration("line", "ray", "segment"),
|
|
48395
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
48396
|
+
color: parseLockedFigureColor,
|
|
48397
|
+
lineStyle: parseLockedLineStyle,
|
|
48398
|
+
showPoint1: boolean,
|
|
48399
|
+
showPoint2: boolean,
|
|
48400
|
+
// TODO(benchristel): default labels to empty array?
|
|
48401
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48402
|
+
ariaLabel: optional(string)
|
|
48403
|
+
});
|
|
48404
|
+
const parseLockedVectorType = object({
|
|
48405
|
+
type: constant("vector"),
|
|
48406
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
48407
|
+
color: parseLockedFigureColor,
|
|
48408
|
+
// TODO(benchristel): default labels to empty array?
|
|
48409
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48410
|
+
ariaLabel: optional(string)
|
|
48411
|
+
});
|
|
48412
|
+
const parseLockedEllipseType = object({
|
|
48413
|
+
type: constant("ellipse"),
|
|
48414
|
+
center: pairOfNumbers,
|
|
48415
|
+
radius: pairOfNumbers,
|
|
48416
|
+
angle: number,
|
|
48417
|
+
color: parseLockedFigureColor,
|
|
48418
|
+
fillStyle: parseLockedFigureFillType,
|
|
48419
|
+
strokeStyle: parseLockedLineStyle,
|
|
48420
|
+
// TODO(benchristel): default labels to empty array?
|
|
48421
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48422
|
+
ariaLabel: optional(string)
|
|
48423
|
+
});
|
|
48424
|
+
const parseLockedPolygonType = object({
|
|
48425
|
+
type: constant("polygon"),
|
|
48426
|
+
points: array(pairOfNumbers),
|
|
48427
|
+
color: parseLockedFigureColor,
|
|
48428
|
+
showVertices: boolean,
|
|
48429
|
+
fillStyle: parseLockedFigureFillType,
|
|
48430
|
+
strokeStyle: parseLockedLineStyle,
|
|
48431
|
+
// TODO(benchristel): default labels to empty array?
|
|
48432
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48433
|
+
ariaLabel: optional(string)
|
|
48434
|
+
});
|
|
48435
|
+
const parseLockedFunctionType = object({
|
|
48436
|
+
type: constant("function"),
|
|
48437
|
+
color: parseLockedFigureColor,
|
|
48438
|
+
strokeStyle: parseLockedLineStyle,
|
|
48439
|
+
equation: string,
|
|
48440
|
+
directionalAxis: enumeration("x", "y"),
|
|
48441
|
+
domain: optional(pairOfNumbers),
|
|
48442
|
+
// TODO(benchristel): default labels to empty array?
|
|
48443
|
+
labels: optional(array(parseLockedLabelType)),
|
|
48444
|
+
ariaLabel: optional(string)
|
|
48445
|
+
});
|
|
48446
|
+
const parseLockedFigure = union(parseLockedPointType).or(parseLockedLineType).or(parseLockedVectorType).or(parseLockedEllipseType).or(parseLockedPolygonType).or(parseLockedFunctionType).or(parseLockedLabelType).parser;
|
|
48447
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
48448
|
+
step: pairOfNumbers,
|
|
48449
|
+
gridStep: pairOfNumbers,
|
|
48450
|
+
snapStep: pairOfNumbers,
|
|
48451
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
48452
|
+
markings: enumeration("graph", "grid", "none"),
|
|
48453
|
+
labels: array(string),
|
|
48454
|
+
showProtractor: boolean,
|
|
48455
|
+
showRuler: optional(boolean),
|
|
48456
|
+
showTooltips: optional(boolean),
|
|
48457
|
+
rulerLabel: optional(string),
|
|
48458
|
+
rulerTicks: optional(number),
|
|
48459
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
48460
|
+
graph: parsePerseusGraphType,
|
|
48461
|
+
correct: parsePerseusGraphType,
|
|
48462
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
48463
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
48464
|
+
fullGraphLabel: optional(string),
|
|
48465
|
+
fullGraphAriaDescription: optional(string)
|
|
48466
|
+
}));
|
|
48467
|
+
|
|
48468
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
48469
|
+
if (!isObject(rawValue)) {
|
|
48470
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
48471
|
+
}
|
|
48472
|
+
const widgetsMap = {};
|
|
48473
|
+
for (const key of Object.keys(rawValue)) {
|
|
48474
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
48475
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
48476
|
+
// against the widget type.
|
|
48477
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
48478
|
+
if (isFailure(entryResult)) {
|
|
48479
|
+
return entryResult;
|
|
48480
|
+
}
|
|
48481
|
+
}
|
|
48482
|
+
return ctx.success(widgetsMap);
|
|
48483
|
+
};
|
|
48484
|
+
const parseWidgetsMapEntry = ([key, widget], widgetMap, ctx) => {
|
|
48485
|
+
const keyComponentsResult = parseWidgetMapKeyComponents(key.split(" "), ctx);
|
|
48486
|
+
if (isFailure(keyComponentsResult)) {
|
|
48487
|
+
return keyComponentsResult;
|
|
48488
|
+
}
|
|
48489
|
+
const [type, id] = keyComponentsResult.value;
|
|
48490
|
+
function parseAndAssign(key, parse) {
|
|
48491
|
+
const widgetResult = parse(widget, ctx);
|
|
48492
|
+
if (isFailure(widgetResult)) {
|
|
48493
|
+
return widgetResult;
|
|
48494
|
+
}
|
|
48495
|
+
widgetMap[key] = widgetResult.value;
|
|
48496
|
+
return ctx.success(undefined);
|
|
48497
|
+
}
|
|
48498
|
+
switch (type) {
|
|
48499
|
+
case "categorizer":
|
|
48500
|
+
return parseAndAssign(`categorizer ${id}`, parseCategorizerWidget);
|
|
48501
|
+
case "cs-program":
|
|
48502
|
+
return parseAndAssign(`cs-program ${id}`, parseCSProgramWidget);
|
|
48503
|
+
case "definition":
|
|
48504
|
+
return parseAndAssign(`definition ${id}`, parseDefinitionWidget);
|
|
48505
|
+
case "dropdown":
|
|
48506
|
+
return parseAndAssign(`dropdown ${id}`, parseDropdownWidget);
|
|
48507
|
+
case "explanation":
|
|
48508
|
+
return parseAndAssign(`explanation ${id}`, parseExplanationWidget);
|
|
48509
|
+
case "expression":
|
|
48510
|
+
return parseAndAssign(`expression ${id}`, parseExpressionWidget);
|
|
48511
|
+
case "grapher":
|
|
48512
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48513
|
+
return parseAndAssign(`grapher ${id}`, any);
|
|
48514
|
+
case "group":
|
|
48515
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48516
|
+
return parseAndAssign(`group ${id}`, any);
|
|
48517
|
+
case "graded-group":
|
|
48518
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48519
|
+
return parseAndAssign(`graded-group ${id}`, any);
|
|
48520
|
+
case "graded-group-set":
|
|
48521
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48522
|
+
return parseAndAssign(`graded-group-set ${id}`, any);
|
|
48523
|
+
case "iframe":
|
|
48524
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48525
|
+
return parseAndAssign(`iframe ${id}`, any);
|
|
48526
|
+
case "image":
|
|
48527
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48528
|
+
return parseAndAssign(`image ${id}`, any);
|
|
48529
|
+
case "input-number":
|
|
48530
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48531
|
+
return parseAndAssign(`input-number ${id}`, any);
|
|
48532
|
+
case "interaction":
|
|
48533
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48534
|
+
return parseAndAssign(`interaction ${id}`, any);
|
|
48535
|
+
case "interactive-graph":
|
|
48536
|
+
return parseAndAssign(`interactive-graph ${id}`, parseInteractiveGraphWidget);
|
|
48537
|
+
case "label-image":
|
|
48538
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48539
|
+
return parseAndAssign(`label-image ${id}`, any);
|
|
48540
|
+
case "matcher":
|
|
48541
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48542
|
+
return parseAndAssign(`matcher ${id}`, any);
|
|
48543
|
+
case "matrix":
|
|
48544
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48545
|
+
return parseAndAssign(`matrix ${id}`, any);
|
|
48546
|
+
case "measurer":
|
|
48547
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48548
|
+
return parseAndAssign(`measurer ${id}`, any);
|
|
48549
|
+
case "molecule-renderer":
|
|
48550
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48551
|
+
return parseAndAssign(`molecule-renderer ${id}`, any);
|
|
48552
|
+
case "number-line":
|
|
48553
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48554
|
+
return parseAndAssign(`number-line ${id}`, any);
|
|
48555
|
+
case "numeric-input":
|
|
48556
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48557
|
+
return parseAndAssign(`numeric-input ${id}`, any);
|
|
48558
|
+
case "orderer":
|
|
48559
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48560
|
+
return parseAndAssign(`orderer ${id}`, any);
|
|
48561
|
+
case "passage":
|
|
48562
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48563
|
+
return parseAndAssign(`passage ${id}`, any);
|
|
48564
|
+
case "passage-ref":
|
|
48565
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48566
|
+
return parseAndAssign(`passage-ref ${id}`, any);
|
|
48567
|
+
case "passage-ref-target":
|
|
48568
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48569
|
+
return parseAndAssign(`passage-ref-target ${id}`, any);
|
|
48570
|
+
case "phet-simulation":
|
|
48571
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48572
|
+
return parseAndAssign(`phet-simulation ${id}`, any);
|
|
48573
|
+
case "plotter":
|
|
48574
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48575
|
+
return parseAndAssign(`plotter ${id}`, any);
|
|
48576
|
+
case "python-program":
|
|
48577
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48578
|
+
return parseAndAssign(`python-program ${id}`, any);
|
|
48579
|
+
case "radio":
|
|
48580
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48581
|
+
return parseAndAssign(`radio ${id}`, any);
|
|
48582
|
+
case "sorter":
|
|
48583
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48584
|
+
return parseAndAssign(`sorter ${id}`, any);
|
|
48585
|
+
case "table":
|
|
48586
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48587
|
+
return parseAndAssign(`table ${id}`, any);
|
|
48588
|
+
case "video":
|
|
48589
|
+
// TODO(LEMS-2585): implement a real parser for this widget
|
|
48590
|
+
return parseAndAssign(`video ${id}`, any);
|
|
48591
|
+
default:
|
|
48592
|
+
return ctx.failure("a valid widget type", type);
|
|
48593
|
+
}
|
|
48594
|
+
};
|
|
48595
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
48596
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
48597
|
+
return ctx.failure("numeric string", rawValue);
|
|
48598
|
+
}
|
|
48599
|
+
return ctx.success(+rawValue);
|
|
48600
|
+
};
|
|
48601
|
+
const parseWidgetMapKeyComponents = pair(string, parseStringToPositiveInt);
|
|
48602
|
+
|
|
48603
|
+
const parseHint = object({
|
|
48604
|
+
replace: optional(boolean),
|
|
48605
|
+
content: string,
|
|
48606
|
+
widgets: parseWidgetsMap,
|
|
48607
|
+
metadata: optional(array(string)),
|
|
48608
|
+
images: record(string, object({
|
|
48609
|
+
width: number,
|
|
48610
|
+
height: number
|
|
48611
|
+
}))
|
|
48612
|
+
});
|
|
48613
|
+
|
|
48614
|
+
const parsePerseusRenderer = object({
|
|
48615
|
+
content: string,
|
|
48616
|
+
widgets: parseWidgetsMap,
|
|
48617
|
+
metadata: optional(array(string)),
|
|
48618
|
+
images: record(string, object({
|
|
48619
|
+
width: number,
|
|
48620
|
+
height: number
|
|
48621
|
+
}))
|
|
48622
|
+
});
|
|
48623
|
+
|
|
48624
|
+
object({
|
|
48625
|
+
question: parsePerseusRenderer,
|
|
48626
|
+
hints: array(parseHint),
|
|
48627
|
+
answerArea: record(enumeration(...ItemExtras), boolean),
|
|
48628
|
+
itemDataVersion: object({
|
|
48629
|
+
major: number,
|
|
48630
|
+
minor: number
|
|
48631
|
+
}),
|
|
48632
|
+
// Deprecated field
|
|
48633
|
+
answer: any
|
|
48634
|
+
});
|
|
48635
|
+
|
|
47370
48636
|
/**
|
|
47371
48637
|
* Helper to parse PerseusItem JSON
|
|
47372
48638
|
* Why not just use JSON.parse? We want:
|