@khanacademy/perseus 41.2.0 → 41.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-widgets.d.ts +1 -1
- package/dist/article-renderer.d.ts +2 -2
- package/dist/basic-widgets.d.ts +1 -1
- package/dist/es/index.js +1609 -335
- package/dist/es/index.js.map +1 -1
- package/dist/extra-widgets.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1528 -253
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +3 -3
- package/dist/perseus-types.d.ts +7 -2
- package/dist/prompt-types.d.ts +33 -0
- package/dist/renderer.d.ts +8 -2
- package/dist/server-item-renderer.d.ts +6 -4
- package/dist/types.d.ts +4 -2
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/compose-parsers.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +15 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/util/{parse-perseus-json.d.ts → parse-perseus-json/index.d.ts} +7 -1
- package/dist/util/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/util/parse-perseus-json/parse.d.ts +3 -0
- package/dist/util/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +3 -0
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/util/parse-perseus-json/result.d.ts +16 -0
- package/dist/validation.types.d.ts +0 -3
- package/dist/widget-ai-utils/categorizer/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/cs-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/definition/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/dropdown/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/explanation/prompt-utils.d.ts +8 -0
- package/dist/widget-ai-utils/expression/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/graded-group/prompt-utils.d.ts +7 -0
- package/dist/widget-ai-utils/graded-group-set/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/grapher/prompt-utils.d.ts +18 -0
- package/dist/widget-ai-utils/group/prompt-utils.d.ts +5 -0
- package/dist/widget-ai-utils/iframe/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/image/prompt-utils.d.ts +12 -0
- package/dist/widget-ai-utils/input-number/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/interaction/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/interactive-graph/prompt-utils.d.ts +94 -0
- package/dist/widget-ai-utils/label-image/prompt-utils.d.ts +24 -0
- package/dist/widget-ai-utils/matcher/prompt-utils.d.ts +17 -0
- package/dist/widget-ai-utils/matrix/prompt-utils.d.ts +14 -0
- package/dist/widget-ai-utils/measurer/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/number-line/prompt-utils.d.ts +16 -0
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/orderer/prompt-utils.d.ts +13 -0
- package/dist/widget-ai-utils/passage/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/passage-ref/prompt-utils.d.ts +11 -0
- package/dist/widget-ai-utils/phet-simulation/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/plotter/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/python-program/prompt-utils.d.ts +2 -0
- package/dist/widget-ai-utils/radio/prompt-utils.d.ts +15 -0
- package/dist/widget-ai-utils/sorter/prompt-utils.d.ts +9 -0
- package/dist/widget-ai-utils/unsupported-widget.d.ts +11 -0
- package/dist/widget-ai-utils/video/prompt-utils.d.ts +2 -0
- package/dist/widgets/__shared__/{noop-validator.d.ts → score-noop.d.ts} +4 -4
- package/dist/widgets/categorizer/categorizer.d.ts +10 -19
- package/dist/widgets/categorizer/{categorizer-validator.d.ts → score-categorizer.d.ts} +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +8 -19
- package/dist/widgets/cs-program/{cs-program-validator.d.ts → score-cs-program.d.ts} +2 -1
- package/dist/widgets/definition/definition.d.ts +8 -19
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +4 -19
- package/dist/widgets/dropdown/dropdown.d.ts +10 -19
- package/dist/widgets/dropdown/score-dropdown.d.ts +4 -0
- package/dist/widgets/explanation/explanation.d.ts +9 -19
- package/dist/widgets/expression/expression.d.ts +26 -290
- package/dist/widgets/expression/{expression-validator.d.ts → score-expression.d.ts} +2 -2
- package/dist/widgets/graded-group/graded-group.d.ts +13 -21
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +9 -20
- package/dist/widgets/grapher/grapher.d.ts +9 -19
- package/dist/widgets/grapher/score-grapher.d.ts +4 -0
- package/dist/widgets/group/group.d.ts +7 -19
- package/dist/widgets/iframe/iframe.d.ts +7 -19
- package/dist/widgets/iframe/{iframe-validator.d.ts → score-iframe.d.ts} +1 -1
- package/dist/widgets/image/image.d.ts +9 -19
- package/dist/widgets/input-number/input-number.d.ts +11 -19
- package/dist/widgets/input-number/{input-number-validator.d.ts → score-input-number.d.ts} +2 -2
- package/dist/widgets/interaction/interaction.d.ts +7 -19
- package/dist/widgets/interactive-graph.d.ts +8 -19
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +4 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +1 -1
- package/dist/widgets/label-image/label-image.d.ts +10 -86
- package/dist/widgets/label-image/{label-image-validator.d.ts → score-label-image.d.ts} +2 -2
- package/dist/widgets/matcher/matcher.d.ts +7 -19
- package/dist/widgets/matcher/score-matcher.d.ts +4 -0
- package/dist/widgets/matrix/matrix.d.ts +10 -19
- package/dist/widgets/matrix/{matrix-validator.d.ts → score-matrix.d.ts} +2 -2
- package/dist/widgets/measurer/measurer.d.ts +13 -19
- package/dist/widgets/molecule/molecule.d.ts +4 -19
- package/dist/widgets/number-line/number-line.d.ts +8 -19
- package/dist/widgets/number-line/score-number-line.d.ts +4 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +22 -19
- package/dist/widgets/numeric-input/{numeric-input-validator.d.ts → score-numeric-input.d.ts} +2 -2
- package/dist/widgets/orderer/orderer.d.ts +8 -19
- package/dist/widgets/orderer/{orderer-validator.d.ts → score-orderer.d.ts} +1 -1
- package/dist/widgets/passage/passage.d.ts +8 -19
- package/dist/widgets/passage-ref/passage-ref.d.ts +12 -19
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +11 -19
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +5 -19
- package/dist/widgets/plotter/plotter.d.ts +8 -19
- package/dist/widgets/plotter/score-plotter.d.ts +4 -0
- package/dist/widgets/python-program/python-program.d.ts +4 -19
- package/dist/widgets/radio/radio-component.d.ts +3 -1
- package/dist/widgets/radio/radio.d.ts +18 -19
- package/dist/widgets/radio/{radio-validator.d.ts → score-radio.d.ts} +2 -2
- package/dist/widgets/sorter/score-sorter.d.ts +4 -0
- package/dist/widgets/sorter/sorter.d.ts +7 -19
- package/dist/widgets/table/{table-validator.d.ts → score-table.d.ts} +2 -2
- package/dist/widgets/table/table.d.ts +8 -19
- package/dist/widgets/video/video.d.ts +7 -19
- package/dist/widgets.d.ts +2 -2
- package/package.json +4 -4
- package/dist/widgets/dropdown/dropdown-validator.d.ts +0 -4
- package/dist/widgets/grapher/grapher-validator.d.ts +0 -4
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +0 -4
- package/dist/widgets/number-line/number-line-validator.d.ts +0 -4
- package/dist/widgets/plotter/plotter-validator.d.ts +0 -4
- package/dist/widgets/sorter/sorter-validator.d.ts +0 -4
package/dist/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)
|
|
26926
27050
|
};
|
|
26927
27051
|
|
|
26928
|
-
|
|
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
|
+
};
|
|
27062
|
+
};
|
|
27063
|
+
|
|
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",
|
|
@@ -31644,17 +32042,58 @@ function isUnlimitedGraphState(state) {
|
|
|
31644
32042
|
* and the text outside of the $ blocks will be non-TeX text.
|
|
31645
32043
|
*/
|
|
31646
32044
|
function replaceOutsideTeX(mathString) {
|
|
31647
|
-
|
|
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);
|
|
31648
32057
|
let result = "";
|
|
31649
|
-
const
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
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
|
+
});
|
|
31654
32087
|
}
|
|
31655
32088
|
return result;
|
|
31656
32089
|
}
|
|
31657
32090
|
|
|
32091
|
+
// Helper function for replaceOutsideTeX()
|
|
32092
|
+
function escapeSpecialChars(str) {
|
|
32093
|
+
// Escape $, \, {, and } characters
|
|
32094
|
+
return str.replace(/([$\\{}])/g, "\\$1");
|
|
32095
|
+
}
|
|
32096
|
+
|
|
31658
32097
|
function LockedLabel(props) {
|
|
31659
32098
|
const {
|
|
31660
32099
|
coord,
|
|
@@ -35915,7 +36354,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
35915
36354
|
|
|
35916
36355
|
const eq$1 = Util.eq;
|
|
35917
36356
|
const deepEq$2 = Util.deepEq;
|
|
35918
|
-
function
|
|
36357
|
+
function scoreInteractiveGraph(userInput, rubric) {
|
|
35919
36358
|
// None-type graphs are not graded
|
|
35920
36359
|
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35921
36360
|
return {
|
|
@@ -36024,7 +36463,7 @@ function interactiveGraphValidator(userInput, rubric) {
|
|
|
36024
36463
|
if (rubric.correct.match === "similar") {
|
|
36025
36464
|
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36026
36465
|
} else if (rubric.correct.match === "congruent") {
|
|
36027
|
-
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
36466
|
+
match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
|
|
36028
36467
|
} else if (rubric.correct.match === "approx") {
|
|
36029
36468
|
match = similar(guess, correct, 0.1);
|
|
36030
36469
|
} else {
|
|
@@ -36333,7 +36772,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
36333
36772
|
};
|
|
36334
36773
|
this.setTrashCanVisibility = opacity => {
|
|
36335
36774
|
const graphie = this.graphie;
|
|
36336
|
-
if (number.equal(opacity, 0)) {
|
|
36775
|
+
if (number$1.equal(opacity, 0)) {
|
|
36337
36776
|
if (this.trashCan) {
|
|
36338
36777
|
this.trashCan.remove();
|
|
36339
36778
|
this.trashCan = null;
|
|
@@ -37427,6 +37866,9 @@ class InteractiveGraph extends React.Component {
|
|
|
37427
37866
|
}
|
|
37428
37867
|
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
37429
37868
|
}
|
|
37869
|
+
getPromptJSON() {
|
|
37870
|
+
return getPromptJSON$b(this.props, this.getUserInput());
|
|
37871
|
+
}
|
|
37430
37872
|
render() {
|
|
37431
37873
|
var _this$props$apiOption;
|
|
37432
37874
|
// Mafs shim
|
|
@@ -37805,7 +38247,7 @@ var InteractiveGraph$1 = {
|
|
|
37805
38247
|
displayName: "Interactive graph (Assessments only)",
|
|
37806
38248
|
widget: InteractiveGraph,
|
|
37807
38249
|
staticTransform: staticTransform$3,
|
|
37808
|
-
|
|
38250
|
+
scorer: scoreInteractiveGraph
|
|
37809
38251
|
};
|
|
37810
38252
|
|
|
37811
38253
|
// TODO: can we use WB typography?
|
|
@@ -37817,6 +38259,32 @@ const bodyXsmallBold = {
|
|
|
37817
38259
|
lineHeight: "22px"
|
|
37818
38260
|
};
|
|
37819
38261
|
|
|
38262
|
+
const getPromptJSON$a = (renderProps, userInput) => {
|
|
38263
|
+
const propMarkers = renderProps.markers.map(marker => {
|
|
38264
|
+
return {
|
|
38265
|
+
label: marker.label
|
|
38266
|
+
};
|
|
38267
|
+
});
|
|
38268
|
+
const inputMarkers = userInput.markers.map(marker => {
|
|
38269
|
+
return {
|
|
38270
|
+
label: marker.label,
|
|
38271
|
+
selected: marker.selected
|
|
38272
|
+
};
|
|
38273
|
+
});
|
|
38274
|
+
return {
|
|
38275
|
+
type: "label-image",
|
|
38276
|
+
options: {
|
|
38277
|
+
choices: renderProps.choices,
|
|
38278
|
+
imageUrl: renderProps.imageUrl,
|
|
38279
|
+
imageAlt: renderProps.imageAlt,
|
|
38280
|
+
markers: propMarkers
|
|
38281
|
+
},
|
|
38282
|
+
userInput: {
|
|
38283
|
+
markers: inputMarkers
|
|
38284
|
+
}
|
|
38285
|
+
};
|
|
38286
|
+
};
|
|
38287
|
+
|
|
37820
38288
|
const AnswerChoices = props => {
|
|
37821
38289
|
const {
|
|
37822
38290
|
strings
|
|
@@ -37899,59 +38367,6 @@ const styles$d = StyleSheet.create({
|
|
|
37899
38367
|
}
|
|
37900
38368
|
});
|
|
37901
38369
|
|
|
37902
|
-
// Question state for marker as result of user selected answers.
|
|
37903
|
-
|
|
37904
|
-
function scoreMarker(marker) {
|
|
37905
|
-
const score = {
|
|
37906
|
-
hasAnswers: false,
|
|
37907
|
-
isCorrect: false
|
|
37908
|
-
};
|
|
37909
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
37910
|
-
score.hasAnswers = true;
|
|
37911
|
-
}
|
|
37912
|
-
if (marker.answers.length > 0) {
|
|
37913
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
37914
|
-
// All correct answers are selected by the user.
|
|
37915
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
37916
|
-
}
|
|
37917
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
37918
|
-
// Correct as no answers should be selected by the user.
|
|
37919
|
-
score.isCorrect = true;
|
|
37920
|
-
}
|
|
37921
|
-
return score;
|
|
37922
|
-
}
|
|
37923
|
-
|
|
37924
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
37925
|
-
function labelImageValidator(userInput, rubric) {
|
|
37926
|
-
let numAnswered = 0;
|
|
37927
|
-
let numCorrect = 0;
|
|
37928
|
-
for (const marker of userInput.markers) {
|
|
37929
|
-
const score = scoreMarker(marker);
|
|
37930
|
-
if (score.hasAnswers) {
|
|
37931
|
-
numAnswered++;
|
|
37932
|
-
}
|
|
37933
|
-
if (score.isCorrect) {
|
|
37934
|
-
numCorrect++;
|
|
37935
|
-
}
|
|
37936
|
-
}
|
|
37937
|
-
|
|
37938
|
-
// We expect all question markers to be answered before grading.
|
|
37939
|
-
if (numAnswered !== userInput.markers.length) {
|
|
37940
|
-
return {
|
|
37941
|
-
type: "invalid",
|
|
37942
|
-
message: null
|
|
37943
|
-
};
|
|
37944
|
-
}
|
|
37945
|
-
return {
|
|
37946
|
-
type: "points",
|
|
37947
|
-
// Markers with no expected answers are graded as correct if user
|
|
37948
|
-
// makes no answer selection.
|
|
37949
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
37950
|
-
total: 1,
|
|
37951
|
-
message: null
|
|
37952
|
-
};
|
|
37953
|
-
}
|
|
37954
|
-
|
|
37955
38370
|
const BringToFront = {
|
|
37956
38371
|
boxShadow: `0 8px 8px ${color.offBlack64}`,
|
|
37957
38372
|
zIndex: 1000
|
|
@@ -38205,6 +38620,59 @@ const styles$b = StyleSheet.create({
|
|
|
38205
38620
|
}
|
|
38206
38621
|
});
|
|
38207
38622
|
|
|
38623
|
+
// Question state for marker as result of user selected answers.
|
|
38624
|
+
|
|
38625
|
+
function scoreMarker(marker) {
|
|
38626
|
+
const score = {
|
|
38627
|
+
hasAnswers: false,
|
|
38628
|
+
isCorrect: false
|
|
38629
|
+
};
|
|
38630
|
+
if (marker.selected && marker.selected.length > 0) {
|
|
38631
|
+
score.hasAnswers = true;
|
|
38632
|
+
}
|
|
38633
|
+
if (marker.answers.length > 0) {
|
|
38634
|
+
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38635
|
+
// All correct answers are selected by the user.
|
|
38636
|
+
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38637
|
+
}
|
|
38638
|
+
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38639
|
+
// Correct as no answers should be selected by the user.
|
|
38640
|
+
score.isCorrect = true;
|
|
38641
|
+
}
|
|
38642
|
+
return score;
|
|
38643
|
+
}
|
|
38644
|
+
|
|
38645
|
+
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38646
|
+
function scoreLabelImage(userInput, rubric) {
|
|
38647
|
+
let numAnswered = 0;
|
|
38648
|
+
let numCorrect = 0;
|
|
38649
|
+
for (const marker of userInput.markers) {
|
|
38650
|
+
const score = scoreMarker(marker);
|
|
38651
|
+
if (score.hasAnswers) {
|
|
38652
|
+
numAnswered++;
|
|
38653
|
+
}
|
|
38654
|
+
if (score.isCorrect) {
|
|
38655
|
+
numCorrect++;
|
|
38656
|
+
}
|
|
38657
|
+
}
|
|
38658
|
+
|
|
38659
|
+
// We expect all question markers to be answered before grading.
|
|
38660
|
+
if (numAnswered !== userInput.markers.length) {
|
|
38661
|
+
return {
|
|
38662
|
+
type: "invalid",
|
|
38663
|
+
message: null
|
|
38664
|
+
};
|
|
38665
|
+
}
|
|
38666
|
+
return {
|
|
38667
|
+
type: "points",
|
|
38668
|
+
// Markers with no expected answers are graded as correct if user
|
|
38669
|
+
// makes no answer selection.
|
|
38670
|
+
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38671
|
+
total: 1,
|
|
38672
|
+
message: null
|
|
38673
|
+
};
|
|
38674
|
+
}
|
|
38675
|
+
|
|
38208
38676
|
/**
|
|
38209
38677
|
* Represents a direction vector.
|
|
38210
38678
|
* No diagonals allowed.
|
|
@@ -38413,6 +38881,9 @@ class LabelImage extends React.Component {
|
|
|
38413
38881
|
markers
|
|
38414
38882
|
};
|
|
38415
38883
|
}
|
|
38884
|
+
getPromptJSON() {
|
|
38885
|
+
return getPromptJSON$a(this.props, this.getUserInput());
|
|
38886
|
+
}
|
|
38416
38887
|
|
|
38417
38888
|
// TODO(LEMS-2544): Investigate impact on scoring; possibly pull out &/or remove rubric parameter.
|
|
38418
38889
|
showRationalesForCurrentlySelectedChoices(rubric) {
|
|
@@ -38769,7 +39240,7 @@ var LabelImage$1 = {
|
|
|
38769
39240
|
widget: LabelImageWithDependencies,
|
|
38770
39241
|
accessible: true,
|
|
38771
39242
|
isLintable: true,
|
|
38772
|
-
|
|
39243
|
+
scorer: scoreLabelImage
|
|
38773
39244
|
};
|
|
38774
39245
|
|
|
38775
39246
|
var ItemState = /*#__PURE__*/function (ItemState) {
|
|
@@ -39481,7 +39952,23 @@ const styles$9 = StyleSheet.create({
|
|
|
39481
39952
|
}
|
|
39482
39953
|
});
|
|
39483
39954
|
|
|
39484
|
-
|
|
39955
|
+
const getPromptJSON$9 = (renderProps, userInput) => {
|
|
39956
|
+
return {
|
|
39957
|
+
type: "matcher",
|
|
39958
|
+
options: {
|
|
39959
|
+
labels: renderProps.labels,
|
|
39960
|
+
left: renderProps.left,
|
|
39961
|
+
right: renderProps.right,
|
|
39962
|
+
orderMatters: renderProps.orderMatters
|
|
39963
|
+
},
|
|
39964
|
+
userInput: {
|
|
39965
|
+
left: userInput.left,
|
|
39966
|
+
right: userInput.right
|
|
39967
|
+
}
|
|
39968
|
+
};
|
|
39969
|
+
};
|
|
39970
|
+
|
|
39971
|
+
function scoreMatcher(state, rubric) {
|
|
39485
39972
|
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
39486
39973
|
return {
|
|
39487
39974
|
type: "points",
|
|
@@ -39554,6 +40041,9 @@ class Matcher extends React.Component {
|
|
|
39554
40041
|
this.refs.right.moveOptionToIndex(option, index);
|
|
39555
40042
|
};
|
|
39556
40043
|
}
|
|
40044
|
+
getPromptJSON() {
|
|
40045
|
+
return getPromptJSON$9(this.props, this.getUserInput());
|
|
40046
|
+
}
|
|
39557
40047
|
render() {
|
|
39558
40048
|
// To minimize layout shift, we display a spinner until our math
|
|
39559
40049
|
// renderer is ready to render the math inside the matcher. To
|
|
@@ -39692,10 +40182,23 @@ var Matcher$1 = {
|
|
|
39692
40182
|
displayName: "Matcher (two column)",
|
|
39693
40183
|
widget: Matcher,
|
|
39694
40184
|
isLintable: true,
|
|
39695
|
-
|
|
40185
|
+
scorer: scoreMatcher
|
|
39696
40186
|
};
|
|
39697
40187
|
|
|
39698
|
-
|
|
40188
|
+
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
40189
|
+
return {
|
|
40190
|
+
type: "matrix",
|
|
40191
|
+
options: {
|
|
40192
|
+
height: renderProps.matrixBoardSize[0],
|
|
40193
|
+
width: renderProps.matrixBoardSize[1]
|
|
40194
|
+
},
|
|
40195
|
+
userInput: {
|
|
40196
|
+
answerRows: userInput.answers
|
|
40197
|
+
}
|
|
40198
|
+
};
|
|
40199
|
+
};
|
|
40200
|
+
|
|
40201
|
+
function scoreMatrix(userInput, rubric, strings) {
|
|
39699
40202
|
const solution = rubric.answers;
|
|
39700
40203
|
const supplied = userInput.answers;
|
|
39701
40204
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -39965,6 +40468,9 @@ class Matrix extends React.Component {
|
|
|
39965
40468
|
answers: this.props.answers
|
|
39966
40469
|
};
|
|
39967
40470
|
}
|
|
40471
|
+
getPromptJSON() {
|
|
40472
|
+
return getPromptJSON$8(this.props, this.getUserInput());
|
|
40473
|
+
}
|
|
39968
40474
|
render() {
|
|
39969
40475
|
// Set the input sizes through JS so we can control the size of the
|
|
39970
40476
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40158,7 +40664,11 @@ var Matrix$1 = {
|
|
|
40158
40664
|
transform: propTransform$1,
|
|
40159
40665
|
staticTransform: staticTransform$2,
|
|
40160
40666
|
isLintable: true,
|
|
40161
|
-
|
|
40667
|
+
scorer: scoreMatrix
|
|
40668
|
+
};
|
|
40669
|
+
|
|
40670
|
+
const getPromptJSON$7 = () => {
|
|
40671
|
+
return getUnsupportedPromptJSON("measurer");
|
|
40162
40672
|
};
|
|
40163
40673
|
|
|
40164
40674
|
const defaultImage = {
|
|
@@ -40225,6 +40735,9 @@ class Measurer extends React.Component {
|
|
|
40225
40735
|
});
|
|
40226
40736
|
}
|
|
40227
40737
|
}
|
|
40738
|
+
getPromptJSON() {
|
|
40739
|
+
return getPromptJSON$7();
|
|
40740
|
+
}
|
|
40228
40741
|
render() {
|
|
40229
40742
|
const image = _.extend({}, defaultImage, this.props.image);
|
|
40230
40743
|
|
|
@@ -40284,8 +40797,8 @@ var Measurer$1 = {
|
|
|
40284
40797
|
minor: 0
|
|
40285
40798
|
},
|
|
40286
40799
|
propUpgrades: propUpgrades,
|
|
40287
|
-
// TODO: things that aren't interactive shouldn't need
|
|
40288
|
-
|
|
40800
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
40801
|
+
scorer: () => scoreNoop(1)
|
|
40289
40802
|
};
|
|
40290
40803
|
|
|
40291
40804
|
/**
|
|
@@ -41174,16 +41687,31 @@ var Molecule$1 = {
|
|
|
41174
41687
|
displayName: "Molecule renderer",
|
|
41175
41688
|
hidden: true,
|
|
41176
41689
|
widget: MoleculeWidget,
|
|
41177
|
-
|
|
41690
|
+
scorer: () => scoreNoop()
|
|
41178
41691
|
};
|
|
41179
41692
|
|
|
41180
|
-
|
|
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) {
|
|
41181
41709
|
const range = rubric.range;
|
|
41182
41710
|
const divisionRange = state.divisionRange;
|
|
41183
41711
|
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
41184
41712
|
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
41185
41713
|
const correctRel = rubric.correctRel || "eq";
|
|
41186
|
-
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41714
|
+
const correctPos = number$1.equal(state.numLinePosition, rubric.correctX || 0);
|
|
41187
41715
|
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
41188
41716
|
|
|
41189
41717
|
// TODO: I don't think isTickCrtl is a thing anymore
|
|
@@ -41312,7 +41840,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
41312
41840
|
const x = range[0] + i * tickStep;
|
|
41313
41841
|
fractions.push(x);
|
|
41314
41842
|
}
|
|
41315
|
-
const getDenom = x => number.toFraction(x)[1];
|
|
41843
|
+
const getDenom = x => number$1.toFraction(x)[1];
|
|
41316
41844
|
const denoms = _.map(fractions, getDenom);
|
|
41317
41845
|
base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
41318
41846
|
} else {
|
|
@@ -41375,7 +41903,7 @@ class NumberLine extends React.Component {
|
|
|
41375
41903
|
let initialX = this.props.numLinePosition;
|
|
41376
41904
|
const divisionRange = this.props.divisionRange;
|
|
41377
41905
|
initialX = initialX == null ? range[0] : initialX;
|
|
41378
|
-
return range[0] < range[1] && number.sign(initialX - range[0]) >= 0 && number.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
41906
|
+
return range[0] < range[1] && number$1.sign(initialX - range[0]) >= 0 && number$1.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
41379
41907
|
};
|
|
41380
41908
|
this.onNumDivisionsChange = (numDivisions, cb) => {
|
|
41381
41909
|
const divRange = this.props.divisionRange.slice();
|
|
@@ -41478,7 +42006,7 @@ class NumberLine extends React.Component {
|
|
|
41478
42006
|
const right = props.range[1];
|
|
41479
42007
|
const snapX = props.tickStep / props.snapDivisions;
|
|
41480
42008
|
let x = bound(numLinePosition, left, right);
|
|
41481
|
-
x = left + number.roundTo(x - left, snapX);
|
|
42009
|
+
x = left + number$1.roundTo(x - left, snapX);
|
|
41482
42010
|
assert$1(_.isFinite(x));
|
|
41483
42011
|
return x;
|
|
41484
42012
|
};
|
|
@@ -41645,6 +42173,9 @@ class NumberLine extends React.Component {
|
|
|
41645
42173
|
divisionRange: this.props.divisionRange
|
|
41646
42174
|
};
|
|
41647
42175
|
}
|
|
42176
|
+
getPromptJSON() {
|
|
42177
|
+
return getPromptJSON$6(this.props, this.getUserInput());
|
|
42178
|
+
}
|
|
41648
42179
|
render() {
|
|
41649
42180
|
const {
|
|
41650
42181
|
strings
|
|
@@ -41764,10 +42295,22 @@ var NumberLine$1 = {
|
|
|
41764
42295
|
widget: NumberLine,
|
|
41765
42296
|
transform: numberLineTransform,
|
|
41766
42297
|
staticTransform: staticTransform$1,
|
|
41767
|
-
|
|
42298
|
+
scorer: scoreNumberLine
|
|
41768
42299
|
};
|
|
41769
42300
|
|
|
41770
|
-
|
|
42301
|
+
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
42302
|
+
return {
|
|
42303
|
+
type: "orderer",
|
|
42304
|
+
options: {
|
|
42305
|
+
options: renderProps.options.map(option => option.content)
|
|
42306
|
+
},
|
|
42307
|
+
userInput: {
|
|
42308
|
+
values: userInput.current
|
|
42309
|
+
}
|
|
42310
|
+
};
|
|
42311
|
+
};
|
|
42312
|
+
|
|
42313
|
+
function scoreOrderer(userInput, rubric) {
|
|
41771
42314
|
if (userInput.current.length === 0) {
|
|
41772
42315
|
return {
|
|
41773
42316
|
type: "invalid",
|
|
@@ -42211,6 +42754,9 @@ class Orderer extends React.Component {
|
|
|
42211
42754
|
})
|
|
42212
42755
|
};
|
|
42213
42756
|
}
|
|
42757
|
+
getPromptJSON() {
|
|
42758
|
+
return getPromptJSON$5(this.props, this.getUserInput());
|
|
42759
|
+
}
|
|
42214
42760
|
render() {
|
|
42215
42761
|
// This is the card we are currently dragging
|
|
42216
42762
|
const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
|
|
@@ -42329,7 +42875,7 @@ var Orderer$1 = {
|
|
|
42329
42875
|
hidden: true,
|
|
42330
42876
|
widget: Orderer,
|
|
42331
42877
|
isLintable: true,
|
|
42332
|
-
|
|
42878
|
+
scorer: scoreOrderer
|
|
42333
42879
|
};
|
|
42334
42880
|
|
|
42335
42881
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -42378,8 +42924,12 @@ var PassageRefTarget$1 = {
|
|
|
42378
42924
|
minor: 0
|
|
42379
42925
|
},
|
|
42380
42926
|
isLintable: true,
|
|
42381
|
-
// TODO: things that aren't interactive shouldn't need
|
|
42382
|
-
|
|
42927
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
42928
|
+
scorer: () => scoreNoop()
|
|
42929
|
+
};
|
|
42930
|
+
|
|
42931
|
+
const getPromptJSON$4 = () => {
|
|
42932
|
+
return getUnsupportedPromptJSON("phet-simulation");
|
|
42383
42933
|
};
|
|
42384
42934
|
|
|
42385
42935
|
/**
|
|
@@ -42437,6 +42987,9 @@ class PhetSimulation extends React.Component {
|
|
|
42437
42987
|
await this.updateSimState(this.props.url);
|
|
42438
42988
|
}
|
|
42439
42989
|
}
|
|
42990
|
+
getPromptJSON() {
|
|
42991
|
+
return getPromptJSON$4();
|
|
42992
|
+
}
|
|
42440
42993
|
async updateSimState(urlString) {
|
|
42441
42994
|
const url = makeSafeUrl(urlString, this.locale);
|
|
42442
42995
|
if (url === null) {
|
|
@@ -42585,10 +43138,14 @@ var PhetSimulation$1 = {
|
|
|
42585
43138
|
isLintable: true
|
|
42586
43139
|
};
|
|
42587
43140
|
|
|
43141
|
+
const getPromptJSON$3 = () => {
|
|
43142
|
+
return getUnsupportedPromptJSON("plotter");
|
|
43143
|
+
};
|
|
43144
|
+
|
|
42588
43145
|
const {
|
|
42589
43146
|
deepEq: deepEq$1
|
|
42590
43147
|
} = Util;
|
|
42591
|
-
function
|
|
43148
|
+
function scorePlotter(userInput, rubric) {
|
|
42592
43149
|
if (deepEq$1(userInput, rubric.starting)) {
|
|
42593
43150
|
return {
|
|
42594
43151
|
type: "invalid",
|
|
@@ -43361,6 +43918,9 @@ class Plotter extends React.Component {
|
|
|
43361
43918
|
getUserInput() {
|
|
43362
43919
|
return this.state.values;
|
|
43363
43920
|
}
|
|
43921
|
+
getPromptJSON() {
|
|
43922
|
+
return getPromptJSON$3();
|
|
43923
|
+
}
|
|
43364
43924
|
render() {
|
|
43365
43925
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
43366
43926
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -43401,7 +43961,11 @@ var Plotter$1 = {
|
|
|
43401
43961
|
hidden: true,
|
|
43402
43962
|
widget: Plotter,
|
|
43403
43963
|
staticTransform: staticTransform,
|
|
43404
|
-
|
|
43964
|
+
scorer: scorePlotter
|
|
43965
|
+
};
|
|
43966
|
+
|
|
43967
|
+
const getPromptJSON$2 = () => {
|
|
43968
|
+
return getUnsupportedPromptJSON("python-program");
|
|
43405
43969
|
};
|
|
43406
43970
|
|
|
43407
43971
|
/**
|
|
@@ -43423,6 +43987,9 @@ class PythonProgram extends React.Component {
|
|
|
43423
43987
|
getDOMNodeForPath(path) {
|
|
43424
43988
|
return null;
|
|
43425
43989
|
}
|
|
43990
|
+
getPromptJSON() {
|
|
43991
|
+
return getPromptJSON$2();
|
|
43992
|
+
}
|
|
43426
43993
|
render() {
|
|
43427
43994
|
const url = getUrlFromProgramID(this.props.programID);
|
|
43428
43995
|
const iframeStyle = {
|
|
@@ -43460,7 +44027,17 @@ var PythonProgram$1 = {
|
|
|
43460
44027
|
widget: PythonProgram
|
|
43461
44028
|
};
|
|
43462
44029
|
|
|
43463
|
-
|
|
44030
|
+
const getPromptJSON$1 = userInput => {
|
|
44031
|
+
return {
|
|
44032
|
+
type: "sorter",
|
|
44033
|
+
userInput: {
|
|
44034
|
+
values: userInput.options,
|
|
44035
|
+
changed: userInput.changed
|
|
44036
|
+
}
|
|
44037
|
+
};
|
|
44038
|
+
};
|
|
44039
|
+
|
|
44040
|
+
function scoreSorter(userInput, rubric) {
|
|
43464
44041
|
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43465
44042
|
// prevents the "Check" button from becoming active. We want the user
|
|
43466
44043
|
// to make a change before trying to move forward. This makes an
|
|
@@ -43532,6 +44109,9 @@ class Sorter extends React.Component {
|
|
|
43532
44109
|
changed: this.state.changed
|
|
43533
44110
|
};
|
|
43534
44111
|
}
|
|
44112
|
+
getPromptJSON() {
|
|
44113
|
+
return getPromptJSON$1(this.getUserInput());
|
|
44114
|
+
}
|
|
43535
44115
|
render() {
|
|
43536
44116
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
43537
44117
|
const {
|
|
@@ -43566,10 +44146,10 @@ var Sorter$1 = {
|
|
|
43566
44146
|
displayName: "Sorter",
|
|
43567
44147
|
widget: Sorter,
|
|
43568
44148
|
isLintable: true,
|
|
43569
|
-
|
|
44149
|
+
scorer: scoreSorter
|
|
43570
44150
|
};
|
|
43571
44151
|
|
|
43572
|
-
function
|
|
44152
|
+
function scoreTable(state, rubric, strings) {
|
|
43573
44153
|
const filterNonEmpty = function filterNonEmpty(table) {
|
|
43574
44154
|
return _.filter(table, function (row) {
|
|
43575
44155
|
// Check if row has a cell that is nonempty
|
|
@@ -43846,7 +44426,11 @@ var Table$1 = {
|
|
|
43846
44426
|
transform: propTransform,
|
|
43847
44427
|
hidden: true,
|
|
43848
44428
|
isLintable: true,
|
|
43849
|
-
|
|
44429
|
+
scorer: scoreTable
|
|
44430
|
+
};
|
|
44431
|
+
|
|
44432
|
+
const getPromptJSON = () => {
|
|
44433
|
+
return getUnsupportedPromptJSON("video");
|
|
43850
44434
|
};
|
|
43851
44435
|
|
|
43852
44436
|
/**
|
|
@@ -43942,6 +44526,9 @@ class Video extends React.Component {
|
|
|
43942
44526
|
return change.apply(this, args);
|
|
43943
44527
|
};
|
|
43944
44528
|
}
|
|
44529
|
+
getPromptJSON() {
|
|
44530
|
+
return getPromptJSON();
|
|
44531
|
+
}
|
|
43945
44532
|
render() {
|
|
43946
44533
|
const {
|
|
43947
44534
|
InitialRequestUrl
|
|
@@ -44002,8 +44589,8 @@ var Video$1 = {
|
|
|
44002
44589
|
defaultAlignment: "block",
|
|
44003
44590
|
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44004
44591
|
widget: Video,
|
|
44005
|
-
// TODO: things that aren't interactive shouldn't need
|
|
44006
|
-
|
|
44592
|
+
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
44593
|
+
scorer: () => scoreNoop()
|
|
44007
44594
|
};
|
|
44008
44595
|
|
|
44009
44596
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -44022,7 +44609,7 @@ const init = function init() {
|
|
|
44022
44609
|
|
|
44023
44610
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44024
44611
|
const libName = "@khanacademy/perseus";
|
|
44025
|
-
const libVersion = "41.
|
|
44612
|
+
const libVersion = "41.3.0";
|
|
44026
44613
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44027
44614
|
|
|
44028
44615
|
const apiVersion = {
|
|
@@ -44759,6 +45346,9 @@ class ServerItemRenderer extends React.Component {
|
|
|
44759
45346
|
getNumHints() {
|
|
44760
45347
|
return this.props.item.hints.length;
|
|
44761
45348
|
}
|
|
45349
|
+
getPromptJSON() {
|
|
45350
|
+
return this.questionRenderer.getPromptJSON();
|
|
45351
|
+
}
|
|
44762
45352
|
|
|
44763
45353
|
/**
|
|
44764
45354
|
* Grades the item.
|
|
@@ -45406,7 +45996,7 @@ function treeToItem(node) {
|
|
|
45406
45996
|
};
|
|
45407
45997
|
}
|
|
45408
45998
|
|
|
45409
|
-
var isObject = function isObject(obj) {
|
|
45999
|
+
var isObject$1 = function isObject(obj) {
|
|
45410
46000
|
return obj === Object(obj);
|
|
45411
46001
|
};
|
|
45412
46002
|
var merge = function merge() {
|
|
@@ -45422,7 +46012,7 @@ var merge = function merge() {
|
|
|
45422
46012
|
return obj;
|
|
45423
46013
|
};
|
|
45424
46014
|
var clone = function clone(obj) {
|
|
45425
|
-
if (!isObject(obj)) {
|
|
46015
|
+
if (!isObject$1(obj)) {
|
|
45426
46016
|
return obj;
|
|
45427
46017
|
}
|
|
45428
46018
|
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
@@ -45438,7 +46028,7 @@ var clone = function clone(obj) {
|
|
|
45438
46028
|
var dispatch = function dispatch(x) {
|
|
45439
46029
|
if (Array.isArray(x)) {
|
|
45440
46030
|
return arr;
|
|
45441
|
-
} else if (isObject(x)) {
|
|
46031
|
+
} else if (isObject$1(x)) {
|
|
45442
46032
|
return obj;
|
|
45443
46033
|
} else if (typeof x === "string") {
|
|
45444
46034
|
return str;
|
|
@@ -47359,6 +47949,690 @@ function buildTestData(testObject) {
|
|
|
47359
47949
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
47360
47950
|
}
|
|
47361
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
|
+
|
|
47362
48636
|
/**
|
|
47363
48637
|
* Helper to parse PerseusItem JSON
|
|
47364
48638
|
* Why not just use JSON.parse? We want:
|