@khanacademy/perseus 34.1.0 → 35.1.1
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/es/index.js +1109 -974
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +8 -2
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1032 -905
- package/dist/index.js.map +1 -1
- package/dist/mixins/widget-prop-denylist.d.ts +0 -1
- package/dist/perseus-api.d.ts +5 -5
- package/dist/perseus-types.d.ts +13 -19
- package/dist/renderer.d.ts +4 -4
- package/dist/server-item-renderer.d.ts +5 -9
- package/dist/strings.d.ts +3 -0
- package/dist/strings.js +8 -2
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +3 -6
- package/dist/validation.types.d.ts +114 -0
- package/dist/widgets/categorizer/categorizer-validator.d.ts +2 -2
- package/dist/widgets/categorizer/categorizer.d.ts +6 -6
- package/dist/widgets/cs-program/cs-program-validator.d.ts +3 -0
- package/dist/widgets/cs-program/cs-program.d.ts +5 -10
- package/dist/widgets/definition/definition.d.ts +3 -4
- package/dist/widgets/dropdown/dropdown-validator.d.ts +2 -2
- package/dist/widgets/dropdown/dropdown.d.ts +5 -5
- package/dist/widgets/explanation/explanation.d.ts +3 -2
- package/dist/widgets/expression/expression-validator.d.ts +2 -2
- package/dist/widgets/expression/expression.d.ts +46 -28
- package/dist/widgets/expression/expression.types.d.ts +0 -1
- package/dist/widgets/graded-group/graded-group.d.ts +2 -2
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +2 -2
- package/dist/widgets/grapher/grapher-validator.d.ts +4 -0
- package/dist/widgets/grapher/grapher.d.ts +6 -7
- package/dist/widgets/grapher/util.d.ts +0 -2
- package/dist/widgets/group/group.d.ts +3 -2
- package/dist/widgets/iframe/iframe-validator.d.ts +3 -0
- package/dist/widgets/iframe/iframe.d.ts +7 -11
- package/dist/widgets/image/image.d.ts +3 -4
- package/dist/widgets/input-number/input-number-validator.d.ts +3 -3
- package/dist/widgets/input-number/input-number.d.ts +7 -10
- package/dist/widgets/interaction/interaction.d.ts +3 -3
- package/dist/widgets/interactive-graph.d.ts +11 -14
- package/dist/widgets/interactive-graphs/backgrounds/axis-ticks.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graph-locked-layer.d.ts +2 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +15 -2
- package/dist/widgets/interactive-graphs/interactive-graph-validator.d.ts +4 -0
- package/dist/widgets/interactive-graphs/locked-figures/locked-ellipse.d.ts +5 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +5 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-line.d.ts +2 -0
- package/dist/widgets/interactive-graphs/locked-figures/locked-point.d.ts +5 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-polygon.d.ts +5 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-vector.d.ts +5 -1
- package/dist/widgets/interactive-graphs/mafs-state-to-interactive-graph.d.ts +3 -0
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +5 -2
- package/dist/widgets/label-image/label-image-validator.d.ts +10 -0
- package/dist/widgets/label-image/label-image.d.ts +15 -10
- package/dist/widgets/label-image/types.d.ts +0 -4
- package/dist/widgets/matcher/matcher-validator.d.ts +4 -0
- package/dist/widgets/matcher/matcher.d.ts +6 -5
- package/dist/widgets/matrix/matrix-validator.d.ts +5 -0
- package/dist/widgets/matrix/matrix.d.ts +6 -5
- package/dist/widgets/molecule/molecule.d.ts +2 -1
- package/dist/widgets/number-line/number-line-validator.d.ts +4 -0
- package/dist/widgets/number-line/number-line.d.ts +7 -4
- package/dist/widgets/numeric-input/numeric-input-validator.d.ts +2 -2
- package/dist/widgets/numeric-input/numeric-input.d.ts +7 -7
- package/dist/widgets/orderer/orderer.d.ts +4 -4
- package/dist/widgets/passage/passage.d.ts +3 -4
- package/dist/widgets/passage-ref/passage-ref.d.ts +3 -11
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +2 -1
- package/dist/widgets/phet-simulation/phet-simulation.d.ts +2 -2
- package/dist/widgets/plotter/plotter-validator.d.ts +4 -0
- package/dist/widgets/plotter/plotter.d.ts +6 -5
- package/dist/widgets/radio/radio-component.d.ts +6 -13
- package/dist/widgets/radio/radio-validator.d.ts +5 -0
- package/dist/widgets/sorter/sorter-validator.d.ts +4 -0
- package/dist/widgets/sorter/sorter.d.ts +7 -9
- package/dist/widgets/table/table-validator.d.ts +5 -0
- package/dist/widgets/table/table.d.ts +6 -5
- package/dist/widgets/video/video.d.ts +2 -2
- package/package.json +1 -1
- package/dist/widgets/categorizer/categorizer.types.d.ts +0 -5
- package/dist/widgets/dropdown/dropdown.types.d.ts +0 -5
- package/dist/widgets/input-number/input-number.types.d.ts +0 -2
- package/dist/widgets/numeric-input/numeric-input.types.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -366,7 +366,10 @@ const mockStrings = {
|
|
|
366
366
|
cos: "cos",
|
|
367
367
|
tan: "tan",
|
|
368
368
|
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
369
|
-
simulationLocaleWarning: "Sorry, this simulation isn't available in your language."
|
|
369
|
+
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
370
|
+
addPoint: "Add Point",
|
|
371
|
+
removePoint: "Remove Point",
|
|
372
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
|
|
370
373
|
};
|
|
371
374
|
|
|
372
375
|
/**
|
|
@@ -862,7 +865,7 @@ const denylist = [
|
|
|
862
865
|
// (technically the renderer still adds them)
|
|
863
866
|
"key", "ref",
|
|
864
867
|
// added by src/renderer.jsx
|
|
865
|
-
"containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "questionCompleted", "findWidgets",
|
|
868
|
+
"containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "widgetIsOpen", "questionCompleted", "findWidgets",
|
|
866
869
|
// added by src/editor.jsx, for widgets removing themselves
|
|
867
870
|
// this is soooo not the right place for this, but alas.
|
|
868
871
|
"onRemove",
|
|
@@ -870,17 +873,6 @@ const denylist = [
|
|
|
870
873
|
"id",
|
|
871
874
|
// Callbacks and items for interaction handling
|
|
872
875
|
"onBlur", "onFocus", "trackInteraction", "keypadElement"];
|
|
873
|
-
const removeDenylistProps = props => {
|
|
874
|
-
const newProps = {
|
|
875
|
-
...props
|
|
876
|
-
};
|
|
877
|
-
for (const prop of denylist) {
|
|
878
|
-
if (prop in newProps) {
|
|
879
|
-
delete newProps[prop];
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
return newProps;
|
|
883
|
-
};
|
|
884
876
|
|
|
885
877
|
/* eslint-disable @babel/no-invalid-this */
|
|
886
878
|
const USAGE = "Usage:\n" + " this.change({propName: 5}, callback);\n" + ' this.change("propName", 5, callback);\n' + ' this.change("propName")';
|
|
@@ -1142,8 +1134,6 @@ class StubTagEditor extends React__namespace.Component {
|
|
|
1142
1134
|
* * nothing if it is purely a bug fix.
|
|
1143
1135
|
*
|
|
1144
1136
|
* Callbacks passed to Renderer/ItemRenderer:
|
|
1145
|
-
* * onInputError:
|
|
1146
|
-
* Called when there is an error grading a widget
|
|
1147
1137
|
* * onFocusChange: (newFocusPath, oldFocusPath, keypadDOMNode)
|
|
1148
1138
|
* Called when the user focus changes. The first two parameters are `path`
|
|
1149
1139
|
* arrays uniquely identifying the respect inputs. The third parameter,
|
|
@@ -1166,7 +1156,6 @@ class StubTagEditor extends React__namespace.Component {
|
|
|
1166
1156
|
const ApiOptions = {
|
|
1167
1157
|
propTypes: PropTypes__default["default"].shape({
|
|
1168
1158
|
isArticle: PropTypes__default["default"].bool.isRequired,
|
|
1169
|
-
onInputError: PropTypes__default["default"].func.isRequired,
|
|
1170
1159
|
onFocusChange: PropTypes__default["default"].func.isRequired,
|
|
1171
1160
|
GroupMetadataEditor: PropTypes__default["default"].func.isRequired,
|
|
1172
1161
|
showAlignmentOptions: PropTypes__default["default"].bool.isRequired,
|
|
@@ -1250,7 +1239,6 @@ const ApiOptions = {
|
|
|
1250
1239
|
defaults: {
|
|
1251
1240
|
isArticle: false,
|
|
1252
1241
|
isMobile: false,
|
|
1253
|
-
onInputError: function () {},
|
|
1254
1242
|
onFocusChange: function () {},
|
|
1255
1243
|
GroupMetadataEditor: StubTagEditor,
|
|
1256
1244
|
showAlignmentOptions: false,
|
|
@@ -2262,10 +2250,7 @@ const getDecimalSeparator = locale => {
|
|
|
2262
2250
|
* show the user an error. TODO(joel) - what error?
|
|
2263
2251
|
* - Otherwise, pass through the resulting points and message.
|
|
2264
2252
|
*/
|
|
2265
|
-
function expressionValidator(userInput, rubric) {
|
|
2266
|
-
let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
|
2267
|
-
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2268
|
-
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2253
|
+
function expressionValidator(userInput, rubric, strings, locale) {
|
|
2269
2254
|
const options = ___default["default"].clone(rubric);
|
|
2270
2255
|
___default["default"].extend(options, {
|
|
2271
2256
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2365,14 +2350,9 @@ function expressionValidator(userInput, rubric) {
|
|
|
2365
2350
|
};
|
|
2366
2351
|
}
|
|
2367
2352
|
if (matchingAnswerForm.considered === "ungraded") {
|
|
2368
|
-
// We matched an ungraded answer form - return "invalid", which
|
|
2369
|
-
// will let the user try again with no penalty
|
|
2370
|
-
const apiResult = onInputError(null,
|
|
2371
|
-
// Reserved for some widget identifier
|
|
2372
|
-
userInput, matchMessage);
|
|
2373
2353
|
return {
|
|
2374
2354
|
type: "invalid",
|
|
2375
|
-
message:
|
|
2355
|
+
message: matchMessage
|
|
2376
2356
|
};
|
|
2377
2357
|
}
|
|
2378
2358
|
// We matched a graded answer form, so we can now tell the user
|
|
@@ -2422,11 +2402,8 @@ class Expression extends React__namespace.Component {
|
|
|
2422
2402
|
_isMounted = false;
|
|
2423
2403
|
|
|
2424
2404
|
// TODO remove this in favor of just using expressionValidator
|
|
2425
|
-
static validate(userInput, rubric) {
|
|
2426
|
-
|
|
2427
|
-
let strings = arguments.length > 3 ? arguments[3] : undefined;
|
|
2428
|
-
let locale = arguments.length > 4 ? arguments[4] : undefined;
|
|
2429
|
-
return expressionValidator(userInput, rubric, onInputError, strings, locale);
|
|
2405
|
+
static validate(userInput, rubric, strings, locale) {
|
|
2406
|
+
return expressionValidator(userInput, rubric, strings, locale);
|
|
2430
2407
|
}
|
|
2431
2408
|
static getUserInputFromProps(props) {
|
|
2432
2409
|
return normalizeTex(props.value);
|
|
@@ -2487,14 +2464,9 @@ class Expression extends React__namespace.Component {
|
|
|
2487
2464
|
showErrorStyle: false
|
|
2488
2465
|
});
|
|
2489
2466
|
if (!this.parse(this.props.value, this.props).parsed) {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (apiResult !== false) {
|
|
2494
|
-
this.setState({
|
|
2495
|
-
invalid: true
|
|
2496
|
-
});
|
|
2497
|
-
}
|
|
2467
|
+
this.setState({
|
|
2468
|
+
invalid: true
|
|
2469
|
+
});
|
|
2498
2470
|
}
|
|
2499
2471
|
}
|
|
2500
2472
|
};
|
|
@@ -2508,12 +2480,12 @@ class Expression extends React__namespace.Component {
|
|
|
2508
2480
|
});
|
|
2509
2481
|
}
|
|
2510
2482
|
};
|
|
2511
|
-
simpleValidate =
|
|
2483
|
+
simpleValidate = _ref => {
|
|
2512
2484
|
let {
|
|
2513
2485
|
scoring,
|
|
2514
2486
|
...rubric
|
|
2515
2487
|
} = _ref;
|
|
2516
|
-
const score = expressionValidator(this.getUserInput(), rubric,
|
|
2488
|
+
const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
|
|
2517
2489
|
|
|
2518
2490
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
2519
2491
|
// otherwise, we may just be checking validity after changes.
|
|
@@ -2528,9 +2500,9 @@ class Expression extends React__namespace.Component {
|
|
|
2528
2500
|
}
|
|
2529
2501
|
return score;
|
|
2530
2502
|
};
|
|
2531
|
-
getUserInput
|
|
2503
|
+
getUserInput() {
|
|
2532
2504
|
return Expression.getUserInputFromProps(this.props);
|
|
2533
|
-
}
|
|
2505
|
+
}
|
|
2534
2506
|
change = (() => {
|
|
2535
2507
|
var _this = this;
|
|
2536
2508
|
return function () {
|
|
@@ -3282,7 +3254,7 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
3282
3254
|
/**
|
|
3283
3255
|
* Approximate equality on numbers and primitives.
|
|
3284
3256
|
*/
|
|
3285
|
-
function eq$
|
|
3257
|
+
function eq$4(x, y) {
|
|
3286
3258
|
if (typeof x === "number" && typeof y === "number") {
|
|
3287
3259
|
return Math.abs(x - y) < 1e-9;
|
|
3288
3260
|
}
|
|
@@ -3309,7 +3281,7 @@ function deepEq$5(x, y) {
|
|
|
3309
3281
|
return false;
|
|
3310
3282
|
}
|
|
3311
3283
|
if (typeof x === "function" && typeof y === "function") {
|
|
3312
|
-
return eq$
|
|
3284
|
+
return eq$4(x, y);
|
|
3313
3285
|
}
|
|
3314
3286
|
if (typeof x === "function" || typeof y === "function") {
|
|
3315
3287
|
return false;
|
|
@@ -3326,7 +3298,7 @@ function deepEq$5(x, y) {
|
|
|
3326
3298
|
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
3327
3299
|
return false;
|
|
3328
3300
|
}
|
|
3329
|
-
return eq$
|
|
3301
|
+
return eq$4(x, y);
|
|
3330
3302
|
}
|
|
3331
3303
|
|
|
3332
3304
|
/**
|
|
@@ -3624,7 +3596,7 @@ const Util = {
|
|
|
3624
3596
|
gridStepFromTickStep,
|
|
3625
3597
|
tickStepFromNumTicks,
|
|
3626
3598
|
constrainedTickStepsFromTickSteps,
|
|
3627
|
-
eq: eq$
|
|
3599
|
+
eq: eq$4,
|
|
3628
3600
|
deepEq: deepEq$5,
|
|
3629
3601
|
parseQueryString,
|
|
3630
3602
|
updateQueryString: updateQueryString$2,
|
|
@@ -10949,7 +10921,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10949
10921
|
* A collection of geomtry-related utility functions
|
|
10950
10922
|
*/
|
|
10951
10923
|
const {
|
|
10952
|
-
eq: eq$
|
|
10924
|
+
eq: eq$3,
|
|
10953
10925
|
deepEq: deepEq$4
|
|
10954
10926
|
} = Util;
|
|
10955
10927
|
|
|
@@ -10959,7 +10931,7 @@ const {
|
|
|
10959
10931
|
|
|
10960
10932
|
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10961
10933
|
function sign(val) {
|
|
10962
|
-
if (eq$
|
|
10934
|
+
if (eq$3(val, 0)) {
|
|
10963
10935
|
return 0;
|
|
10964
10936
|
}
|
|
10965
10937
|
return val > 0 ? 1 : -1;
|
|
@@ -10971,7 +10943,7 @@ function ccw(a, b, c) {
|
|
|
10971
10943
|
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10972
10944
|
}
|
|
10973
10945
|
function collinear(a, b, c) {
|
|
10974
|
-
return eq$
|
|
10946
|
+
return eq$3(ccw(a, b, c), 0);
|
|
10975
10947
|
}
|
|
10976
10948
|
|
|
10977
10949
|
// Given rect bounding points A and B, whether point C is inside the rect
|
|
@@ -11116,7 +11088,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
11116
11088
|
return pair[0] / pair[1];
|
|
11117
11089
|
});
|
|
11118
11090
|
const same = ___default["default"].all(factors, function (factor) {
|
|
11119
|
-
return eq$
|
|
11091
|
+
return eq$3(factors[0], factor);
|
|
11120
11092
|
});
|
|
11121
11093
|
const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
|
|
11122
11094
|
return kmath.number.equal(pair[0], pair[1], tolerance);
|
|
@@ -11169,7 +11141,7 @@ function rotate(array, n) {
|
|
|
11169
11141
|
return array.slice(n).concat(array.slice(0, n));
|
|
11170
11142
|
}
|
|
11171
11143
|
function getLineEquation(first, second) {
|
|
11172
|
-
if (eq$
|
|
11144
|
+
if (eq$3(first[0], second[0])) {
|
|
11173
11145
|
return "x = " + first[0].toFixed(3);
|
|
11174
11146
|
}
|
|
11175
11147
|
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
@@ -14815,7 +14787,7 @@ const pluck = function (table, subKey) {
|
|
|
14815
14787
|
* });
|
|
14816
14788
|
* {a: 2, b: 3}
|
|
14817
14789
|
*/
|
|
14818
|
-
const mapObject$
|
|
14790
|
+
const mapObject$1 = function (obj, lambda) {
|
|
14819
14791
|
const result = {};
|
|
14820
14792
|
___default["default"].each(___default["default"].keys(obj), function (key) {
|
|
14821
14793
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
@@ -14841,7 +14813,7 @@ const mapObjectFromArray$1 = function (arr, lambda) {
|
|
|
14841
14813
|
};
|
|
14842
14814
|
var objective_ = {
|
|
14843
14815
|
pluck: pluck,
|
|
14844
|
-
mapObject: mapObject$
|
|
14816
|
+
mapObject: mapObject$1,
|
|
14845
14817
|
mapObjectFromArray: mapObjectFromArray$1
|
|
14846
14818
|
};
|
|
14847
14819
|
|
|
@@ -19789,7 +19761,7 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
19789
19761
|
}
|
|
19790
19762
|
|
|
19791
19763
|
const {
|
|
19792
|
-
mapObject
|
|
19764
|
+
mapObject,
|
|
19793
19765
|
mapObjectFromArray
|
|
19794
19766
|
} = objective_;
|
|
19795
19767
|
const rContainsNonWhitespace = /\S/;
|
|
@@ -19817,9 +19789,6 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
|
|
|
19817
19789
|
});
|
|
19818
19790
|
};
|
|
19819
19791
|
|
|
19820
|
-
// The return type for getUserInput. Widgets have full control of what is
|
|
19821
|
-
// returned so it's not easily typed (some widgets return a scalar (string),
|
|
19822
|
-
// some return a custom-built object
|
|
19823
19792
|
/**
|
|
19824
19793
|
* The Widget type represents the common API that the Renderer uses to interact
|
|
19825
19794
|
* with all widgets. All widgets must implement the methods in this API, unless
|
|
@@ -19829,6 +19798,7 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
|
|
|
19829
19798
|
* communicate with the individual widgets to coordinate actions such as
|
|
19830
19799
|
* scoring, state serialization/deserialization, and focus management.
|
|
19831
19800
|
*/
|
|
19801
|
+
|
|
19832
19802
|
class Renderer extends React__namespace.Component {
|
|
19833
19803
|
_currentFocus;
|
|
19834
19804
|
// @ts-expect-error - TS2564 - Property '_foundTextNodes' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -20025,7 +19995,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20025
19995
|
|
|
20026
19996
|
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
20027
19997
|
_getAllWidgetsInfo = props => {
|
|
20028
|
-
return mapObject
|
|
19998
|
+
return mapObject(props.widgets, (widgetInfo, widgetId) => {
|
|
20029
19999
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
20030
20000
|
const newValues = {};
|
|
20031
20001
|
if (!widgetInfo.type) {
|
|
@@ -20172,7 +20142,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20172
20142
|
* state from that instead of the current widget props.
|
|
20173
20143
|
*/
|
|
20174
20144
|
getSerializedState = widgetProps => {
|
|
20175
|
-
return mapObject
|
|
20145
|
+
return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
20176
20146
|
const widget = this.getWidgetInstance(widgetId);
|
|
20177
20147
|
if (widget && widget.getSerializedState) {
|
|
20178
20148
|
return widget.getSerializedState();
|
|
@@ -20208,7 +20178,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20208
20178
|
}
|
|
20209
20179
|
};
|
|
20210
20180
|
this.setState({
|
|
20211
|
-
widgetProps: mapObject
|
|
20181
|
+
widgetProps: mapObject(serializedState, (props, widgetId) => {
|
|
20212
20182
|
const widget = this.getWidgetInstance(widgetId);
|
|
20213
20183
|
if (widget && widget.restoreSerializedState) {
|
|
20214
20184
|
// Note that we probably can't call
|
|
@@ -21083,7 +21053,6 @@ class Renderer extends React__namespace.Component {
|
|
|
21083
21053
|
*/
|
|
21084
21054
|
scoreWidgets = () => {
|
|
21085
21055
|
const widgetProps = this.state.widgetInfo;
|
|
21086
|
-
const onInputError = this.getApiOptions().onInputError;
|
|
21087
21056
|
const gradedWidgetIds = ___default["default"].filter(this.widgetIds, id => {
|
|
21088
21057
|
const props = widgetProps[id];
|
|
21089
21058
|
const widgetIsGraded = props?.graded == null || props.graded;
|
|
@@ -21100,7 +21069,7 @@ class Renderer extends React__namespace.Component {
|
|
|
21100
21069
|
widgetScores[id] = widget.simpleValidate({
|
|
21101
21070
|
...props?.options,
|
|
21102
21071
|
scoring: true
|
|
21103
|
-
}
|
|
21072
|
+
});
|
|
21104
21073
|
}
|
|
21105
21074
|
});
|
|
21106
21075
|
return widgetScores;
|
|
@@ -21973,7 +21942,6 @@ const answerTypes = {
|
|
|
21973
21942
|
}
|
|
21974
21943
|
};
|
|
21975
21944
|
function inputNumberValidator(state, rubric, strings) {
|
|
21976
|
-
let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
|
|
21977
21945
|
if (rubric.answerType == null) {
|
|
21978
21946
|
rubric.answerType = "number";
|
|
21979
21947
|
}
|
|
@@ -21997,13 +21965,9 @@ function inputNumberValidator(state, rubric, strings) {
|
|
|
21997
21965
|
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
21998
21966
|
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
21999
21967
|
if (result.empty) {
|
|
22000
|
-
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
22001
|
-
const apiResult = onInputError?.(null,
|
|
22002
|
-
// reserved for some widget identifier
|
|
22003
|
-
state.currentValue, result.message);
|
|
22004
21968
|
return {
|
|
22005
21969
|
type: "invalid",
|
|
22006
|
-
message:
|
|
21970
|
+
message: result.message
|
|
22007
21971
|
};
|
|
22008
21972
|
}
|
|
22009
21973
|
return {
|
|
@@ -22056,8 +22020,7 @@ class InputNumber extends React__namespace.Component {
|
|
|
22056
22020
|
linterContext: PerseusLinter.linterContextDefault
|
|
22057
22021
|
};
|
|
22058
22022
|
static validate(state, rubric, strings) {
|
|
22059
|
-
|
|
22060
|
-
return inputNumberValidator(state, rubric, strings, onInputError);
|
|
22023
|
+
return inputNumberValidator(state, rubric, strings);
|
|
22061
22024
|
}
|
|
22062
22025
|
static getUserInputFromProps(props) {
|
|
22063
22026
|
return {
|
|
@@ -22126,13 +22089,12 @@ class InputNumber extends React__namespace.Component {
|
|
|
22126
22089
|
currentValue: newValue
|
|
22127
22090
|
}, cb);
|
|
22128
22091
|
};
|
|
22129
|
-
getUserInput
|
|
22092
|
+
getUserInput() {
|
|
22130
22093
|
return InputNumber.getUserInputFromProps(this.props);
|
|
22131
|
-
}
|
|
22132
|
-
simpleValidate
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
};
|
|
22094
|
+
}
|
|
22095
|
+
simpleValidate(rubric) {
|
|
22096
|
+
return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22097
|
+
}
|
|
22136
22098
|
examples = () => {
|
|
22137
22099
|
const {
|
|
22138
22100
|
strings
|
|
@@ -22477,9 +22439,9 @@ class NumericInput extends React__namespace.Component {
|
|
|
22477
22439
|
const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
|
|
22478
22440
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22479
22441
|
};
|
|
22480
|
-
simpleValidate
|
|
22442
|
+
simpleValidate(rubric) {
|
|
22481
22443
|
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22482
|
-
}
|
|
22444
|
+
}
|
|
22483
22445
|
focus = () => {
|
|
22484
22446
|
this.inputRef?.focus();
|
|
22485
22447
|
return true;
|
|
@@ -22506,9 +22468,9 @@ class NumericInput extends React__namespace.Component {
|
|
|
22506
22468
|
currentValue: newValue
|
|
22507
22469
|
}, cb);
|
|
22508
22470
|
};
|
|
22509
|
-
getUserInput
|
|
22471
|
+
getUserInput() {
|
|
22510
22472
|
return NumericInput.getUserInputFromProps(this.props);
|
|
22511
|
-
}
|
|
22473
|
+
}
|
|
22512
22474
|
handleChange = (newValue, cb) => {
|
|
22513
22475
|
this.props.onChange({
|
|
22514
22476
|
currentValue: newValue
|
|
@@ -22695,11 +22657,10 @@ class PassageRef extends React__namespace.Component {
|
|
|
22695
22657
|
this._isMounted = false;
|
|
22696
22658
|
}
|
|
22697
22659
|
|
|
22698
|
-
// TODO(
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
};
|
|
22660
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22661
|
+
getUserInput() {
|
|
22662
|
+
return null;
|
|
22663
|
+
}
|
|
22703
22664
|
change = (() => {
|
|
22704
22665
|
var _this = this;
|
|
22705
22666
|
return function () {
|
|
@@ -24143,6 +24104,56 @@ const styles$t = aphrodite.StyleSheet.create({
|
|
|
24143
24104
|
}
|
|
24144
24105
|
});
|
|
24145
24106
|
|
|
24107
|
+
function radioValidator(userInput, rubric, strings) {
|
|
24108
|
+
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
24109
|
+
return sum + (selected ? 1 : 0);
|
|
24110
|
+
}, 0);
|
|
24111
|
+
if (numSelected === 0) {
|
|
24112
|
+
return {
|
|
24113
|
+
type: "invalid",
|
|
24114
|
+
message: null
|
|
24115
|
+
};
|
|
24116
|
+
}
|
|
24117
|
+
|
|
24118
|
+
// TODO: should numCorrect actually be on the rubric
|
|
24119
|
+
// instead of the userInput?
|
|
24120
|
+
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
24121
|
+
return {
|
|
24122
|
+
type: "invalid",
|
|
24123
|
+
message: strings.chooseCorrectNum
|
|
24124
|
+
};
|
|
24125
|
+
// If NOTA and some other answer are checked, ...
|
|
24126
|
+
}
|
|
24127
|
+
|
|
24128
|
+
// TODO: should noneOfTheAboveSelected be replaced with a
|
|
24129
|
+
// combination of choicesSelected and noneOfTheAboveIndex?
|
|
24130
|
+
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
24131
|
+
return {
|
|
24132
|
+
type: "invalid",
|
|
24133
|
+
message: strings.notNoneOfTheAbove
|
|
24134
|
+
};
|
|
24135
|
+
}
|
|
24136
|
+
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
24137
|
+
let isCorrect;
|
|
24138
|
+
// TODO: should noneOfTheAboveIndex actually be on the rubric
|
|
24139
|
+
// instead of the userInput?
|
|
24140
|
+
if (userInput.noneOfTheAboveIndex === i) {
|
|
24141
|
+
isCorrect = rubric.choices.every((choice, j) => {
|
|
24142
|
+
return i === j || !choice.correct;
|
|
24143
|
+
});
|
|
24144
|
+
} else {
|
|
24145
|
+
isCorrect = !!rubric.choices[i].correct;
|
|
24146
|
+
}
|
|
24147
|
+
return isCorrect === selected;
|
|
24148
|
+
});
|
|
24149
|
+
return {
|
|
24150
|
+
type: "points",
|
|
24151
|
+
earned: correct ? 1 : 0,
|
|
24152
|
+
total: 1,
|
|
24153
|
+
message: null
|
|
24154
|
+
};
|
|
24155
|
+
}
|
|
24156
|
+
|
|
24146
24157
|
// RenderProps is the return type for radio.jsx#transform
|
|
24147
24158
|
|
|
24148
24159
|
class Radio$1 extends React__namespace.Component {
|
|
@@ -24158,46 +24169,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
24158
24169
|
showSolutions: "none"
|
|
24159
24170
|
};
|
|
24160
24171
|
static validate(userInput, rubric, strings) {
|
|
24161
|
-
|
|
24162
|
-
return sum + (selected ? 1 : 0);
|
|
24163
|
-
}, 0);
|
|
24164
|
-
if (numSelected === 0) {
|
|
24165
|
-
return {
|
|
24166
|
-
type: "invalid",
|
|
24167
|
-
message: null
|
|
24168
|
-
};
|
|
24169
|
-
}
|
|
24170
|
-
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
24171
|
-
return {
|
|
24172
|
-
type: "invalid",
|
|
24173
|
-
message: strings.chooseCorrectNum
|
|
24174
|
-
};
|
|
24175
|
-
// If NOTA and some other answer are checked, ...
|
|
24176
|
-
}
|
|
24177
|
-
|
|
24178
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
24179
|
-
return {
|
|
24180
|
-
type: "invalid",
|
|
24181
|
-
message: strings.notNoneOfTheAbove
|
|
24182
|
-
};
|
|
24183
|
-
}
|
|
24184
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
24185
|
-
let isCorrect;
|
|
24186
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
24187
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
24188
|
-
return i === j || !choice.correct;
|
|
24189
|
-
});
|
|
24190
|
-
} else {
|
|
24191
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
24192
|
-
}
|
|
24193
|
-
return isCorrect === selected;
|
|
24194
|
-
});
|
|
24195
|
-
return {
|
|
24196
|
-
type: "points",
|
|
24197
|
-
earned: correct ? 1 : 0,
|
|
24198
|
-
total: 1,
|
|
24199
|
-
message: null
|
|
24200
|
-
};
|
|
24172
|
+
return radioValidator(userInput, rubric, strings);
|
|
24201
24173
|
}
|
|
24202
24174
|
static getUserInputFromProps(props) {
|
|
24203
24175
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
@@ -24382,12 +24354,12 @@ class Radio$1 extends React__namespace.Component {
|
|
|
24382
24354
|
});
|
|
24383
24355
|
this.props.trackInteraction();
|
|
24384
24356
|
};
|
|
24385
|
-
getUserInput
|
|
24357
|
+
getUserInput() {
|
|
24386
24358
|
return Radio$1.getUserInputFromProps(this.props);
|
|
24387
|
-
}
|
|
24388
|
-
simpleValidate
|
|
24389
|
-
return
|
|
24390
|
-
}
|
|
24359
|
+
}
|
|
24360
|
+
simpleValidate(rubric) {
|
|
24361
|
+
return radioValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24362
|
+
}
|
|
24391
24363
|
|
|
24392
24364
|
/**
|
|
24393
24365
|
* Turn on rationale display for the currently selected choices. Note that
|
|
@@ -24741,9 +24713,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
24741
24713
|
return change.apply(_this, args);
|
|
24742
24714
|
};
|
|
24743
24715
|
})();
|
|
24744
|
-
getUserInput
|
|
24716
|
+
getUserInput() {
|
|
24745
24717
|
return Categorizer.getUserInputFromProps(this.props);
|
|
24746
|
-
}
|
|
24718
|
+
}
|
|
24747
24719
|
onChange(itemNum, catNum) {
|
|
24748
24720
|
const values = [...this.props.values];
|
|
24749
24721
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -24751,9 +24723,9 @@ class Categorizer extends React__namespace.Component {
|
|
|
24751
24723
|
this.change("values", values);
|
|
24752
24724
|
this.props.trackInteraction();
|
|
24753
24725
|
}
|
|
24754
|
-
simpleValidate
|
|
24726
|
+
simpleValidate(rubric) {
|
|
24755
24727
|
return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24756
|
-
}
|
|
24728
|
+
}
|
|
24757
24729
|
render() {
|
|
24758
24730
|
const self = this;
|
|
24759
24731
|
|
|
@@ -24942,6 +24914,31 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
24942
24914
|
*/
|
|
24943
24915
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
24944
24916
|
|
|
24917
|
+
function csProgramValidator(state) {
|
|
24918
|
+
// The iframe can tell us whether it's correct or incorrect,
|
|
24919
|
+
// and pass an optional message
|
|
24920
|
+
if (state.status === "correct") {
|
|
24921
|
+
return {
|
|
24922
|
+
type: "points",
|
|
24923
|
+
earned: 1,
|
|
24924
|
+
total: 1,
|
|
24925
|
+
message: state.message || null
|
|
24926
|
+
};
|
|
24927
|
+
}
|
|
24928
|
+
if (state.status === "incorrect") {
|
|
24929
|
+
return {
|
|
24930
|
+
type: "points",
|
|
24931
|
+
earned: 0,
|
|
24932
|
+
total: 1,
|
|
24933
|
+
message: state.message || null
|
|
24934
|
+
};
|
|
24935
|
+
}
|
|
24936
|
+
return {
|
|
24937
|
+
type: "invalid",
|
|
24938
|
+
message: "Keep going, you're not there yet!"
|
|
24939
|
+
};
|
|
24940
|
+
}
|
|
24941
|
+
|
|
24945
24942
|
/**
|
|
24946
24943
|
* This widget is for embedding Khan Academy CS programs.
|
|
24947
24944
|
*/
|
|
@@ -24974,29 +24971,8 @@ class CSProgram extends React__namespace.Component {
|
|
|
24974
24971
|
};
|
|
24975
24972
|
|
|
24976
24973
|
// The widget's grading function
|
|
24977
|
-
static validate(state
|
|
24978
|
-
|
|
24979
|
-
// and pass an optional message
|
|
24980
|
-
if (state.status === "correct") {
|
|
24981
|
-
return {
|
|
24982
|
-
type: "points",
|
|
24983
|
-
earned: 1,
|
|
24984
|
-
total: 1,
|
|
24985
|
-
message: state.message || null
|
|
24986
|
-
};
|
|
24987
|
-
}
|
|
24988
|
-
if (state.status === "incorrect") {
|
|
24989
|
-
return {
|
|
24990
|
-
type: "points",
|
|
24991
|
-
earned: 0,
|
|
24992
|
-
total: 1,
|
|
24993
|
-
message: state.message || null
|
|
24994
|
-
};
|
|
24995
|
-
}
|
|
24996
|
-
return {
|
|
24997
|
-
type: "invalid",
|
|
24998
|
-
message: "Keep going, you're not there yet!"
|
|
24999
|
-
};
|
|
24974
|
+
static validate(state) {
|
|
24975
|
+
return csProgramValidator(state);
|
|
25000
24976
|
}
|
|
25001
24977
|
componentDidMount() {
|
|
25002
24978
|
$__default["default"](window).on("message", this.handleMessageEvent);
|
|
@@ -25033,11 +25009,11 @@ class CSProgram extends React__namespace.Component {
|
|
|
25033
25009
|
return change.apply(_this, args);
|
|
25034
25010
|
};
|
|
25035
25011
|
})();
|
|
25036
|
-
simpleValidate(
|
|
25037
|
-
return
|
|
25012
|
+
simpleValidate() {
|
|
25013
|
+
return csProgramValidator({
|
|
25038
25014
|
status: this.props.status,
|
|
25039
25015
|
message: this.props.message
|
|
25040
|
-
}
|
|
25016
|
+
});
|
|
25041
25017
|
}
|
|
25042
25018
|
render() {
|
|
25043
25019
|
if (!this.props.programID) {
|
|
@@ -25127,9 +25103,9 @@ class Definition extends React__namespace.Component {
|
|
|
25127
25103
|
static validate() {
|
|
25128
25104
|
return noopValidator();
|
|
25129
25105
|
}
|
|
25130
|
-
getUserInput
|
|
25106
|
+
getUserInput() {
|
|
25131
25107
|
return {};
|
|
25132
|
-
}
|
|
25108
|
+
}
|
|
25133
25109
|
|
|
25134
25110
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25135
25111
|
simpleValidate() {
|
|
@@ -25272,14 +25248,14 @@ class Dropdown extends React__namespace.Component {
|
|
|
25272
25248
|
selected: selected
|
|
25273
25249
|
});
|
|
25274
25250
|
};
|
|
25275
|
-
getUserInput
|
|
25251
|
+
getUserInput() {
|
|
25276
25252
|
return {
|
|
25277
25253
|
value: this.props.selected
|
|
25278
25254
|
};
|
|
25279
|
-
}
|
|
25280
|
-
simpleValidate
|
|
25255
|
+
}
|
|
25256
|
+
simpleValidate(rubric) {
|
|
25281
25257
|
return dropdownValidator(this.getUserInput(), rubric);
|
|
25282
|
-
}
|
|
25258
|
+
}
|
|
25283
25259
|
render() {
|
|
25284
25260
|
const children = [/*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.OptionItem, {
|
|
25285
25261
|
key: "placeholder",
|
|
@@ -25370,9 +25346,9 @@ class Explanation extends React__namespace.Component {
|
|
|
25370
25346
|
});
|
|
25371
25347
|
this.props.trackInteraction();
|
|
25372
25348
|
};
|
|
25373
|
-
getUserInput
|
|
25349
|
+
getUserInput() {
|
|
25374
25350
|
return {};
|
|
25375
|
-
}
|
|
25351
|
+
}
|
|
25376
25352
|
|
|
25377
25353
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25378
25354
|
simpleValidate() {
|
|
@@ -26640,49 +26616,6 @@ function functionForType(
|
|
|
26640
26616
|
type) {
|
|
26641
26617
|
return functionTypeMapping[type];
|
|
26642
26618
|
}
|
|
26643
|
-
const validate$1 = (state, rubric) => {
|
|
26644
|
-
if (state.type !== rubric.correct.type) {
|
|
26645
|
-
return {
|
|
26646
|
-
type: "points",
|
|
26647
|
-
earned: 0,
|
|
26648
|
-
total: 1,
|
|
26649
|
-
message: null
|
|
26650
|
-
};
|
|
26651
|
-
}
|
|
26652
|
-
|
|
26653
|
-
// We haven't moved the coords
|
|
26654
|
-
if (state.coords == null) {
|
|
26655
|
-
return {
|
|
26656
|
-
type: "invalid",
|
|
26657
|
-
message: null
|
|
26658
|
-
};
|
|
26659
|
-
}
|
|
26660
|
-
|
|
26661
|
-
// Get new function handler for grading
|
|
26662
|
-
const grader = functionForType(state.type);
|
|
26663
|
-
const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
|
|
26664
|
-
const correctCoeffs = grader.getCoefficients(rubric.correct.coords, rubric.correct.asymptote);
|
|
26665
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
26666
|
-
return {
|
|
26667
|
-
type: "invalid",
|
|
26668
|
-
message: null
|
|
26669
|
-
};
|
|
26670
|
-
}
|
|
26671
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
26672
|
-
return {
|
|
26673
|
-
type: "points",
|
|
26674
|
-
earned: 1,
|
|
26675
|
-
total: 1,
|
|
26676
|
-
message: null
|
|
26677
|
-
};
|
|
26678
|
-
}
|
|
26679
|
-
return {
|
|
26680
|
-
type: "points",
|
|
26681
|
-
earned: 0,
|
|
26682
|
-
total: 1,
|
|
26683
|
-
message: null
|
|
26684
|
-
};
|
|
26685
|
-
};
|
|
26686
26619
|
const getEquationString = props => {
|
|
26687
26620
|
const plot = props.plot;
|
|
26688
26621
|
if (plot.type && plot.coords) {
|
|
@@ -26800,7 +26733,6 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26800
26733
|
DEFAULT_BACKGROUND_IMAGE: DEFAULT_BACKGROUND_IMAGE,
|
|
26801
26734
|
allTypes: allTypes,
|
|
26802
26735
|
functionForType: functionForType,
|
|
26803
|
-
validate: validate$1,
|
|
26804
26736
|
getEquationString: getEquationString,
|
|
26805
26737
|
pointsFromNormalized: pointsFromNormalized,
|
|
26806
26738
|
maybePointsFromNormalized: maybePointsFromNormalized,
|
|
@@ -26811,6 +26743,52 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26811
26743
|
typeToButton: typeToButton
|
|
26812
26744
|
});
|
|
26813
26745
|
|
|
26746
|
+
function grapherValidator(state, rubric) {
|
|
26747
|
+
if (state.type !== rubric.correct.type) {
|
|
26748
|
+
return {
|
|
26749
|
+
type: "points",
|
|
26750
|
+
earned: 0,
|
|
26751
|
+
total: 1,
|
|
26752
|
+
message: null
|
|
26753
|
+
};
|
|
26754
|
+
}
|
|
26755
|
+
|
|
26756
|
+
// We haven't moved the coords
|
|
26757
|
+
if (state.coords == null) {
|
|
26758
|
+
return {
|
|
26759
|
+
type: "invalid",
|
|
26760
|
+
message: null
|
|
26761
|
+
};
|
|
26762
|
+
}
|
|
26763
|
+
|
|
26764
|
+
// Get new function handler for grading
|
|
26765
|
+
const grader = functionForType(state.type);
|
|
26766
|
+
const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
|
|
26767
|
+
const correctCoeffs = grader.getCoefficients(rubric.correct.coords,
|
|
26768
|
+
// @ts-expect-error - TS(2339) - Property 'asymptote' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; }'
|
|
26769
|
+
rubric.correct.asymptote);
|
|
26770
|
+
if (guessCoeffs == null || correctCoeffs == null) {
|
|
26771
|
+
return {
|
|
26772
|
+
type: "invalid",
|
|
26773
|
+
message: null
|
|
26774
|
+
};
|
|
26775
|
+
}
|
|
26776
|
+
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
26777
|
+
return {
|
|
26778
|
+
type: "points",
|
|
26779
|
+
earned: 1,
|
|
26780
|
+
total: 1,
|
|
26781
|
+
message: null
|
|
26782
|
+
};
|
|
26783
|
+
}
|
|
26784
|
+
return {
|
|
26785
|
+
type: "points",
|
|
26786
|
+
earned: 0,
|
|
26787
|
+
total: 1,
|
|
26788
|
+
message: null
|
|
26789
|
+
};
|
|
26790
|
+
}
|
|
26791
|
+
|
|
26814
26792
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
26815
26793
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
26816
26794
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -27008,16 +26986,13 @@ class FunctionGrapher extends React__namespace.Component {
|
|
|
27008
26986
|
}), this.props.model && this.renderPlot(), this.props.model && this.renderAsymptote(), this.props.model && points)));
|
|
27009
26987
|
}
|
|
27010
26988
|
}
|
|
27011
|
-
|
|
27012
|
-
// Really this is props.plot, I think
|
|
27013
|
-
|
|
27014
26989
|
/* Widget and editor. */
|
|
27015
26990
|
class Grapher extends React__namespace.Component {
|
|
27016
26991
|
horizHairline;
|
|
27017
26992
|
vertHairline;
|
|
27018
26993
|
static defaultProps = DEFAULT_GRAPHER_PROPS;
|
|
27019
26994
|
static validate(state, rubric) {
|
|
27020
|
-
return
|
|
26995
|
+
return grapherValidator(state, rubric);
|
|
27021
26996
|
}
|
|
27022
26997
|
static getUserInputFromProps(props) {
|
|
27023
26998
|
return props.plot;
|
|
@@ -27122,7 +27097,7 @@ class Grapher extends React__namespace.Component {
|
|
|
27122
27097
|
}
|
|
27123
27098
|
};
|
|
27124
27099
|
simpleValidate(rubric) {
|
|
27125
|
-
return
|
|
27100
|
+
return grapherValidator(this.getUserInput(), rubric);
|
|
27126
27101
|
}
|
|
27127
27102
|
getUserInput() {
|
|
27128
27103
|
return Grapher.getUserInputFromProps(this.props);
|
|
@@ -27245,9 +27220,9 @@ class Group extends React__namespace.Component {
|
|
|
27245
27220
|
return change.apply(_this, args);
|
|
27246
27221
|
};
|
|
27247
27222
|
})();
|
|
27248
|
-
getUserInput
|
|
27223
|
+
getUserInput() {
|
|
27249
27224
|
return this.rendererRef?.getUserInput();
|
|
27250
|
-
}
|
|
27225
|
+
}
|
|
27251
27226
|
getSerializedState = () => {
|
|
27252
27227
|
return this.rendererRef?.getSerializedState();
|
|
27253
27228
|
};
|
|
@@ -27257,9 +27232,9 @@ class Group extends React__namespace.Component {
|
|
|
27257
27232
|
// (all our changes were in state):
|
|
27258
27233
|
return null;
|
|
27259
27234
|
};
|
|
27260
|
-
simpleValidate
|
|
27235
|
+
simpleValidate() {
|
|
27261
27236
|
return this.rendererRef?.score();
|
|
27262
|
-
}
|
|
27237
|
+
}
|
|
27263
27238
|
|
|
27264
27239
|
// Mobile API:
|
|
27265
27240
|
getInputPaths = () => {
|
|
@@ -27365,6 +27340,31 @@ var Group$1 = {
|
|
|
27365
27340
|
isLintable: true
|
|
27366
27341
|
};
|
|
27367
27342
|
|
|
27343
|
+
function iframeValidator(state) {
|
|
27344
|
+
// The iframe can tell us whether it's correct or incorrect,
|
|
27345
|
+
// and pass an optional message
|
|
27346
|
+
if (state.status === "correct") {
|
|
27347
|
+
return {
|
|
27348
|
+
type: "points",
|
|
27349
|
+
earned: 1,
|
|
27350
|
+
total: 1,
|
|
27351
|
+
message: state.message || null
|
|
27352
|
+
};
|
|
27353
|
+
}
|
|
27354
|
+
if (state.status === "incorrect") {
|
|
27355
|
+
return {
|
|
27356
|
+
type: "points",
|
|
27357
|
+
earned: 0,
|
|
27358
|
+
total: 1,
|
|
27359
|
+
message: state.message || null
|
|
27360
|
+
};
|
|
27361
|
+
}
|
|
27362
|
+
return {
|
|
27363
|
+
type: "invalid",
|
|
27364
|
+
message: "Keep going, you're not there yet!"
|
|
27365
|
+
};
|
|
27366
|
+
}
|
|
27367
|
+
|
|
27368
27368
|
/**
|
|
27369
27369
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
27370
27370
|
* then communicates its state via window.postMessage
|
|
@@ -27385,6 +27385,9 @@ class Iframe extends React__namespace.Component {
|
|
|
27385
27385
|
allowFullScreen: false,
|
|
27386
27386
|
allowTopNavigation: false
|
|
27387
27387
|
};
|
|
27388
|
+
static validate(state) {
|
|
27389
|
+
return iframeValidator(state);
|
|
27390
|
+
}
|
|
27388
27391
|
componentDidMount() {
|
|
27389
27392
|
$__default["default"](window).on("message", this.handleMessageEvent);
|
|
27390
27393
|
}
|
|
@@ -27426,10 +27429,9 @@ class Iframe extends React__namespace.Component {
|
|
|
27426
27429
|
return change.apply(_this, args);
|
|
27427
27430
|
};
|
|
27428
27431
|
})();
|
|
27429
|
-
simpleValidate
|
|
27430
|
-
|
|
27431
|
-
|
|
27432
|
-
};
|
|
27432
|
+
simpleValidate() {
|
|
27433
|
+
return iframeValidator(this.getUserInput());
|
|
27434
|
+
}
|
|
27433
27435
|
render() {
|
|
27434
27436
|
const style = {
|
|
27435
27437
|
width: String(this.props.width),
|
|
@@ -27492,36 +27494,6 @@ class Iframe extends React__namespace.Component {
|
|
|
27492
27494
|
});
|
|
27493
27495
|
}
|
|
27494
27496
|
}
|
|
27495
|
-
|
|
27496
|
-
/**
|
|
27497
|
-
* This is the widget's grading function
|
|
27498
|
-
*/
|
|
27499
|
-
___default["default"].extend(Iframe, {
|
|
27500
|
-
validate: function (state, rubric) {
|
|
27501
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
27502
|
-
// and pass an optional message
|
|
27503
|
-
if (state.status === "correct") {
|
|
27504
|
-
return {
|
|
27505
|
-
type: "points",
|
|
27506
|
-
earned: 1,
|
|
27507
|
-
total: 1,
|
|
27508
|
-
message: state.message || null
|
|
27509
|
-
};
|
|
27510
|
-
}
|
|
27511
|
-
if (state.status === "incorrect") {
|
|
27512
|
-
return {
|
|
27513
|
-
type: "points",
|
|
27514
|
-
earned: 0,
|
|
27515
|
-
total: 1,
|
|
27516
|
-
message: state.message || null
|
|
27517
|
-
};
|
|
27518
|
-
}
|
|
27519
|
-
return {
|
|
27520
|
-
type: "invalid",
|
|
27521
|
-
message: "Keep going, you're not there yet!"
|
|
27522
|
-
};
|
|
27523
|
-
}
|
|
27524
|
-
});
|
|
27525
27497
|
var Iframe$1 = {
|
|
27526
27498
|
name: "iframe",
|
|
27527
27499
|
displayName: "Iframe (deprecated)",
|
|
@@ -27569,9 +27541,9 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27569
27541
|
return change.apply(_this, args);
|
|
27570
27542
|
};
|
|
27571
27543
|
})();
|
|
27572
|
-
getUserInput
|
|
27544
|
+
getUserInput() {
|
|
27573
27545
|
return null;
|
|
27574
|
-
}
|
|
27546
|
+
}
|
|
27575
27547
|
|
|
27576
27548
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27577
27549
|
simpleValidate() {
|
|
@@ -27914,13 +27886,13 @@ class Interaction extends React__namespace.Component {
|
|
|
27914
27886
|
return change.apply(_this, args);
|
|
27915
27887
|
};
|
|
27916
27888
|
})();
|
|
27917
|
-
getUserInput
|
|
27889
|
+
getUserInput() {
|
|
27918
27890
|
// TODO(eater): Perhaps we want to be able to record the state of the
|
|
27919
27891
|
// user's interaction. Unfortunately sending all the props will
|
|
27920
27892
|
// probably make the attempt payload too large. So for now, don't send
|
|
27921
27893
|
// anything.
|
|
27922
27894
|
return {};
|
|
27923
|
-
}
|
|
27895
|
+
}
|
|
27924
27896
|
|
|
27925
27897
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27926
27898
|
simpleValidate() {
|
|
@@ -29054,7 +29026,8 @@ const tickLabelSize = 14;
|
|
|
29054
29026
|
const YGridTick = _ref => {
|
|
29055
29027
|
let {
|
|
29056
29028
|
y,
|
|
29057
|
-
range
|
|
29029
|
+
range,
|
|
29030
|
+
tickStep
|
|
29058
29031
|
} = _ref;
|
|
29059
29032
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
29060
29033
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -29089,9 +29062,10 @@ const YGridTick = _ref => {
|
|
|
29089
29062
|
|
|
29090
29063
|
// If the graph displays both the y and x axis lines within the graph, we want
|
|
29091
29064
|
// to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
|
|
29092
|
-
const showLabel = shouldShowLabel(y, range);
|
|
29065
|
+
const showLabel = shouldShowLabel(y, range, tickStep);
|
|
29093
29066
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
29094
|
-
className: "tick"
|
|
29067
|
+
className: "tick",
|
|
29068
|
+
"aria-hidden": true
|
|
29095
29069
|
}, /*#__PURE__*/React__namespace.createElement("line", {
|
|
29096
29070
|
x1: x1,
|
|
29097
29071
|
y1: y1,
|
|
@@ -29151,7 +29125,8 @@ const XGridTick = _ref2 => {
|
|
|
29151
29125
|
const xPositionText = xPosition + xAdjustment;
|
|
29152
29126
|
const yPositionText = yPosition + yAdjustment;
|
|
29153
29127
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
29154
|
-
className: "tick"
|
|
29128
|
+
className: "tick",
|
|
29129
|
+
"aria-hidden": true
|
|
29155
29130
|
}, /*#__PURE__*/React__namespace.createElement("line", {
|
|
29156
29131
|
x1: x1,
|
|
29157
29132
|
y1: y1,
|
|
@@ -29170,13 +29145,13 @@ const XGridTick = _ref2 => {
|
|
|
29170
29145
|
};
|
|
29171
29146
|
|
|
29172
29147
|
// Determines whether to show the label for the given tick
|
|
29173
|
-
// Currently, the only condition is to hide the label at -
|
|
29174
|
-
// on the y-axis when the
|
|
29175
|
-
const shouldShowLabel = (
|
|
29148
|
+
// Currently, the only condition is to hide the label at -tickStep
|
|
29149
|
+
// on the y-axis when the y-axis is within the graph bounds
|
|
29150
|
+
const shouldShowLabel = (currentTick, range, tickStep) => {
|
|
29176
29151
|
let showLabel = true;
|
|
29177
29152
|
|
|
29178
|
-
// If the
|
|
29179
|
-
if (range[X][MIN] < -
|
|
29153
|
+
// If the y-axis is within the graph and currentTick equals -tickStep, hide the label
|
|
29154
|
+
if (range[X][MIN] < -tickStep && range[X][MAX] > 0 && currentTick === -tickStep) {
|
|
29180
29155
|
showLabel = false;
|
|
29181
29156
|
}
|
|
29182
29157
|
return showLabel;
|
|
@@ -29218,7 +29193,8 @@ const AxisTicks = () => {
|
|
|
29218
29193
|
return /*#__PURE__*/React__namespace.createElement(YGridTick, {
|
|
29219
29194
|
y: y,
|
|
29220
29195
|
key: `y-grid-tick-${y}`,
|
|
29221
|
-
range: range
|
|
29196
|
+
range: range,
|
|
29197
|
+
tickStep: tickStep[Y]
|
|
29222
29198
|
});
|
|
29223
29199
|
})), /*#__PURE__*/React__namespace.createElement("g", {
|
|
29224
29200
|
className: "x-axis-ticks"
|
|
@@ -29973,7 +29949,8 @@ function LockedLabel(props) {
|
|
|
29973
29949
|
color: lockedFigureColors[color],
|
|
29974
29950
|
fontSize: wonderBlocksTokens.font.size[size],
|
|
29975
29951
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
29976
|
-
}
|
|
29952
|
+
},
|
|
29953
|
+
"aria-hidden": true
|
|
29977
29954
|
}, /*#__PURE__*/React__namespace.createElement(TeX, null, text));
|
|
29978
29955
|
}
|
|
29979
29956
|
|
|
@@ -29992,14 +29969,22 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29992
29969
|
// Point flag + point type
|
|
29993
29970
|
flags?.["mafs"]?.["locked-point-labels"] && figure.type === "point" ||
|
|
29994
29971
|
// Line flag + line type
|
|
29995
|
-
flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line"
|
|
29972
|
+
flags?.["mafs"]?.["locked-line-labels"] && figure.type === "line" ||
|
|
29973
|
+
// Vector flag + vector type
|
|
29974
|
+
flags?.["mafs"]?.["locked-vector-labels"] && figure.type === "vector" ||
|
|
29975
|
+
// Ellipse flag + ellipse type
|
|
29976
|
+
flags?.["mafs"]?.["locked-ellipse-labels"] && figure.type === "ellipse" ||
|
|
29977
|
+
// Polygon flag + polygon type
|
|
29978
|
+
flags?.["mafs"]?.["locked-polygon-labels"] && figure.type === "polygon" ||
|
|
29979
|
+
// Function flag + function type
|
|
29980
|
+
flags?.["mafs"]?.["locked-function-labels"] && figure.type === "function") {
|
|
29996
29981
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, {
|
|
29997
29982
|
key: i
|
|
29998
|
-
}, figure.labels
|
|
29983
|
+
}, figure.labels?.map((label, j) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
29999
29984
|
key: `${i}-label-${j}`
|
|
30000
|
-
}, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels
|
|
29985
|
+
}, label))), figure.type === "line" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, figure.points[0].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
30001
29986
|
key: `locked-figure-${i}-point-1-label-${k}`
|
|
30002
|
-
}, label))), figure.points[1].labels
|
|
29987
|
+
}, label))), figure.points[1].labels?.map((label, k) => /*#__PURE__*/React__namespace.createElement(LockedLabel, _extends({
|
|
30003
29988
|
key: `locked-figure-${i}-point-2-label-${k}`
|
|
30004
29989
|
}, label)))));
|
|
30005
29990
|
}
|
|
@@ -30014,9 +29999,16 @@ const LockedEllipse = props => {
|
|
|
30014
29999
|
angle,
|
|
30015
30000
|
color,
|
|
30016
30001
|
fillStyle,
|
|
30017
|
-
strokeStyle
|
|
30002
|
+
strokeStyle,
|
|
30003
|
+
ariaLabel,
|
|
30004
|
+
flags
|
|
30018
30005
|
} = props;
|
|
30019
|
-
|
|
30006
|
+
const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
|
|
30007
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30008
|
+
className: "locked-ellipse",
|
|
30009
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
30010
|
+
"aria-hidden": !hasAria
|
|
30011
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Ellipse, {
|
|
30020
30012
|
center: center,
|
|
30021
30013
|
radius: radius,
|
|
30022
30014
|
angle: angle,
|
|
@@ -30032,7 +30024,7 @@ const LockedEllipse = props => {
|
|
|
30032
30024
|
fill: fillStyle === "white" ? wonderBlocksTokens.color.white : lockedFigureColors[color]
|
|
30033
30025
|
}
|
|
30034
30026
|
}
|
|
30035
|
-
});
|
|
30027
|
+
}));
|
|
30036
30028
|
};
|
|
30037
30029
|
|
|
30038
30030
|
const LockedFunction = props => {
|
|
@@ -30048,6 +30040,7 @@ const LockedFunction = props => {
|
|
|
30048
30040
|
style: strokeStyle,
|
|
30049
30041
|
domain
|
|
30050
30042
|
};
|
|
30043
|
+
const hasAria = props.ariaLabel && props.flags?.["mafs"]?.["locked-figures-aria"];
|
|
30051
30044
|
React.useEffect(() => {
|
|
30052
30045
|
// Parsing the equation in a "useEffect" hook saves about 2ms each frame
|
|
30053
30046
|
// when the learner is interacting with the graph (i.e. moving points).
|
|
@@ -30057,7 +30050,9 @@ const LockedFunction = props => {
|
|
|
30057
30050
|
return null;
|
|
30058
30051
|
}
|
|
30059
30052
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30060
|
-
className: "locked-function"
|
|
30053
|
+
className: "locked-function",
|
|
30054
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
30055
|
+
"aria-hidden": !hasAria
|
|
30061
30056
|
}, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
|
|
30062
30057
|
y: x => equation.eval({
|
|
30063
30058
|
x
|
|
@@ -30157,9 +30152,12 @@ const LockedLine = props => {
|
|
|
30157
30152
|
points,
|
|
30158
30153
|
showPoint1,
|
|
30159
30154
|
showPoint2,
|
|
30155
|
+
ariaLabel,
|
|
30156
|
+
flags,
|
|
30160
30157
|
range
|
|
30161
30158
|
} = props;
|
|
30162
30159
|
const [point1, point2] = points;
|
|
30160
|
+
const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
|
|
30163
30161
|
let line;
|
|
30164
30162
|
if (kind === "ray") {
|
|
30165
30163
|
// Rays extend to the end of the graph in one direction.
|
|
@@ -30199,7 +30197,9 @@ const LockedLine = props => {
|
|
|
30199
30197
|
}), endArrowHead);
|
|
30200
30198
|
}
|
|
30201
30199
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30202
|
-
className: kind === "ray" ? "locked-ray" : "locked-line"
|
|
30200
|
+
className: kind === "ray" ? "locked-ray" : "locked-line",
|
|
30201
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
30202
|
+
"aria-hidden": !hasAria
|
|
30203
30203
|
}, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
30204
30204
|
x: point1.coord[X],
|
|
30205
30205
|
y: point1.coord[Y],
|
|
@@ -30225,12 +30225,19 @@ const LockedLine = props => {
|
|
|
30225
30225
|
|
|
30226
30226
|
const LockedPoint = props => {
|
|
30227
30227
|
const {
|
|
30228
|
+
flags,
|
|
30228
30229
|
color,
|
|
30229
30230
|
coord,
|
|
30230
|
-
filled
|
|
30231
|
+
filled,
|
|
30232
|
+
ariaLabel
|
|
30231
30233
|
} = props;
|
|
30232
30234
|
const [x, y] = coord;
|
|
30233
|
-
|
|
30235
|
+
const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
|
|
30236
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30237
|
+
className: "locked-point",
|
|
30238
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
30239
|
+
"aria-hidden": !hasAria
|
|
30240
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
30234
30241
|
x: x,
|
|
30235
30242
|
y: y,
|
|
30236
30243
|
svgCircleProps: {
|
|
@@ -30240,7 +30247,7 @@ const LockedPoint = props => {
|
|
|
30240
30247
|
strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
|
|
30241
30248
|
}
|
|
30242
30249
|
}
|
|
30243
|
-
});
|
|
30250
|
+
}));
|
|
30244
30251
|
};
|
|
30245
30252
|
|
|
30246
30253
|
const LockedPolygon = props => {
|
|
@@ -30251,8 +30258,11 @@ const LockedPolygon = props => {
|
|
|
30251
30258
|
fillStyle,
|
|
30252
30259
|
strokeStyle
|
|
30253
30260
|
} = props;
|
|
30261
|
+
const hasAria = props.ariaLabel && props.flags?.["mafs"]?.["locked-figures-aria"];
|
|
30254
30262
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30255
|
-
className: "locked-polygon"
|
|
30263
|
+
className: "locked-polygon",
|
|
30264
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
30265
|
+
"aria-hidden": !hasAria
|
|
30256
30266
|
}, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
30257
30267
|
points: [...points],
|
|
30258
30268
|
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
@@ -30278,11 +30288,16 @@ const LockedPolygon = props => {
|
|
|
30278
30288
|
const LockedVector = props => {
|
|
30279
30289
|
const {
|
|
30280
30290
|
color,
|
|
30281
|
-
points
|
|
30291
|
+
points,
|
|
30292
|
+
ariaLabel,
|
|
30293
|
+
flags
|
|
30282
30294
|
} = props;
|
|
30283
30295
|
const [tail, tip] = points;
|
|
30296
|
+
const hasAria = ariaLabel && flags?.["mafs"]?.["locked-figures-aria"];
|
|
30284
30297
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30285
|
-
className: "locked-vector"
|
|
30298
|
+
className: "locked-vector",
|
|
30299
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
30300
|
+
"aria-hidden": !hasAria
|
|
30286
30301
|
}, /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
30287
30302
|
tail: tail,
|
|
30288
30303
|
tip: tip,
|
|
@@ -30292,6 +30307,7 @@ const LockedVector = props => {
|
|
|
30292
30307
|
|
|
30293
30308
|
const GraphLockedLayer = props => {
|
|
30294
30309
|
const {
|
|
30310
|
+
flags,
|
|
30295
30311
|
lockedFigures
|
|
30296
30312
|
} = props;
|
|
30297
30313
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
|
|
@@ -30299,28 +30315,40 @@ const GraphLockedLayer = props => {
|
|
|
30299
30315
|
case "point":
|
|
30300
30316
|
return /*#__PURE__*/React__namespace.createElement(LockedPoint, _extends({
|
|
30301
30317
|
key: `point-${index}`
|
|
30302
|
-
}, figure
|
|
30318
|
+
}, figure, {
|
|
30319
|
+
flags: flags
|
|
30320
|
+
}));
|
|
30303
30321
|
case "line":
|
|
30304
30322
|
return /*#__PURE__*/React__namespace.createElement(LockedLine, _extends({
|
|
30305
30323
|
key: `line-${index}`,
|
|
30306
30324
|
range: props.range
|
|
30307
|
-
}, figure
|
|
30325
|
+
}, figure, {
|
|
30326
|
+
flags: flags
|
|
30327
|
+
}));
|
|
30308
30328
|
case "vector":
|
|
30309
30329
|
return /*#__PURE__*/React__namespace.createElement(LockedVector, _extends({
|
|
30310
30330
|
key: `vector-${index}`
|
|
30311
|
-
}, figure
|
|
30331
|
+
}, figure, {
|
|
30332
|
+
flags: flags
|
|
30333
|
+
}));
|
|
30312
30334
|
case "ellipse":
|
|
30313
30335
|
return /*#__PURE__*/React__namespace.createElement(LockedEllipse, _extends({
|
|
30314
30336
|
key: `ellipse-${index}`
|
|
30315
|
-
}, figure
|
|
30337
|
+
}, figure, {
|
|
30338
|
+
flags: flags
|
|
30339
|
+
}));
|
|
30316
30340
|
case "polygon":
|
|
30317
30341
|
return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
|
|
30318
30342
|
key: `polygon-${index}`
|
|
30319
|
-
}, figure
|
|
30343
|
+
}, figure, {
|
|
30344
|
+
flags: flags
|
|
30345
|
+
}));
|
|
30320
30346
|
case "function":
|
|
30321
30347
|
return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
|
|
30322
30348
|
key: `function-${index}`
|
|
30323
|
-
}, figure
|
|
30349
|
+
}, figure, {
|
|
30350
|
+
flags: flags
|
|
30351
|
+
}));
|
|
30324
30352
|
case "label":
|
|
30325
30353
|
// This is rendered outside the SVG element, since
|
|
30326
30354
|
// TeX cannot be rendered inside an SVG.
|
|
@@ -31807,7 +31835,7 @@ function QuadraticGraph(props) {
|
|
|
31807
31835
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
31808
31836
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
31809
31837
|
const coeffRef = React__namespace.useRef([0, 0, 0]);
|
|
31810
|
-
const coeffs = getQuadraticCoefficients(coords);
|
|
31838
|
+
const coeffs = getQuadraticCoefficients$1(coords);
|
|
31811
31839
|
if (coeffs !== undefined) {
|
|
31812
31840
|
coeffRef.current = coeffs;
|
|
31813
31841
|
}
|
|
@@ -31831,7 +31859,7 @@ function QuadraticGraph(props) {
|
|
|
31831
31859
|
// These equations were originally set up in 2013 and may require some
|
|
31832
31860
|
// additional comments to help clarify the quadratic formula manipulations
|
|
31833
31861
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
31834
|
-
const getQuadraticCoefficients = coords => {
|
|
31862
|
+
const getQuadraticCoefficients$1 = coords => {
|
|
31835
31863
|
const p1 = coords[0];
|
|
31836
31864
|
const p2 = coords[1];
|
|
31837
31865
|
const p3 = coords[2];
|
|
@@ -31871,7 +31899,7 @@ function SinusoidGraph(props) {
|
|
|
31871
31899
|
phase: 1,
|
|
31872
31900
|
verticalOffset: 0
|
|
31873
31901
|
});
|
|
31874
|
-
const coeffs = getSinusoidCoefficients(coords);
|
|
31902
|
+
const coeffs = getSinusoidCoefficients$1(coords);
|
|
31875
31903
|
|
|
31876
31904
|
// If the coefficients are valid, update the reference
|
|
31877
31905
|
if (coeffs !== undefined) {
|
|
@@ -31900,7 +31928,7 @@ sinusoidCoefficients) {
|
|
|
31900
31928
|
} = sinusoidCoefficients;
|
|
31901
31929
|
return a * Math.sin(b * x - c) + d;
|
|
31902
31930
|
};
|
|
31903
|
-
const getSinusoidCoefficients = coords => {
|
|
31931
|
+
const getSinusoidCoefficients$1 = coords => {
|
|
31904
31932
|
// It's assumed that p1 is the root and p2 is the first peak
|
|
31905
31933
|
const p1 = coords[0];
|
|
31906
31934
|
const p2 = coords[1];
|
|
@@ -32117,7 +32145,7 @@ function Protractor() {
|
|
|
32117
32145
|
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
32118
32146
|
// Position the protractor in the center of the graph (horizontally), and
|
|
32119
32147
|
// 95% of the way to the bottom of the graph (vertically).
|
|
32120
|
-
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.
|
|
32148
|
+
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.25)];
|
|
32121
32149
|
const [center, setCenter] = React.useState(initialCenter);
|
|
32122
32150
|
const [rotationHandleOffset, setRotationHandleOffset] = React.useState(centerToRotationHandle);
|
|
32123
32151
|
const draggableRef = React.useRef(null);
|
|
@@ -32278,6 +32306,9 @@ const MafsGraph = props => {
|
|
|
32278
32306
|
x: viewboxX,
|
|
32279
32307
|
y: viewboxY
|
|
32280
32308
|
};
|
|
32309
|
+
const {
|
|
32310
|
+
strings
|
|
32311
|
+
} = usePerseusI18n();
|
|
32281
32312
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
32282
32313
|
value: {
|
|
32283
32314
|
range: state.range,
|
|
@@ -32356,6 +32387,7 @@ const MafsGraph = props => {
|
|
|
32356
32387
|
})),
|
|
32357
32388
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
32358
32389
|
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
32390
|
+
flags: props.flags,
|
|
32359
32391
|
lockedFigures: props.lockedFigures,
|
|
32360
32392
|
range: state.range
|
|
32361
32393
|
}))), props.flags?.["mafs"]?.["interactive-graph-locked-features-labels"] && props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
@@ -32392,10 +32424,11 @@ const MafsGraph = props => {
|
|
|
32392
32424
|
top: "50%",
|
|
32393
32425
|
transform: "translateY(-50%)"
|
|
32394
32426
|
}
|
|
32395
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null,
|
|
32427
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelMedium, null, strings.graphKeyboardPrompt))), renderGraphControls({
|
|
32396
32428
|
state,
|
|
32397
32429
|
dispatch,
|
|
32398
|
-
width
|
|
32430
|
+
width,
|
|
32431
|
+
strings
|
|
32399
32432
|
})));
|
|
32400
32433
|
};
|
|
32401
32434
|
const renderPointGraphControls = props => {
|
|
@@ -32404,6 +32437,10 @@ const renderPointGraphControls = props => {
|
|
|
32404
32437
|
showRemovePointButton,
|
|
32405
32438
|
focusedPointIndex
|
|
32406
32439
|
} = props.state;
|
|
32440
|
+
const {
|
|
32441
|
+
strings
|
|
32442
|
+
} = props;
|
|
32443
|
+
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
32407
32444
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32408
32445
|
style: {
|
|
32409
32446
|
flexDirection: "row",
|
|
@@ -32419,7 +32456,7 @@ const renderPointGraphControls = props => {
|
|
|
32419
32456
|
onClick: () => {
|
|
32420
32457
|
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32421
32458
|
}
|
|
32422
|
-
},
|
|
32459
|
+
}, strings.addPoint), interactionMode === "mouse" && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
32423
32460
|
id: REMOVE_BUTTON_ID,
|
|
32424
32461
|
kind: "secondary",
|
|
32425
32462
|
color: "destructive"
|
|
@@ -32429,18 +32466,20 @@ const renderPointGraphControls = props => {
|
|
|
32429
32466
|
tabIndex: -1,
|
|
32430
32467
|
style: {
|
|
32431
32468
|
width: "100%",
|
|
32432
|
-
marginLeft: "20px"
|
|
32469
|
+
marginLeft: "20px",
|
|
32470
|
+
visibility: shouldShowRemoveButton ? "visible" : "hidden"
|
|
32433
32471
|
},
|
|
32434
32472
|
onClick: event => {
|
|
32435
32473
|
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32436
32474
|
}
|
|
32437
|
-
},
|
|
32475
|
+
}, strings.removePoint));
|
|
32438
32476
|
};
|
|
32439
32477
|
const renderGraphControls = props => {
|
|
32440
32478
|
const {
|
|
32441
32479
|
state,
|
|
32442
32480
|
dispatch,
|
|
32443
|
-
width
|
|
32481
|
+
width,
|
|
32482
|
+
strings
|
|
32444
32483
|
} = props;
|
|
32445
32484
|
const {
|
|
32446
32485
|
type
|
|
@@ -32451,7 +32490,8 @@ const renderGraphControls = props => {
|
|
|
32451
32490
|
return renderPointGraphControls({
|
|
32452
32491
|
state,
|
|
32453
32492
|
dispatch,
|
|
32454
|
-
width
|
|
32493
|
+
width,
|
|
32494
|
+
strings
|
|
32455
32495
|
});
|
|
32456
32496
|
}
|
|
32457
32497
|
return null;
|
|
@@ -32603,6 +32643,95 @@ const renderGraph = props => {
|
|
|
32603
32643
|
}
|
|
32604
32644
|
};
|
|
32605
32645
|
|
|
32646
|
+
// Converts the state of a StatefulMafsGraph back to the format used to
|
|
32647
|
+
// represent graph state in the widget JSON.
|
|
32648
|
+
//
|
|
32649
|
+
// Rather than be tightly bound to how data was structured in
|
|
32650
|
+
// the legacy interactive graph, this lets us store state
|
|
32651
|
+
// however we want and we just transform it before handing it off
|
|
32652
|
+
// the the parent InteractiveGraph.
|
|
32653
|
+
//
|
|
32654
|
+
// The transformed data is used in the interactive graph widget editor, to
|
|
32655
|
+
// set the `correct` field of the graph options. In the learner-facing UI, the
|
|
32656
|
+
// data is also stored by the Renderer, and passed back down to the graph
|
|
32657
|
+
// widget via the `graph` prop. Because the data returned by this function
|
|
32658
|
+
// completely replaces the Renderer's representation of the widget, rather than
|
|
32659
|
+
// being merged into it, we take it upon ourselves to merge in the original
|
|
32660
|
+
// widget data here. If we didn't do this merging, the graph's configuration
|
|
32661
|
+
// would reset to the defaults when the learner interacted with it.
|
|
32662
|
+
function mafsStateToInteractiveGraph(state, originalGraph) {
|
|
32663
|
+
switch (state.type) {
|
|
32664
|
+
case "angle":
|
|
32665
|
+
invariant(originalGraph.type === "angle");
|
|
32666
|
+
return {
|
|
32667
|
+
...originalGraph,
|
|
32668
|
+
coords: state.coords
|
|
32669
|
+
};
|
|
32670
|
+
case "quadratic":
|
|
32671
|
+
invariant(originalGraph.type === "quadratic");
|
|
32672
|
+
return {
|
|
32673
|
+
...originalGraph,
|
|
32674
|
+
coords: state.coords
|
|
32675
|
+
};
|
|
32676
|
+
case "circle":
|
|
32677
|
+
invariant(originalGraph.type === "circle");
|
|
32678
|
+
return {
|
|
32679
|
+
...originalGraph,
|
|
32680
|
+
center: state.center,
|
|
32681
|
+
radius: getRadius(state)
|
|
32682
|
+
};
|
|
32683
|
+
case "linear":
|
|
32684
|
+
invariant(originalGraph.type === "linear");
|
|
32685
|
+
return {
|
|
32686
|
+
...originalGraph,
|
|
32687
|
+
coords: state.coords
|
|
32688
|
+
};
|
|
32689
|
+
case "ray":
|
|
32690
|
+
invariant(originalGraph.type === "ray");
|
|
32691
|
+
return {
|
|
32692
|
+
...originalGraph,
|
|
32693
|
+
coords: state.coords
|
|
32694
|
+
};
|
|
32695
|
+
case "sinusoid":
|
|
32696
|
+
invariant(originalGraph.type === "sinusoid");
|
|
32697
|
+
return {
|
|
32698
|
+
...originalGraph,
|
|
32699
|
+
coords: state.coords
|
|
32700
|
+
};
|
|
32701
|
+
case "segment":
|
|
32702
|
+
invariant(originalGraph.type === "segment");
|
|
32703
|
+
return {
|
|
32704
|
+
...originalGraph,
|
|
32705
|
+
coords: state.coords
|
|
32706
|
+
};
|
|
32707
|
+
case "linear-system":
|
|
32708
|
+
invariant(originalGraph.type === "linear-system");
|
|
32709
|
+
return {
|
|
32710
|
+
...originalGraph,
|
|
32711
|
+
coords: state.coords
|
|
32712
|
+
};
|
|
32713
|
+
case "polygon":
|
|
32714
|
+
invariant(originalGraph.type === "polygon");
|
|
32715
|
+
return {
|
|
32716
|
+
...originalGraph,
|
|
32717
|
+
coords: state.coords
|
|
32718
|
+
};
|
|
32719
|
+
case "point":
|
|
32720
|
+
invariant(originalGraph.type === "point");
|
|
32721
|
+
return {
|
|
32722
|
+
...originalGraph,
|
|
32723
|
+
coords: state.coords
|
|
32724
|
+
};
|
|
32725
|
+
case "none":
|
|
32726
|
+
invariant(originalGraph.type === "none");
|
|
32727
|
+
return {
|
|
32728
|
+
...originalGraph
|
|
32729
|
+
};
|
|
32730
|
+
default:
|
|
32731
|
+
throw new wonderStuffCore.UnreachableCaseError(state);
|
|
32732
|
+
}
|
|
32733
|
+
}
|
|
32734
|
+
|
|
32606
32735
|
function initializeGraphState(params) {
|
|
32607
32736
|
const {
|
|
32608
32737
|
graph,
|
|
@@ -33251,7 +33380,7 @@ function doMovePoint(state, action) {
|
|
|
33251
33380
|
// Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
|
|
33252
33381
|
const boundDestination = boundAndSnapToGrid(action.destination, state);
|
|
33253
33382
|
newCoords[action.index] = boundDestination;
|
|
33254
|
-
const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
|
|
33383
|
+
const QuadraticCoefficients = getQuadraticCoefficients$1(newCoords);
|
|
33255
33384
|
|
|
33256
33385
|
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
33257
33386
|
if (QuadraticCoefficients === undefined) {
|
|
@@ -33407,11 +33536,11 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
33407
33536
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
33408
33537
|
return [dx, dy];
|
|
33409
33538
|
};
|
|
33410
|
-
const eq$
|
|
33539
|
+
const eq$2 = Util.eq;
|
|
33411
33540
|
|
|
33412
33541
|
// Less than or approximately equal
|
|
33413
33542
|
function leq$1(a, b) {
|
|
33414
|
-
return a < b || eq$
|
|
33543
|
+
return a < b || eq$2(a, b);
|
|
33415
33544
|
}
|
|
33416
33545
|
function boundAndSnapToGrid(point, _ref) {
|
|
33417
33546
|
let {
|
|
@@ -33723,24 +33852,6 @@ function setAtIndex(args) {
|
|
|
33723
33852
|
return copy;
|
|
33724
33853
|
}
|
|
33725
33854
|
|
|
33726
|
-
// Rather than be tightly bound to how data was structured in
|
|
33727
|
-
// the legacy interactive graph, this lets us store state
|
|
33728
|
-
// however we want and we just transform it before handing it off
|
|
33729
|
-
// the the parent InteractiveGraph
|
|
33730
|
-
function mafsStateToInteractiveGraph(state) {
|
|
33731
|
-
if (state.graph.type === "circle") {
|
|
33732
|
-
return {
|
|
33733
|
-
...state,
|
|
33734
|
-
graph: {
|
|
33735
|
-
...state.graph,
|
|
33736
|
-
radius: getRadius(state.graph)
|
|
33737
|
-
}
|
|
33738
|
-
};
|
|
33739
|
-
}
|
|
33740
|
-
return {
|
|
33741
|
-
...state
|
|
33742
|
-
};
|
|
33743
|
-
}
|
|
33744
33855
|
const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
33745
33856
|
const {
|
|
33746
33857
|
onChange,
|
|
@@ -33753,12 +33864,12 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33753
33864
|
const prevState = React.useRef(state);
|
|
33754
33865
|
React.useEffect(() => {
|
|
33755
33866
|
if (prevState.current !== state) {
|
|
33756
|
-
onChange(
|
|
33757
|
-
graph: state
|
|
33758
|
-
})
|
|
33867
|
+
onChange({
|
|
33868
|
+
graph: mafsStateToInteractiveGraph(state, graph)
|
|
33869
|
+
});
|
|
33759
33870
|
}
|
|
33760
33871
|
prevState.current = state;
|
|
33761
|
-
}, [onChange, state]);
|
|
33872
|
+
}, [onChange, state, graph]);
|
|
33762
33873
|
|
|
33763
33874
|
// Destructuring first to keep useEffect from making excess calls
|
|
33764
33875
|
const [xSnap, ySnap] = props.snapStep;
|
|
@@ -33811,12 +33922,232 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33811
33922
|
}));
|
|
33812
33923
|
});
|
|
33813
33924
|
|
|
33925
|
+
const eq$1 = Util.eq;
|
|
33926
|
+
const deepEq$1 = Util.deepEq;
|
|
33927
|
+
function interactiveGraphValidator(userInput, rubric) {
|
|
33928
|
+
// None-type graphs are not graded
|
|
33929
|
+
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
33930
|
+
return {
|
|
33931
|
+
type: "points",
|
|
33932
|
+
earned: 0,
|
|
33933
|
+
total: 0,
|
|
33934
|
+
message: null
|
|
33935
|
+
};
|
|
33936
|
+
}
|
|
33937
|
+
|
|
33938
|
+
// When nothing has moved, there will neither be coords nor the
|
|
33939
|
+
// circle's center/radius fields. When those fields are absent, skip
|
|
33940
|
+
// all these checks; just go mark the answer as empty.
|
|
33941
|
+
const hasValue = Boolean(
|
|
33942
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
33943
|
+
userInput.coords ||
|
|
33944
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
33945
|
+
userInput.center && userInput.radius);
|
|
33946
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
33947
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
33948
|
+
const guess = userInput.coords;
|
|
33949
|
+
const correct = rubric.correct.coords;
|
|
33950
|
+
|
|
33951
|
+
// If both of the guess points are on the correct line, it's
|
|
33952
|
+
// correct.
|
|
33953
|
+
if (
|
|
33954
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33955
|
+
collinear(correct[0], correct[1], guess[0]) &&
|
|
33956
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33957
|
+
collinear(correct[0], correct[1], guess[1])) {
|
|
33958
|
+
return {
|
|
33959
|
+
type: "points",
|
|
33960
|
+
earned: 1,
|
|
33961
|
+
total: 1,
|
|
33962
|
+
message: null
|
|
33963
|
+
};
|
|
33964
|
+
}
|
|
33965
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
33966
|
+
const guess = userInput.coords;
|
|
33967
|
+
const correct = rubric.correct.coords;
|
|
33968
|
+
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
33969
|
+
return {
|
|
33970
|
+
type: "points",
|
|
33971
|
+
earned: 1,
|
|
33972
|
+
total: 1,
|
|
33973
|
+
message: null
|
|
33974
|
+
};
|
|
33975
|
+
}
|
|
33976
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
33977
|
+
// If the parabola coefficients match, it's correct.
|
|
33978
|
+
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
33979
|
+
const correctCoeffs = getQuadraticCoefficients(
|
|
33980
|
+
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
33981
|
+
rubric.correct.coords);
|
|
33982
|
+
if (deepEq$1(guessCoeffs, correctCoeffs)) {
|
|
33983
|
+
return {
|
|
33984
|
+
type: "points",
|
|
33985
|
+
earned: 1,
|
|
33986
|
+
total: 1,
|
|
33987
|
+
message: null
|
|
33988
|
+
};
|
|
33989
|
+
}
|
|
33990
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
33991
|
+
const guessCoeffs = getSinusoidCoefficients(userInput.coords);
|
|
33992
|
+
const correctCoeffs = getSinusoidCoefficients(
|
|
33993
|
+
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
33994
|
+
rubric.correct.coords);
|
|
33995
|
+
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
33996
|
+
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
33997
|
+
// If the canonical coefficients match, it's correct.
|
|
33998
|
+
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
33999
|
+
return {
|
|
34000
|
+
type: "points",
|
|
34001
|
+
earned: 1,
|
|
34002
|
+
total: 1,
|
|
34003
|
+
message: null
|
|
34004
|
+
};
|
|
34005
|
+
}
|
|
34006
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
34007
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
34008
|
+
return {
|
|
34009
|
+
type: "points",
|
|
34010
|
+
earned: 1,
|
|
34011
|
+
total: 1,
|
|
34012
|
+
message: null
|
|
34013
|
+
};
|
|
34014
|
+
}
|
|
34015
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
34016
|
+
let correct = rubric.correct.coords;
|
|
34017
|
+
if (correct == null) {
|
|
34018
|
+
throw new Error("Point graph rubric has null coords");
|
|
34019
|
+
}
|
|
34020
|
+
const guess = userInput.coords.slice();
|
|
34021
|
+
correct = correct.slice();
|
|
34022
|
+
// Everything's already rounded so we shouldn't need to do an
|
|
34023
|
+
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
34024
|
+
// eq() anyway. The sort should be fine because it'll stringify
|
|
34025
|
+
// it and -0 converted to a string is "0"
|
|
34026
|
+
guess?.sort();
|
|
34027
|
+
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
34028
|
+
correct.sort();
|
|
34029
|
+
if (deepEq$1(guess, correct)) {
|
|
34030
|
+
return {
|
|
34031
|
+
type: "points",
|
|
34032
|
+
earned: 1,
|
|
34033
|
+
total: 1,
|
|
34034
|
+
message: null
|
|
34035
|
+
};
|
|
34036
|
+
}
|
|
34037
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
34038
|
+
const guess = userInput.coords?.slice();
|
|
34039
|
+
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
34040
|
+
const correct = rubric.correct.coords?.slice();
|
|
34041
|
+
let match;
|
|
34042
|
+
if (rubric.correct.match === "similar") {
|
|
34043
|
+
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
34044
|
+
} else if (rubric.correct.match === "congruent") {
|
|
34045
|
+
match = similar(guess, correct, kmath.number.DEFAULT_TOLERANCE);
|
|
34046
|
+
} else if (rubric.correct.match === "approx") {
|
|
34047
|
+
match = similar(guess, correct, 0.1);
|
|
34048
|
+
} else {
|
|
34049
|
+
/* exact */
|
|
34050
|
+
guess.sort();
|
|
34051
|
+
correct.sort();
|
|
34052
|
+
match = deepEq$1(guess, correct);
|
|
34053
|
+
}
|
|
34054
|
+
if (match) {
|
|
34055
|
+
return {
|
|
34056
|
+
type: "points",
|
|
34057
|
+
earned: 1,
|
|
34058
|
+
total: 1,
|
|
34059
|
+
message: null
|
|
34060
|
+
};
|
|
34061
|
+
}
|
|
34062
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
34063
|
+
let guess = Util.deepClone(userInput.coords);
|
|
34064
|
+
let correct = Util.deepClone(rubric.correct?.coords);
|
|
34065
|
+
guess = ___default["default"].invoke(guess, "sort").sort();
|
|
34066
|
+
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
34067
|
+
correct = ___default["default"].invoke(correct, "sort").sort();
|
|
34068
|
+
if (deepEq$1(guess, correct)) {
|
|
34069
|
+
return {
|
|
34070
|
+
type: "points",
|
|
34071
|
+
earned: 1,
|
|
34072
|
+
total: 1,
|
|
34073
|
+
message: null
|
|
34074
|
+
};
|
|
34075
|
+
}
|
|
34076
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
34077
|
+
const guess = userInput.coords;
|
|
34078
|
+
const correct = rubric.correct.coords;
|
|
34079
|
+
if (
|
|
34080
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
34081
|
+
deepEq$1(guess[0], correct[0]) &&
|
|
34082
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
34083
|
+
collinear(correct[0], correct[1], guess[1])) {
|
|
34084
|
+
return {
|
|
34085
|
+
type: "points",
|
|
34086
|
+
earned: 1,
|
|
34087
|
+
total: 1,
|
|
34088
|
+
message: null
|
|
34089
|
+
};
|
|
34090
|
+
}
|
|
34091
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
34092
|
+
const guess = userInput.coords;
|
|
34093
|
+
const correct = rubric.correct.coords;
|
|
34094
|
+
let match;
|
|
34095
|
+
if (rubric.correct.match === "congruent") {
|
|
34096
|
+
const angles = ___default["default"].map([guess, correct], function (coords) {
|
|
34097
|
+
const angle = GraphUtils.findAngle(
|
|
34098
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
34099
|
+
coords[2],
|
|
34100
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
34101
|
+
coords[0],
|
|
34102
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
34103
|
+
coords[1]);
|
|
34104
|
+
return (angle + 360) % 360;
|
|
34105
|
+
});
|
|
34106
|
+
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
34107
|
+
match = eq$1(...angles);
|
|
34108
|
+
} else {
|
|
34109
|
+
/* exact */
|
|
34110
|
+
match =
|
|
34111
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
34112
|
+
deepEq$1(guess[1], correct[1]) &&
|
|
34113
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
34114
|
+
collinear(correct[1], correct[0], guess[0]) &&
|
|
34115
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
34116
|
+
collinear(correct[1], correct[2], guess[2]);
|
|
34117
|
+
}
|
|
34118
|
+
if (match) {
|
|
34119
|
+
return {
|
|
34120
|
+
type: "points",
|
|
34121
|
+
earned: 1,
|
|
34122
|
+
total: 1,
|
|
34123
|
+
message: null
|
|
34124
|
+
};
|
|
34125
|
+
}
|
|
34126
|
+
}
|
|
34127
|
+
}
|
|
34128
|
+
|
|
34129
|
+
// The input wasn't correct, so check if it's a blank input or if it's
|
|
34130
|
+
// actually just wrong
|
|
34131
|
+
if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
|
|
34132
|
+
// We're where we started.
|
|
34133
|
+
return {
|
|
34134
|
+
type: "invalid",
|
|
34135
|
+
message: null
|
|
34136
|
+
};
|
|
34137
|
+
}
|
|
34138
|
+
return {
|
|
34139
|
+
type: "points",
|
|
34140
|
+
earned: 0,
|
|
34141
|
+
total: 1,
|
|
34142
|
+
message: null
|
|
34143
|
+
};
|
|
34144
|
+
}
|
|
34145
|
+
|
|
33814
34146
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
33815
34147
|
const defaultBackgroundImage = {
|
|
33816
34148
|
url: null
|
|
33817
34149
|
};
|
|
33818
34150
|
const eq = Util.eq;
|
|
33819
|
-
const deepEq$1 = Util.deepEq;
|
|
33820
34151
|
const UNLIMITED = "unlimited";
|
|
33821
34152
|
|
|
33822
34153
|
// Sample background image:
|
|
@@ -33857,6 +34188,39 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
33857
34188
|
|
|
33858
34189
|
// There's no transform function in exports
|
|
33859
34190
|
|
|
34191
|
+
// TODO: there's another, very similar getSinusoidCoefficients function
|
|
34192
|
+
// they should probably be merged
|
|
34193
|
+
function getSinusoidCoefficients(coords) {
|
|
34194
|
+
// It's assumed that p1 is the root and p2 is the first peak
|
|
34195
|
+
const p1 = coords[0];
|
|
34196
|
+
const p2 = coords[1];
|
|
34197
|
+
|
|
34198
|
+
// Resulting coefficients are canonical for this sine curve
|
|
34199
|
+
const amplitude = p2[1] - p1[1];
|
|
34200
|
+
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
34201
|
+
const phase = p1[0] * angularFrequency;
|
|
34202
|
+
const verticalOffset = p1[1];
|
|
34203
|
+
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
34204
|
+
}
|
|
34205
|
+
|
|
34206
|
+
// TODO: there's another, very similar getQuadraticCoefficients function
|
|
34207
|
+
// they should probably be merged
|
|
34208
|
+
function getQuadraticCoefficients(coords) {
|
|
34209
|
+
const p1 = coords[0];
|
|
34210
|
+
const p2 = coords[1];
|
|
34211
|
+
const p3 = coords[2];
|
|
34212
|
+
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
34213
|
+
if (denom === 0) {
|
|
34214
|
+
// Many of the callers assume that the return value is always defined.
|
|
34215
|
+
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
|
|
34216
|
+
return;
|
|
34217
|
+
}
|
|
34218
|
+
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
34219
|
+
const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
|
|
34220
|
+
const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
|
|
34221
|
+
return [a, b, c];
|
|
34222
|
+
}
|
|
34223
|
+
|
|
33860
34224
|
// (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
|
|
33861
34225
|
// to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
|
|
33862
34226
|
// for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
|
|
@@ -34977,7 +35341,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
34977
35341
|
});
|
|
34978
35342
|
$__default["default"](this.angle).on("move", () => {
|
|
34979
35343
|
this.onChange({
|
|
34980
|
-
// @ts-expect-error Type '{ coords: any; type: "angle"; showAngles?: boolean | undefined; allowReflexAngles?: boolean | undefined; angleOffsetDeg?: number | undefined; snapDegrees?: number | undefined; match?: "congruent" | undefined; }' is not assignable to type 'InteractiveGraphState | undefined'.
|
|
34981
35344
|
graph: {
|
|
34982
35345
|
...graph,
|
|
34983
35346
|
coords: this.angle?.getClockwiseCoords()
|
|
@@ -35004,7 +35367,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
35004
35367
|
return InteractiveGraph.getUserInputFromProps(this.props);
|
|
35005
35368
|
}
|
|
35006
35369
|
simpleValidate(rubric) {
|
|
35007
|
-
return
|
|
35370
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
35008
35371
|
}
|
|
35009
35372
|
focus = $__default["default"].noop;
|
|
35010
35373
|
render() {
|
|
@@ -35069,17 +35432,17 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35069
35432
|
type: "linear"
|
|
35070
35433
|
}
|
|
35071
35434
|
};
|
|
35072
|
-
getUserInput
|
|
35073
|
-
if (this.mafsRef.current) {
|
|
35074
|
-
return this.mafsRef.current.getUserInput
|
|
35435
|
+
getUserInput() {
|
|
35436
|
+
if (this.mafsRef.current?.getUserInput) {
|
|
35437
|
+
return this.mafsRef.current.getUserInput();
|
|
35075
35438
|
}
|
|
35076
|
-
if (this.legacyGraphRef.current) {
|
|
35439
|
+
if (this.legacyGraphRef.current?.getUserInput) {
|
|
35077
35440
|
return this.legacyGraphRef.current.getUserInput();
|
|
35078
35441
|
}
|
|
35079
35442
|
throw new perseusCore.PerseusError("Cannot getUserInput from a graph that has never rendered", perseusCore.Errors.NotAllowed);
|
|
35080
|
-
}
|
|
35443
|
+
}
|
|
35081
35444
|
simpleValidate(rubric) {
|
|
35082
|
-
return
|
|
35445
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
35083
35446
|
}
|
|
35084
35447
|
render() {
|
|
35085
35448
|
// Mafs shim
|
|
@@ -35102,33 +35465,6 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35102
35465
|
ref: this.legacyGraphRef
|
|
35103
35466
|
}, this.props));
|
|
35104
35467
|
}
|
|
35105
|
-
static getQuadraticCoefficients(coords) {
|
|
35106
|
-
const p1 = coords[0];
|
|
35107
|
-
const p2 = coords[1];
|
|
35108
|
-
const p3 = coords[2];
|
|
35109
|
-
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
35110
|
-
if (denom === 0) {
|
|
35111
|
-
// Many of the callers assume that the return value is always defined.
|
|
35112
|
-
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
|
|
35113
|
-
return;
|
|
35114
|
-
}
|
|
35115
|
-
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
35116
|
-
const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
|
|
35117
|
-
const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
|
|
35118
|
-
return [a, b, c];
|
|
35119
|
-
}
|
|
35120
|
-
static getSinusoidCoefficients(coords) {
|
|
35121
|
-
// It's assumed that p1 is the root and p2 is the first peak
|
|
35122
|
-
const p1 = coords[0];
|
|
35123
|
-
const p2 = coords[1];
|
|
35124
|
-
|
|
35125
|
-
// Resulting coefficients are canonical for this sine curve
|
|
35126
|
-
const amplitude = p2[1] - p1[1];
|
|
35127
|
-
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
35128
|
-
const phase = p1[0] * angularFrequency;
|
|
35129
|
-
const verticalOffset = p1[1];
|
|
35130
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
35131
|
-
}
|
|
35132
35468
|
|
|
35133
35469
|
/**
|
|
35134
35470
|
* @param {object} graph Like props.graph or props.correct
|
|
@@ -35342,7 +35678,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35342
35678
|
const coords =
|
|
35343
35679
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35344
35680
|
props.graph.coords || InteractiveGraph.defaultQuadraticCoords(props);
|
|
35345
|
-
return
|
|
35681
|
+
return getQuadraticCoefficients(coords);
|
|
35346
35682
|
}
|
|
35347
35683
|
static defaultQuadraticCoords(props) {
|
|
35348
35684
|
const coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
|
|
@@ -35357,7 +35693,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35357
35693
|
const coords =
|
|
35358
35694
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35359
35695
|
props.graph.coords || InteractiveGraph.defaultSinusoidCoords(props);
|
|
35360
|
-
return
|
|
35696
|
+
return getSinusoidCoefficients(coords);
|
|
35361
35697
|
}
|
|
35362
35698
|
static defaultSinusoidCoords(props) {
|
|
35363
35699
|
const coords = [[0.5, 0.5], [0.65, 0.6]];
|
|
@@ -35437,220 +35773,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
35437
35773
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
35438
35774
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
35439
35775
|
}
|
|
35440
|
-
static validate(userInput, rubric
|
|
35441
|
-
|
|
35442
|
-
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35443
|
-
return {
|
|
35444
|
-
type: "points",
|
|
35445
|
-
earned: 0,
|
|
35446
|
-
total: 0,
|
|
35447
|
-
message: null
|
|
35448
|
-
};
|
|
35449
|
-
}
|
|
35450
|
-
|
|
35451
|
-
// When nothing has moved, there will neither be coords nor the
|
|
35452
|
-
// circle's center/radius fields. When those fields are absent, skip
|
|
35453
|
-
// all these checks; just go mark the answer as empty.
|
|
35454
|
-
const hasValue = Boolean(
|
|
35455
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35456
|
-
userInput.coords ||
|
|
35457
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
35458
|
-
userInput.center && userInput.radius);
|
|
35459
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
35460
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
35461
|
-
const guess = userInput.coords;
|
|
35462
|
-
const correct = rubric.correct.coords;
|
|
35463
|
-
|
|
35464
|
-
// If both of the guess points are on the correct line, it's
|
|
35465
|
-
// correct.
|
|
35466
|
-
if (
|
|
35467
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35468
|
-
collinear(correct[0], correct[1], guess[0]) &&
|
|
35469
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35470
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35471
|
-
return {
|
|
35472
|
-
type: "points",
|
|
35473
|
-
earned: 1,
|
|
35474
|
-
total: 1,
|
|
35475
|
-
message: null
|
|
35476
|
-
};
|
|
35477
|
-
}
|
|
35478
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
35479
|
-
const guess = userInput.coords;
|
|
35480
|
-
const correct = rubric.correct.coords;
|
|
35481
|
-
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
35482
|
-
return {
|
|
35483
|
-
type: "points",
|
|
35484
|
-
earned: 1,
|
|
35485
|
-
total: 1,
|
|
35486
|
-
message: null
|
|
35487
|
-
};
|
|
35488
|
-
}
|
|
35489
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
35490
|
-
// If the parabola coefficients match, it's correct.
|
|
35491
|
-
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
35492
|
-
const correctCoeffs = this.getQuadraticCoefficients(
|
|
35493
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35494
|
-
rubric.correct.coords);
|
|
35495
|
-
if (deepEq$1(guessCoeffs, correctCoeffs)) {
|
|
35496
|
-
return {
|
|
35497
|
-
type: "points",
|
|
35498
|
-
earned: 1,
|
|
35499
|
-
total: 1,
|
|
35500
|
-
message: null
|
|
35501
|
-
};
|
|
35502
|
-
}
|
|
35503
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
35504
|
-
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
35505
|
-
const correctCoeffs = this.getSinusoidCoefficients(
|
|
35506
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35507
|
-
rubric.correct.coords);
|
|
35508
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
35509
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
35510
|
-
// If the canonical coefficients match, it's correct.
|
|
35511
|
-
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
35512
|
-
return {
|
|
35513
|
-
type: "points",
|
|
35514
|
-
earned: 1,
|
|
35515
|
-
total: 1,
|
|
35516
|
-
message: null
|
|
35517
|
-
};
|
|
35518
|
-
}
|
|
35519
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
35520
|
-
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
35521
|
-
return {
|
|
35522
|
-
type: "points",
|
|
35523
|
-
earned: 1,
|
|
35524
|
-
total: 1,
|
|
35525
|
-
message: null
|
|
35526
|
-
};
|
|
35527
|
-
}
|
|
35528
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
35529
|
-
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
35530
|
-
const guess = userInput.coords.slice();
|
|
35531
|
-
correct = correct.slice();
|
|
35532
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
35533
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
35534
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
35535
|
-
// it and -0 converted to a string is "0"
|
|
35536
|
-
guess?.sort();
|
|
35537
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
35538
|
-
correct.sort();
|
|
35539
|
-
if (deepEq$1(guess, correct)) {
|
|
35540
|
-
return {
|
|
35541
|
-
type: "points",
|
|
35542
|
-
earned: 1,
|
|
35543
|
-
total: 1,
|
|
35544
|
-
message: null
|
|
35545
|
-
};
|
|
35546
|
-
}
|
|
35547
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
35548
|
-
const guess = userInput.coords?.slice();
|
|
35549
|
-
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
35550
|
-
const correct = rubric.correct.coords?.slice();
|
|
35551
|
-
let match;
|
|
35552
|
-
if (rubric.correct.match === "similar") {
|
|
35553
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
35554
|
-
} else if (rubric.correct.match === "congruent") {
|
|
35555
|
-
match = similar(guess, correct, kmath.number.DEFAULT_TOLERANCE);
|
|
35556
|
-
} else if (rubric.correct.match === "approx") {
|
|
35557
|
-
match = similar(guess, correct, 0.1);
|
|
35558
|
-
} else {
|
|
35559
|
-
/* exact */
|
|
35560
|
-
guess.sort();
|
|
35561
|
-
correct.sort();
|
|
35562
|
-
match = deepEq$1(guess, correct);
|
|
35563
|
-
}
|
|
35564
|
-
if (match) {
|
|
35565
|
-
return {
|
|
35566
|
-
type: "points",
|
|
35567
|
-
earned: 1,
|
|
35568
|
-
total: 1,
|
|
35569
|
-
message: null
|
|
35570
|
-
};
|
|
35571
|
-
}
|
|
35572
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
35573
|
-
let guess = Util.deepClone(userInput.coords);
|
|
35574
|
-
let correct = Util.deepClone(rubric.correct?.coords);
|
|
35575
|
-
guess = ___default["default"].invoke(guess, "sort").sort();
|
|
35576
|
-
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
35577
|
-
correct = ___default["default"].invoke(correct, "sort").sort();
|
|
35578
|
-
if (deepEq$1(guess, correct)) {
|
|
35579
|
-
return {
|
|
35580
|
-
type: "points",
|
|
35581
|
-
earned: 1,
|
|
35582
|
-
total: 1,
|
|
35583
|
-
message: null
|
|
35584
|
-
};
|
|
35585
|
-
}
|
|
35586
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
35587
|
-
const guess = userInput.coords;
|
|
35588
|
-
const correct = rubric.correct.coords;
|
|
35589
|
-
if (
|
|
35590
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35591
|
-
deepEq$1(guess[0], correct[0]) &&
|
|
35592
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35593
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35594
|
-
return {
|
|
35595
|
-
type: "points",
|
|
35596
|
-
earned: 1,
|
|
35597
|
-
total: 1,
|
|
35598
|
-
message: null
|
|
35599
|
-
};
|
|
35600
|
-
}
|
|
35601
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
35602
|
-
const guess = userInput.coords;
|
|
35603
|
-
const correct = rubric.correct.coords;
|
|
35604
|
-
let match;
|
|
35605
|
-
if (rubric.correct.match === "congruent") {
|
|
35606
|
-
const angles = ___default["default"].map([guess, correct], function (coords) {
|
|
35607
|
-
const angle = GraphUtils.findAngle(
|
|
35608
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35609
|
-
coords[2],
|
|
35610
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35611
|
-
coords[0],
|
|
35612
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35613
|
-
coords[1]);
|
|
35614
|
-
return (angle + 360) % 360;
|
|
35615
|
-
});
|
|
35616
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
35617
|
-
match = eq(...angles);
|
|
35618
|
-
} else {
|
|
35619
|
-
/* exact */
|
|
35620
|
-
match =
|
|
35621
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35622
|
-
deepEq$1(guess[1], correct[1]) &&
|
|
35623
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35624
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
35625
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35626
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
35627
|
-
}
|
|
35628
|
-
if (match) {
|
|
35629
|
-
return {
|
|
35630
|
-
type: "points",
|
|
35631
|
-
earned: 1,
|
|
35632
|
-
total: 1,
|
|
35633
|
-
message: null
|
|
35634
|
-
};
|
|
35635
|
-
}
|
|
35636
|
-
}
|
|
35637
|
-
}
|
|
35638
|
-
|
|
35639
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
35640
|
-
// actually just wrong
|
|
35641
|
-
if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
|
|
35642
|
-
// We're where we started.
|
|
35643
|
-
return {
|
|
35644
|
-
type: "invalid",
|
|
35645
|
-
message: null
|
|
35646
|
-
};
|
|
35647
|
-
}
|
|
35648
|
-
return {
|
|
35649
|
-
type: "points",
|
|
35650
|
-
earned: 0,
|
|
35651
|
-
total: 1,
|
|
35652
|
-
message: null
|
|
35653
|
-
};
|
|
35776
|
+
static validate(userInput, rubric) {
|
|
35777
|
+
return interactiveGraphValidator(userInput, rubric);
|
|
35654
35778
|
}
|
|
35655
35779
|
static getUserInputFromProps(props) {
|
|
35656
35780
|
return props.graph;
|
|
@@ -36746,6 +36870,57 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
36746
36870
|
}
|
|
36747
36871
|
});
|
|
36748
36872
|
|
|
36873
|
+
// Question state for marker as result of user selected answers.
|
|
36874
|
+
|
|
36875
|
+
function scoreMarker(marker) {
|
|
36876
|
+
const score = {
|
|
36877
|
+
hasAnswers: false,
|
|
36878
|
+
isCorrect: false
|
|
36879
|
+
};
|
|
36880
|
+
if (marker.selected && marker.selected.length > 0) {
|
|
36881
|
+
score.hasAnswers = true;
|
|
36882
|
+
}
|
|
36883
|
+
if (marker.answers.length > 0) {
|
|
36884
|
+
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
36885
|
+
// All correct answers are selected by the user.
|
|
36886
|
+
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
36887
|
+
}
|
|
36888
|
+
} else if (!marker.selected || marker.selected.length === 0) {
|
|
36889
|
+
// Correct as no answers should be selected by the user.
|
|
36890
|
+
score.isCorrect = true;
|
|
36891
|
+
}
|
|
36892
|
+
return score;
|
|
36893
|
+
}
|
|
36894
|
+
function labelImageValidator(state, rubric) {
|
|
36895
|
+
let numAnswered = 0;
|
|
36896
|
+
let numCorrect = 0;
|
|
36897
|
+
for (const marker of state.markers) {
|
|
36898
|
+
const score = scoreMarker(marker);
|
|
36899
|
+
if (score.hasAnswers) {
|
|
36900
|
+
numAnswered++;
|
|
36901
|
+
}
|
|
36902
|
+
if (score.isCorrect) {
|
|
36903
|
+
numCorrect++;
|
|
36904
|
+
}
|
|
36905
|
+
}
|
|
36906
|
+
|
|
36907
|
+
// We expect all question markers to be answered before grading.
|
|
36908
|
+
if (numAnswered !== state.markers.length) {
|
|
36909
|
+
return {
|
|
36910
|
+
type: "invalid",
|
|
36911
|
+
message: null
|
|
36912
|
+
};
|
|
36913
|
+
}
|
|
36914
|
+
return {
|
|
36915
|
+
type: "points",
|
|
36916
|
+
// Markers with no expected answers are graded as correct if user
|
|
36917
|
+
// makes no answer selection.
|
|
36918
|
+
earned: numCorrect === state.markers.length ? 1 : 0,
|
|
36919
|
+
total: 1,
|
|
36920
|
+
message: null
|
|
36921
|
+
};
|
|
36922
|
+
}
|
|
36923
|
+
|
|
36749
36924
|
const BringToFront = {
|
|
36750
36925
|
boxShadow: `0 8px 8px ${wonderBlocksTokens.color.offBlack64}`,
|
|
36751
36926
|
zIndex: 1000
|
|
@@ -37006,61 +37181,15 @@ const styles$f = aphrodite.StyleSheet.create({
|
|
|
37006
37181
|
* No diagonals allowed.
|
|
37007
37182
|
*/
|
|
37008
37183
|
|
|
37184
|
+
// TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
|
|
37185
|
+
|
|
37009
37186
|
class LabelImage extends React__namespace.Component {
|
|
37010
37187
|
static contextType = PerseusI18nContext;
|
|
37011
37188
|
// The rendered markers on the question image for labeling.
|
|
37012
37189
|
_markers;
|
|
37013
37190
|
_mounted = false;
|
|
37014
|
-
static gradeMarker(marker) {
|
|
37015
|
-
const score = {
|
|
37016
|
-
hasAnswers: false,
|
|
37017
|
-
isCorrect: false
|
|
37018
|
-
};
|
|
37019
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
37020
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'hasAnswers' because it is a read-only property.
|
|
37021
|
-
score.hasAnswers = true;
|
|
37022
|
-
}
|
|
37023
|
-
if (marker.answers.length > 0) {
|
|
37024
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
37025
|
-
// All correct answers are selected by the user.
|
|
37026
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
|
|
37027
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
37028
|
-
}
|
|
37029
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
37030
|
-
// Correct as no answers should be selected by the user.
|
|
37031
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
|
|
37032
|
-
score.isCorrect = true;
|
|
37033
|
-
}
|
|
37034
|
-
return score;
|
|
37035
|
-
}
|
|
37036
37191
|
static validate(state, rubric) {
|
|
37037
|
-
|
|
37038
|
-
let numCorrect = 0;
|
|
37039
|
-
for (const marker of state.markers) {
|
|
37040
|
-
const score = LabelImage.gradeMarker(marker);
|
|
37041
|
-
if (score.hasAnswers) {
|
|
37042
|
-
numAnswered++;
|
|
37043
|
-
}
|
|
37044
|
-
if (score.isCorrect) {
|
|
37045
|
-
numCorrect++;
|
|
37046
|
-
}
|
|
37047
|
-
}
|
|
37048
|
-
|
|
37049
|
-
// We expect all question markers to be answered before grading.
|
|
37050
|
-
if (numAnswered !== state.markers.length) {
|
|
37051
|
-
return {
|
|
37052
|
-
type: "invalid",
|
|
37053
|
-
message: null
|
|
37054
|
-
};
|
|
37055
|
-
}
|
|
37056
|
-
return {
|
|
37057
|
-
type: "points",
|
|
37058
|
-
// Markers with no expected answers are graded as correct if user
|
|
37059
|
-
// makes no answer selection.
|
|
37060
|
-
earned: numCorrect === state.markers.length ? 1 : 0,
|
|
37061
|
-
total: 1,
|
|
37062
|
-
message: null
|
|
37063
|
-
};
|
|
37192
|
+
return labelImageValidator(state);
|
|
37064
37193
|
}
|
|
37065
37194
|
|
|
37066
37195
|
/**
|
|
@@ -37255,7 +37384,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37255
37384
|
this._mounted = false;
|
|
37256
37385
|
}
|
|
37257
37386
|
simpleValidate(rubric) {
|
|
37258
|
-
return
|
|
37387
|
+
return labelImageValidator(this.getUserInput());
|
|
37259
37388
|
}
|
|
37260
37389
|
getUserInput() {
|
|
37261
37390
|
const {
|
|
@@ -37273,7 +37402,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37273
37402
|
onChange
|
|
37274
37403
|
} = this.props;
|
|
37275
37404
|
const updatedMarkers = markers.map(marker => {
|
|
37276
|
-
const score =
|
|
37405
|
+
const score = scoreMarker(marker);
|
|
37277
37406
|
return {
|
|
37278
37407
|
...marker,
|
|
37279
37408
|
// Reveal correctness state for markers with answers.
|
|
@@ -37422,7 +37551,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37422
37551
|
bottom: "top"
|
|
37423
37552
|
}[markerPosition];
|
|
37424
37553
|
}
|
|
37425
|
-
const score =
|
|
37554
|
+
const score = scoreMarker(marker);
|
|
37426
37555
|
// Once the question is answered, show markers
|
|
37427
37556
|
// with correct answers, otherwise passthrough
|
|
37428
37557
|
// the correctness state.
|
|
@@ -37628,9 +37757,7 @@ const LabelImageWithDependencies = /*#__PURE__*/React__namespace.forwardRef((pro
|
|
|
37628
37757
|
// methods and instead adjust Peresus to provide these facilities through
|
|
37629
37758
|
// instance methods on our Renderers.
|
|
37630
37759
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
37631
|
-
LabelImageWithDependencies.validate =
|
|
37632
|
-
// @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
|
|
37633
|
-
LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
|
|
37760
|
+
LabelImageWithDependencies.validate = labelImageValidator;
|
|
37634
37761
|
var LabelImage$1 = {
|
|
37635
37762
|
name: "label-image",
|
|
37636
37763
|
displayName: "Label Image",
|
|
@@ -38346,6 +38473,16 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
38346
38473
|
}
|
|
38347
38474
|
});
|
|
38348
38475
|
|
|
38476
|
+
function matcherValidator(state, rubric) {
|
|
38477
|
+
const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
|
|
38478
|
+
return {
|
|
38479
|
+
type: "points",
|
|
38480
|
+
earned: correct ? 1 : 0,
|
|
38481
|
+
total: 1,
|
|
38482
|
+
message: null
|
|
38483
|
+
};
|
|
38484
|
+
}
|
|
38485
|
+
|
|
38349
38486
|
const {
|
|
38350
38487
|
shuffle: shuffle$1,
|
|
38351
38488
|
seededRNG
|
|
@@ -38368,6 +38505,9 @@ class Matcher extends React__namespace.Component {
|
|
|
38368
38505
|
rightHeight: 0,
|
|
38369
38506
|
texRendererLoaded: false
|
|
38370
38507
|
};
|
|
38508
|
+
static validate(state, rubric) {
|
|
38509
|
+
return matcherValidator(state, rubric);
|
|
38510
|
+
}
|
|
38371
38511
|
changeAndTrack = e => {
|
|
38372
38512
|
this.props.onChange(e);
|
|
38373
38513
|
this.props.trackInteraction();
|
|
@@ -38419,10 +38559,9 @@ class Matcher extends React__namespace.Component {
|
|
|
38419
38559
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
38420
38560
|
this.refs.right.moveOptionToIndex(option, index);
|
|
38421
38561
|
};
|
|
38422
|
-
simpleValidate
|
|
38423
|
-
|
|
38424
|
-
|
|
38425
|
-
};
|
|
38562
|
+
simpleValidate(rubric) {
|
|
38563
|
+
return matcherValidator(this.getUserInput(), rubric);
|
|
38564
|
+
}
|
|
38426
38565
|
render() {
|
|
38427
38566
|
// To minimize layout shift, we display a spinner until our math
|
|
38428
38567
|
// renderer is ready to render the math inside the matcher. To
|
|
@@ -38517,17 +38656,6 @@ class Matcher extends React__namespace.Component {
|
|
|
38517
38656
|
})))));
|
|
38518
38657
|
}
|
|
38519
38658
|
}
|
|
38520
|
-
___default["default"].extend(Matcher, {
|
|
38521
|
-
validate: function (state, rubric) {
|
|
38522
|
-
const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
|
|
38523
|
-
return {
|
|
38524
|
-
type: "points",
|
|
38525
|
-
earned: correct ? 1 : 0,
|
|
38526
|
-
total: 1,
|
|
38527
|
-
message: null
|
|
38528
|
-
};
|
|
38529
|
-
}
|
|
38530
|
-
});
|
|
38531
38659
|
const padding = 5;
|
|
38532
38660
|
const border = "1px solid #444";
|
|
38533
38661
|
const styles$c = aphrodite.StyleSheet.create({
|
|
@@ -38563,7 +38691,59 @@ var Matcher$1 = {
|
|
|
38563
38691
|
isLintable: true
|
|
38564
38692
|
};
|
|
38565
38693
|
|
|
38566
|
-
|
|
38694
|
+
function matrixValidator(state, rubric, strings) {
|
|
38695
|
+
const solution = rubric.answers;
|
|
38696
|
+
const supplied = state.answers;
|
|
38697
|
+
const solutionSize = getMatrixSize(solution);
|
|
38698
|
+
const suppliedSize = getMatrixSize(supplied);
|
|
38699
|
+
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
38700
|
+
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
38701
|
+
let message = null;
|
|
38702
|
+
let hasEmptyCell = false;
|
|
38703
|
+
let incorrect = false;
|
|
38704
|
+
___default["default"](suppliedSize[0]).times(row => {
|
|
38705
|
+
___default["default"](suppliedSize[1]).times(col => {
|
|
38706
|
+
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
38707
|
+
hasEmptyCell = true;
|
|
38708
|
+
}
|
|
38709
|
+
if (!incorrectSize) {
|
|
38710
|
+
const validator = createValidator(
|
|
38711
|
+
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
38712
|
+
solution[row][col], {
|
|
38713
|
+
simplify: true
|
|
38714
|
+
}, strings);
|
|
38715
|
+
const result = validator(supplied[row][col]);
|
|
38716
|
+
if (result.message) {
|
|
38717
|
+
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
38718
|
+
message = result.message;
|
|
38719
|
+
}
|
|
38720
|
+
if (!result.correct) {
|
|
38721
|
+
incorrect = true;
|
|
38722
|
+
}
|
|
38723
|
+
}
|
|
38724
|
+
});
|
|
38725
|
+
});
|
|
38726
|
+
if (hasEmptyCell) {
|
|
38727
|
+
return {
|
|
38728
|
+
type: "invalid",
|
|
38729
|
+
message: strings.fillAllCells
|
|
38730
|
+
};
|
|
38731
|
+
}
|
|
38732
|
+
if (incorrectSize) {
|
|
38733
|
+
return {
|
|
38734
|
+
type: "points",
|
|
38735
|
+
earned: 0,
|
|
38736
|
+
total: 1,
|
|
38737
|
+
message: null
|
|
38738
|
+
};
|
|
38739
|
+
}
|
|
38740
|
+
return {
|
|
38741
|
+
type: "points",
|
|
38742
|
+
earned: incorrect ? 0 : 1,
|
|
38743
|
+
total: 1,
|
|
38744
|
+
message: message
|
|
38745
|
+
};
|
|
38746
|
+
}
|
|
38567
38747
|
|
|
38568
38748
|
const {
|
|
38569
38749
|
assert: assert$2
|
|
@@ -38613,7 +38793,7 @@ const getRefForPath$1 = function (path) {
|
|
|
38613
38793
|
const column = getColumnFromPath$1(path);
|
|
38614
38794
|
return "answer" + row + "," + column;
|
|
38615
38795
|
};
|
|
38616
|
-
|
|
38796
|
+
function getMatrixSize(matrix) {
|
|
38617
38797
|
const matrixSize = [1, 1];
|
|
38618
38798
|
|
|
38619
38799
|
// We need to find the widest row and tallest column to get the correct
|
|
@@ -38635,7 +38815,7 @@ const getMatrixSize = function (matrix) {
|
|
|
38635
38815
|
}
|
|
38636
38816
|
});
|
|
38637
38817
|
return matrixSize;
|
|
38638
|
-
}
|
|
38818
|
+
}
|
|
38639
38819
|
class Matrix extends React__namespace.Component {
|
|
38640
38820
|
static contextType = PerseusI18nContext;
|
|
38641
38821
|
// @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -38650,55 +38830,7 @@ class Matrix extends React__namespace.Component {
|
|
|
38650
38830
|
linterContext: PerseusLinter.linterContextDefault
|
|
38651
38831
|
};
|
|
38652
38832
|
static validate(state, rubric, strings) {
|
|
38653
|
-
|
|
38654
|
-
const supplied = state.answers;
|
|
38655
|
-
const solutionSize = getMatrixSize(solution);
|
|
38656
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
38657
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
38658
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
38659
|
-
let message = null;
|
|
38660
|
-
let hasEmptyCell = false;
|
|
38661
|
-
let incorrect = false;
|
|
38662
|
-
___default["default"](suppliedSize[0]).times(row => {
|
|
38663
|
-
___default["default"](suppliedSize[1]).times(col => {
|
|
38664
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
38665
|
-
hasEmptyCell = true;
|
|
38666
|
-
}
|
|
38667
|
-
if (!incorrectSize) {
|
|
38668
|
-
const validator = createValidator(solution[row][col], {
|
|
38669
|
-
simplify: true
|
|
38670
|
-
}, strings);
|
|
38671
|
-
const result = validator(supplied[row][col]);
|
|
38672
|
-
if (result.message) {
|
|
38673
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
38674
|
-
message = result.message;
|
|
38675
|
-
}
|
|
38676
|
-
if (!result.correct) {
|
|
38677
|
-
incorrect = true;
|
|
38678
|
-
}
|
|
38679
|
-
}
|
|
38680
|
-
});
|
|
38681
|
-
});
|
|
38682
|
-
if (hasEmptyCell) {
|
|
38683
|
-
return {
|
|
38684
|
-
type: "invalid",
|
|
38685
|
-
message: strings.fillAllCells
|
|
38686
|
-
};
|
|
38687
|
-
}
|
|
38688
|
-
if (incorrectSize) {
|
|
38689
|
-
return {
|
|
38690
|
-
type: "points",
|
|
38691
|
-
earned: 0,
|
|
38692
|
-
total: 1,
|
|
38693
|
-
message: null
|
|
38694
|
-
};
|
|
38695
|
-
}
|
|
38696
|
-
return {
|
|
38697
|
-
type: "points",
|
|
38698
|
-
earned: incorrect ? 0 : 1,
|
|
38699
|
-
total: 1,
|
|
38700
|
-
message: message
|
|
38701
|
-
};
|
|
38833
|
+
return matrixValidator(state, rubric, strings);
|
|
38702
38834
|
}
|
|
38703
38835
|
state = {
|
|
38704
38836
|
enterTheMatrix: 0
|
|
@@ -38837,14 +38969,14 @@ class Matrix extends React__namespace.Component {
|
|
|
38837
38969
|
}, cb);
|
|
38838
38970
|
this.props.trackInteraction();
|
|
38839
38971
|
};
|
|
38840
|
-
getUserInput
|
|
38972
|
+
getUserInput() {
|
|
38841
38973
|
return {
|
|
38842
38974
|
answers: this.props.answers
|
|
38843
38975
|
};
|
|
38844
|
-
}
|
|
38845
|
-
simpleValidate
|
|
38846
|
-
return
|
|
38847
|
-
}
|
|
38976
|
+
}
|
|
38977
|
+
simpleValidate(rubric) {
|
|
38978
|
+
return matrixValidator(this.getUserInput(), rubric, this.context.strings);
|
|
38979
|
+
}
|
|
38848
38980
|
render() {
|
|
38849
38981
|
// Set the input sizes through JS so we can control the size of the
|
|
38850
38982
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -40057,9 +40189,9 @@ class MoleculeWidget extends React__namespace.Component {
|
|
|
40057
40189
|
simpleValidate() {
|
|
40058
40190
|
return noopValidator();
|
|
40059
40191
|
}
|
|
40060
|
-
getUserInput
|
|
40192
|
+
getUserInput() {
|
|
40061
40193
|
return [];
|
|
40062
|
-
}
|
|
40194
|
+
}
|
|
40063
40195
|
|
|
40064
40196
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
40065
40197
|
validate() {
|
|
@@ -40080,6 +40212,45 @@ var Molecule$1 = {
|
|
|
40080
40212
|
widget: MoleculeWidget
|
|
40081
40213
|
};
|
|
40082
40214
|
|
|
40215
|
+
function numberLineValidator(state, rubric) {
|
|
40216
|
+
const range = rubric.range;
|
|
40217
|
+
const divisionRange = state.divisionRange;
|
|
40218
|
+
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
40219
|
+
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
40220
|
+
const correctRel = rubric.correctRel || "eq";
|
|
40221
|
+
const correctPos = kmath.number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
40222
|
+
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
40223
|
+
|
|
40224
|
+
// TODO: I don't think isTickCrtl is a thing anymore
|
|
40225
|
+
if (state.isTickCrtl && outsideAllowedRange) {
|
|
40226
|
+
return {
|
|
40227
|
+
type: "invalid",
|
|
40228
|
+
message: "Number of divisions is outside the allowed range."
|
|
40229
|
+
};
|
|
40230
|
+
}
|
|
40231
|
+
if (correctPos && correctRel === state.rel) {
|
|
40232
|
+
return {
|
|
40233
|
+
type: "points",
|
|
40234
|
+
earned: 1,
|
|
40235
|
+
total: 1,
|
|
40236
|
+
message: null
|
|
40237
|
+
};
|
|
40238
|
+
}
|
|
40239
|
+
if (state.numLinePosition === start && state.rel === startRel) {
|
|
40240
|
+
// We're where we started.
|
|
40241
|
+
return {
|
|
40242
|
+
type: "invalid",
|
|
40243
|
+
message: null
|
|
40244
|
+
};
|
|
40245
|
+
}
|
|
40246
|
+
return {
|
|
40247
|
+
type: "points",
|
|
40248
|
+
earned: 0,
|
|
40249
|
+
total: 1,
|
|
40250
|
+
message: null
|
|
40251
|
+
};
|
|
40252
|
+
}
|
|
40253
|
+
|
|
40083
40254
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
40084
40255
|
const MovablePoint = Graphie.MovablePoint;
|
|
40085
40256
|
// @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
|
|
@@ -40205,6 +40376,9 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
40205
40376
|
});
|
|
40206
40377
|
return results;
|
|
40207
40378
|
});
|
|
40379
|
+
|
|
40380
|
+
// TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
|
|
40381
|
+
// should this one?
|
|
40208
40382
|
class NumberLine extends React__namespace.Component {
|
|
40209
40383
|
static contextType = PerseusI18nContext;
|
|
40210
40384
|
static defaultProps = {
|
|
@@ -40224,6 +40398,9 @@ class NumberLine extends React__namespace.Component {
|
|
|
40224
40398
|
state = {
|
|
40225
40399
|
numDivisionsEmpty: false
|
|
40226
40400
|
};
|
|
40401
|
+
static validate(state, rubric) {
|
|
40402
|
+
return numberLineValidator(state, rubric);
|
|
40403
|
+
}
|
|
40227
40404
|
change = (() => {
|
|
40228
40405
|
var _this = this;
|
|
40229
40406
|
return function () {
|
|
@@ -40506,18 +40683,17 @@ class NumberLine extends React__namespace.Component {
|
|
|
40506
40683
|
arrows: "->"
|
|
40507
40684
|
});
|
|
40508
40685
|
};
|
|
40509
|
-
getUserInput
|
|
40686
|
+
getUserInput() {
|
|
40510
40687
|
return {
|
|
40511
40688
|
numLinePosition: this.props.numLinePosition,
|
|
40512
40689
|
rel: this.props.isInequality ? this.props.rel : "eq",
|
|
40513
40690
|
numDivisions: this.props.numDivisions,
|
|
40514
40691
|
divisionRange: this.props.divisionRange
|
|
40515
40692
|
};
|
|
40516
|
-
}
|
|
40517
|
-
simpleValidate
|
|
40518
|
-
|
|
40519
|
-
|
|
40520
|
-
};
|
|
40693
|
+
}
|
|
40694
|
+
simpleValidate(rubric) {
|
|
40695
|
+
return numberLineValidator(this.getUserInput(), rubric);
|
|
40696
|
+
}
|
|
40521
40697
|
render() {
|
|
40522
40698
|
const {
|
|
40523
40699
|
strings
|
|
@@ -40568,44 +40744,6 @@ class NumberLine extends React__namespace.Component {
|
|
|
40568
40744
|
})) : this._renderGraphie(), !this.props.static && this.props.isInequality && inequalityControls);
|
|
40569
40745
|
}
|
|
40570
40746
|
}
|
|
40571
|
-
___default["default"].extend(NumberLine, {
|
|
40572
|
-
validate: function (state, rubric) {
|
|
40573
|
-
const range = rubric.range;
|
|
40574
|
-
const divisionRange = state.divisionRange;
|
|
40575
|
-
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
40576
|
-
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
40577
|
-
const correctRel = rubric.correctRel || "eq";
|
|
40578
|
-
const correctPos = kmath.number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
40579
|
-
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
40580
|
-
if (state.isTickCrtl && outsideAllowedRange) {
|
|
40581
|
-
return {
|
|
40582
|
-
type: "invalid",
|
|
40583
|
-
message: "Number of divisions is outside the allowed range."
|
|
40584
|
-
};
|
|
40585
|
-
}
|
|
40586
|
-
if (correctPos && correctRel === state.rel) {
|
|
40587
|
-
return {
|
|
40588
|
-
type: "points",
|
|
40589
|
-
earned: 1,
|
|
40590
|
-
total: 1,
|
|
40591
|
-
message: null
|
|
40592
|
-
};
|
|
40593
|
-
}
|
|
40594
|
-
if (state.numLinePosition === start && state.rel === startRel) {
|
|
40595
|
-
// We're where we started.
|
|
40596
|
-
return {
|
|
40597
|
-
type: "invalid",
|
|
40598
|
-
message: null
|
|
40599
|
-
};
|
|
40600
|
-
}
|
|
40601
|
-
return {
|
|
40602
|
-
type: "points",
|
|
40603
|
-
earned: 0,
|
|
40604
|
-
total: 1,
|
|
40605
|
-
message: null
|
|
40606
|
-
};
|
|
40607
|
-
}
|
|
40608
|
-
});
|
|
40609
40747
|
const numberLineTransform = editorProps => {
|
|
40610
40748
|
const props = ___default["default"].pick(editorProps, ["range", "labelRange", "labelStyle", "labelTicks", "divisionRange", "snapDivisions", "isTickCtrl", "isInequality", "showTooltips"]);
|
|
40611
40749
|
const numLinePosition = editorProps.initialX != null ? editorProps.initialX : editorProps.range[0];
|
|
@@ -40829,8 +40967,8 @@ class Card extends React__namespace.Component {
|
|
|
40829
40967
|
}))));
|
|
40830
40968
|
}
|
|
40831
40969
|
}
|
|
40832
|
-
const NORMAL = "normal"
|
|
40833
|
-
|
|
40970
|
+
const NORMAL = "normal";
|
|
40971
|
+
const HORIZONTAL = "horizontal";
|
|
40834
40972
|
class Orderer extends React__namespace.Component {
|
|
40835
40973
|
static defaultProps = {
|
|
40836
40974
|
current: [],
|
|
@@ -41085,17 +41223,17 @@ class Orderer extends React__namespace.Component {
|
|
|
41085
41223
|
current: list
|
|
41086
41224
|
});
|
|
41087
41225
|
};
|
|
41088
|
-
getUserInput
|
|
41226
|
+
getUserInput() {
|
|
41089
41227
|
return {
|
|
41090
41228
|
current: ___default["default"].map(this.props.current, function (v) {
|
|
41091
41229
|
return v.content;
|
|
41092
41230
|
})
|
|
41093
41231
|
};
|
|
41094
|
-
}
|
|
41095
|
-
simpleValidate
|
|
41232
|
+
}
|
|
41233
|
+
simpleValidate(rubric) {
|
|
41096
41234
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
|
|
41097
41235
|
return Orderer.validate(this.getUserInput(), rubric);
|
|
41098
|
-
}
|
|
41236
|
+
}
|
|
41099
41237
|
render() {
|
|
41100
41238
|
// This is the card we are currently dragging
|
|
41101
41239
|
const dragging = this.state.dragging && /*#__PURE__*/React__namespace.createElement(Card
|
|
@@ -41201,14 +41339,14 @@ class Orderer extends React__namespace.Component {
|
|
|
41201
41339
|
}
|
|
41202
41340
|
}
|
|
41203
41341
|
___default["default"].extend(Orderer, {
|
|
41204
|
-
validate: function (
|
|
41205
|
-
if (
|
|
41342
|
+
validate: function (userInput, rubric) {
|
|
41343
|
+
if (userInput.current.length === 0) {
|
|
41206
41344
|
return {
|
|
41207
41345
|
type: "invalid",
|
|
41208
41346
|
message: null
|
|
41209
41347
|
};
|
|
41210
41348
|
}
|
|
41211
|
-
const correct = ___default["default"].isEqual(
|
|
41349
|
+
const correct = ___default["default"].isEqual(userInput.current, ___default["default"].pluck(rubric.correctOptions, "content"));
|
|
41212
41350
|
return {
|
|
41213
41351
|
type: "points",
|
|
41214
41352
|
earned: correct ? 1 : 0,
|
|
@@ -43418,9 +43556,9 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
43418
43556
|
}
|
|
43419
43557
|
|
|
43420
43558
|
// TODO passage-ref-target isn't interactive; remove
|
|
43421
|
-
getUserInput
|
|
43422
|
-
return;
|
|
43423
|
-
}
|
|
43559
|
+
getUserInput() {
|
|
43560
|
+
return null;
|
|
43561
|
+
}
|
|
43424
43562
|
|
|
43425
43563
|
// TODO passage-ref-target isn't interactive; remove
|
|
43426
43564
|
change = (() => {
|
|
@@ -43472,9 +43610,6 @@ var PassageRefTarget$1 = {
|
|
|
43472
43610
|
* This is a PhET simulation widget. It is used for rendering simulations
|
|
43473
43611
|
* from https://phet.colorado.edu/.
|
|
43474
43612
|
*/
|
|
43475
|
-
|
|
43476
|
-
// For returning user input, but currently the PhET widget
|
|
43477
|
-
// does not support accessing user input
|
|
43478
43613
|
// This renders the PhET sim
|
|
43479
43614
|
class PhetSimulation extends React__namespace.Component {
|
|
43480
43615
|
static contextType = PerseusI18nContext;
|
|
@@ -43497,6 +43632,8 @@ class PhetSimulation extends React__namespace.Component {
|
|
|
43497
43632
|
await this.updateSimState(this.props.url);
|
|
43498
43633
|
}
|
|
43499
43634
|
}
|
|
43635
|
+
|
|
43636
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43500
43637
|
getUserInput() {
|
|
43501
43638
|
return null;
|
|
43502
43639
|
}
|
|
@@ -43673,10 +43810,25 @@ var PhetSimulation$1 = {
|
|
|
43673
43810
|
isLintable: true
|
|
43674
43811
|
};
|
|
43675
43812
|
|
|
43676
|
-
/* eslint-disable react/no-unsafe */
|
|
43677
43813
|
const {
|
|
43678
43814
|
deepEq
|
|
43679
43815
|
} = Util;
|
|
43816
|
+
function plotterValidator(userInput, rubric) {
|
|
43817
|
+
if (deepEq(userInput, rubric.starting)) {
|
|
43818
|
+
return {
|
|
43819
|
+
type: "invalid",
|
|
43820
|
+
message: null
|
|
43821
|
+
};
|
|
43822
|
+
}
|
|
43823
|
+
return {
|
|
43824
|
+
type: "points",
|
|
43825
|
+
earned: deepEq(userInput, rubric.correct) ? 1 : 0,
|
|
43826
|
+
total: 1,
|
|
43827
|
+
message: null
|
|
43828
|
+
};
|
|
43829
|
+
}
|
|
43830
|
+
|
|
43831
|
+
/* eslint-disable react/no-unsafe */
|
|
43680
43832
|
class Plotter extends React__namespace.Component {
|
|
43681
43833
|
static contextType = PerseusI18nContext;
|
|
43682
43834
|
// @ts-expect-error - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -43705,6 +43857,9 @@ class Plotter extends React__namespace.Component {
|
|
|
43705
43857
|
// bottom label.
|
|
43706
43858
|
categoryHeights: {}
|
|
43707
43859
|
};
|
|
43860
|
+
static validate(userInput, rubric) {
|
|
43861
|
+
return plotterValidator(userInput, rubric);
|
|
43862
|
+
}
|
|
43708
43863
|
componentDidMount() {
|
|
43709
43864
|
this._isMounted = true;
|
|
43710
43865
|
this.setupGraphie(this.state);
|
|
@@ -44446,13 +44601,12 @@ class Plotter extends React__namespace.Component {
|
|
|
44446
44601
|
});
|
|
44447
44602
|
});
|
|
44448
44603
|
};
|
|
44449
|
-
getUserInput
|
|
44604
|
+
getUserInput() {
|
|
44450
44605
|
return this.state.values;
|
|
44451
|
-
}
|
|
44452
|
-
simpleValidate
|
|
44453
|
-
|
|
44454
|
-
|
|
44455
|
-
};
|
|
44606
|
+
}
|
|
44607
|
+
simpleValidate(rubric) {
|
|
44608
|
+
return plotterValidator(this.getUserInput(), rubric);
|
|
44609
|
+
}
|
|
44456
44610
|
render() {
|
|
44457
44611
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
44458
44612
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -44470,22 +44624,6 @@ class Plotter extends React__namespace.Component {
|
|
|
44470
44624
|
});
|
|
44471
44625
|
}
|
|
44472
44626
|
}
|
|
44473
|
-
___default["default"].extend(Plotter, {
|
|
44474
|
-
validate: function (guess, rubric) {
|
|
44475
|
-
if (deepEq(guess, rubric.starting)) {
|
|
44476
|
-
return {
|
|
44477
|
-
type: "invalid",
|
|
44478
|
-
message: null
|
|
44479
|
-
};
|
|
44480
|
-
}
|
|
44481
|
-
return {
|
|
44482
|
-
type: "points",
|
|
44483
|
-
earned: deepEq(guess, rubric.correct) ? 1 : 0,
|
|
44484
|
-
total: 1,
|
|
44485
|
-
message: null
|
|
44486
|
-
};
|
|
44487
|
-
}
|
|
44488
|
-
});
|
|
44489
44627
|
|
|
44490
44628
|
// We don't need to change any of the original props for static mode
|
|
44491
44629
|
const staticTransform = ___default["default"].identity;
|
|
@@ -44543,6 +44681,28 @@ var PythonProgram$1 = {
|
|
|
44543
44681
|
widget: PythonProgram
|
|
44544
44682
|
};
|
|
44545
44683
|
|
|
44684
|
+
function sorterValidator(userInput, rubric) {
|
|
44685
|
+
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
44686
|
+
// prevents the "Check" button from becoming active. We want the user
|
|
44687
|
+
// to make a change before trying to move forward. This makes an
|
|
44688
|
+
// assumption that the initial order isn't the correct order! However,
|
|
44689
|
+
// this should be rare if it happens, and interacting with the list
|
|
44690
|
+
// will enable the button, so they won't be locked out of progressing.
|
|
44691
|
+
if (!userInput.changed) {
|
|
44692
|
+
return {
|
|
44693
|
+
type: "invalid",
|
|
44694
|
+
message: null
|
|
44695
|
+
};
|
|
44696
|
+
}
|
|
44697
|
+
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
44698
|
+
return {
|
|
44699
|
+
type: "points",
|
|
44700
|
+
earned: correct ? 1 : 0,
|
|
44701
|
+
total: 1,
|
|
44702
|
+
message: null
|
|
44703
|
+
};
|
|
44704
|
+
}
|
|
44705
|
+
|
|
44546
44706
|
const {
|
|
44547
44707
|
shuffle
|
|
44548
44708
|
} = Util;
|
|
@@ -44556,14 +44716,8 @@ class Sorter extends React__namespace.Component {
|
|
|
44556
44716
|
onChange: function () {},
|
|
44557
44717
|
linterContext: PerseusLinter.linterContextDefault
|
|
44558
44718
|
};
|
|
44559
|
-
static validate(
|
|
44560
|
-
|
|
44561
|
-
return {
|
|
44562
|
-
type: "points",
|
|
44563
|
-
earned: correct ? 1 : 0,
|
|
44564
|
-
total: 1,
|
|
44565
|
-
message: null
|
|
44566
|
-
};
|
|
44719
|
+
static validate(userInput, rubric) {
|
|
44720
|
+
return sorterValidator(userInput, rubric);
|
|
44567
44721
|
}
|
|
44568
44722
|
state = {
|
|
44569
44723
|
changed: false
|
|
@@ -44593,33 +44747,23 @@ class Sorter extends React__namespace.Component {
|
|
|
44593
44747
|
}, 0);
|
|
44594
44748
|
});
|
|
44595
44749
|
};
|
|
44596
|
-
getUserInput
|
|
44750
|
+
getUserInput() {
|
|
44597
44751
|
// eslint-disable-next-line react/no-string-refs
|
|
44598
44752
|
// @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
|
|
44753
|
+
const options = this.refs.sortable.getOptions();
|
|
44599
44754
|
return {
|
|
44600
|
-
options
|
|
44755
|
+
options,
|
|
44756
|
+
changed: this.state.changed
|
|
44601
44757
|
};
|
|
44602
|
-
}
|
|
44758
|
+
}
|
|
44603
44759
|
moveOptionToIndex = (option, index) => {
|
|
44604
44760
|
// eslint-disable-next-line react/no-string-refs
|
|
44605
44761
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
44606
44762
|
this.refs.sortable.moveOptionToIndex(option, index);
|
|
44607
44763
|
};
|
|
44608
|
-
simpleValidate
|
|
44609
|
-
|
|
44610
|
-
|
|
44611
|
-
// to make a change before trying to move forward. This makes an
|
|
44612
|
-
// assumption that the initial order isn't the correct order! However,
|
|
44613
|
-
// this should be rare if it happens, and interacting with the list
|
|
44614
|
-
// will enable the button, so they won't be locked out of progressing.
|
|
44615
|
-
if (!this.state.changed) {
|
|
44616
|
-
return {
|
|
44617
|
-
type: "invalid",
|
|
44618
|
-
message: null
|
|
44619
|
-
};
|
|
44620
|
-
}
|
|
44621
|
-
return Sorter.validate(this.getUserInput(), rubric);
|
|
44622
|
-
};
|
|
44764
|
+
simpleValidate(rubric) {
|
|
44765
|
+
return sorterValidator(this.getUserInput(), rubric);
|
|
44766
|
+
}
|
|
44623
44767
|
render() {
|
|
44624
44768
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
44625
44769
|
const {
|
|
@@ -44648,6 +44792,66 @@ var Sorter$1 = {
|
|
|
44648
44792
|
isLintable: true
|
|
44649
44793
|
};
|
|
44650
44794
|
|
|
44795
|
+
function tableValidator(state, rubric, strings) {
|
|
44796
|
+
const filterNonEmpty = function (table) {
|
|
44797
|
+
return ___default["default"].filter(table, function (row) {
|
|
44798
|
+
// Check if row has a cell that is nonempty
|
|
44799
|
+
return ___default["default"].some(row, ___default["default"].identity);
|
|
44800
|
+
});
|
|
44801
|
+
};
|
|
44802
|
+
const solution = filterNonEmpty(rubric.answers);
|
|
44803
|
+
const supplied = filterNonEmpty(state);
|
|
44804
|
+
const hasEmptyCell = ___default["default"].some(supplied, function (row) {
|
|
44805
|
+
return ___default["default"].some(row, function (cell) {
|
|
44806
|
+
return cell === "";
|
|
44807
|
+
});
|
|
44808
|
+
});
|
|
44809
|
+
if (hasEmptyCell || !supplied.length) {
|
|
44810
|
+
return {
|
|
44811
|
+
type: "invalid",
|
|
44812
|
+
message: null
|
|
44813
|
+
};
|
|
44814
|
+
}
|
|
44815
|
+
if (supplied.length !== solution.length) {
|
|
44816
|
+
return {
|
|
44817
|
+
type: "points",
|
|
44818
|
+
earned: 0,
|
|
44819
|
+
total: 1,
|
|
44820
|
+
message: null
|
|
44821
|
+
};
|
|
44822
|
+
}
|
|
44823
|
+
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44824
|
+
let message = null;
|
|
44825
|
+
const allCorrect = ___default["default"].every(solution, function (rowSolution) {
|
|
44826
|
+
for (let i = 0; i < supplied.length; i++) {
|
|
44827
|
+
const rowSupplied = supplied[i];
|
|
44828
|
+
const correct = ___default["default"].every(rowSupplied, function (cellSupplied, i) {
|
|
44829
|
+
const cellSolution = rowSolution[i];
|
|
44830
|
+
const validator = createValidator(cellSolution, {
|
|
44831
|
+
simplify: true
|
|
44832
|
+
}, strings);
|
|
44833
|
+
const result = validator(cellSupplied);
|
|
44834
|
+
if (result.message) {
|
|
44835
|
+
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
44836
|
+
message = result.message;
|
|
44837
|
+
}
|
|
44838
|
+
return result.correct;
|
|
44839
|
+
});
|
|
44840
|
+
if (correct) {
|
|
44841
|
+
supplied.splice(i, 1);
|
|
44842
|
+
return true;
|
|
44843
|
+
}
|
|
44844
|
+
}
|
|
44845
|
+
return false;
|
|
44846
|
+
});
|
|
44847
|
+
return {
|
|
44848
|
+
type: "points",
|
|
44849
|
+
earned: allCorrect ? 1 : 0,
|
|
44850
|
+
total: 1,
|
|
44851
|
+
message: message
|
|
44852
|
+
};
|
|
44853
|
+
}
|
|
44854
|
+
|
|
44651
44855
|
const {
|
|
44652
44856
|
assert
|
|
44653
44857
|
} = InteractiveUtil;
|
|
@@ -44693,15 +44897,18 @@ class Table extends React__namespace.Component {
|
|
|
44693
44897
|
linterContext: PerseusLinter.linterContextDefault
|
|
44694
44898
|
};
|
|
44695
44899
|
}();
|
|
44900
|
+
static validate(state, rubric, strings) {
|
|
44901
|
+
return tableValidator(state, rubric, strings);
|
|
44902
|
+
}
|
|
44696
44903
|
_getRows = () => {
|
|
44697
44904
|
return this.props.answers.length;
|
|
44698
44905
|
};
|
|
44699
44906
|
_getColumns = () => {
|
|
44700
44907
|
return this.props.answers[0].length;
|
|
44701
44908
|
};
|
|
44702
|
-
getUserInput
|
|
44909
|
+
getUserInput() {
|
|
44703
44910
|
return ___default["default"].map(this.props.answers, ___default["default"].clone);
|
|
44704
|
-
}
|
|
44911
|
+
}
|
|
44705
44912
|
onValueChange = (row, column, eventOrValue) => {
|
|
44706
44913
|
const answers = ___default["default"].map(this.props.answers, ___default["default"].clone);
|
|
44707
44914
|
|
|
@@ -44721,10 +44928,9 @@ class Table extends React__namespace.Component {
|
|
|
44721
44928
|
headers: headers
|
|
44722
44929
|
});
|
|
44723
44930
|
};
|
|
44724
|
-
simpleValidate
|
|
44725
|
-
|
|
44726
|
-
|
|
44727
|
-
};
|
|
44931
|
+
simpleValidate(rubric) {
|
|
44932
|
+
return tableValidator(this.getUserInput(), rubric, this.context.strings);
|
|
44933
|
+
}
|
|
44728
44934
|
_handleFocus = inputPath => {
|
|
44729
44935
|
this.props.onFocus(inputPath);
|
|
44730
44936
|
};
|
|
@@ -44850,68 +45056,6 @@ class Table extends React__namespace.Component {
|
|
|
44850
45056
|
})));
|
|
44851
45057
|
}
|
|
44852
45058
|
}
|
|
44853
|
-
___default["default"].extend(Table, {
|
|
44854
|
-
validate: function (state, rubric, strings) {
|
|
44855
|
-
const filterNonEmpty = function (table) {
|
|
44856
|
-
return ___default["default"].filter(table, function (row) {
|
|
44857
|
-
// Check if row has a cell that is nonempty
|
|
44858
|
-
return ___default["default"].some(row, ___default["default"].identity);
|
|
44859
|
-
});
|
|
44860
|
-
};
|
|
44861
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44862
|
-
const supplied = filterNonEmpty(state);
|
|
44863
|
-
const hasEmptyCell = ___default["default"].some(supplied, function (row) {
|
|
44864
|
-
return ___default["default"].some(row, function (cell) {
|
|
44865
|
-
return cell === "";
|
|
44866
|
-
});
|
|
44867
|
-
});
|
|
44868
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44869
|
-
return {
|
|
44870
|
-
type: "invalid",
|
|
44871
|
-
message: null
|
|
44872
|
-
};
|
|
44873
|
-
}
|
|
44874
|
-
if (supplied.length !== solution.length) {
|
|
44875
|
-
return {
|
|
44876
|
-
type: "points",
|
|
44877
|
-
earned: 0,
|
|
44878
|
-
total: 1,
|
|
44879
|
-
message: null
|
|
44880
|
-
};
|
|
44881
|
-
}
|
|
44882
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44883
|
-
let message = null;
|
|
44884
|
-
const allCorrect = ___default["default"].every(solution, function (rowSolution) {
|
|
44885
|
-
let i;
|
|
44886
|
-
for (i = 0; i < supplied.length; i++) {
|
|
44887
|
-
const rowSupplied = supplied[i];
|
|
44888
|
-
const correct = ___default["default"].every(rowSupplied, function (cellSupplied, i) {
|
|
44889
|
-
const cellSolution = rowSolution[i];
|
|
44890
|
-
const validator = createValidator(cellSolution, {
|
|
44891
|
-
simplify: true
|
|
44892
|
-
}, strings);
|
|
44893
|
-
const result = validator(cellSupplied);
|
|
44894
|
-
if (result.message) {
|
|
44895
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
44896
|
-
message = result.message;
|
|
44897
|
-
}
|
|
44898
|
-
return result.correct;
|
|
44899
|
-
});
|
|
44900
|
-
if (correct) {
|
|
44901
|
-
supplied.splice(i, 1);
|
|
44902
|
-
return true;
|
|
44903
|
-
}
|
|
44904
|
-
}
|
|
44905
|
-
return false;
|
|
44906
|
-
});
|
|
44907
|
-
return {
|
|
44908
|
-
type: "points",
|
|
44909
|
-
earned: allCorrect ? 1 : 0,
|
|
44910
|
-
total: 1,
|
|
44911
|
-
message: message
|
|
44912
|
-
};
|
|
44913
|
-
}
|
|
44914
|
-
});
|
|
44915
45059
|
const propTransform = editorProps => {
|
|
44916
45060
|
// Remove answers before passing to widget
|
|
44917
45061
|
const rows = editorProps.answers.length;
|
|
@@ -45144,7 +45288,7 @@ const init = function (options) {
|
|
|
45144
45288
|
|
|
45145
45289
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45146
45290
|
const libName = "@khanacademy/perseus";
|
|
45147
|
-
const libVersion = "
|
|
45291
|
+
const libVersion = "35.1.1";
|
|
45148
45292
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45149
45293
|
|
|
45150
45294
|
const apiVersion = {
|
|
@@ -45698,9 +45842,6 @@ const defaultContext = {
|
|
|
45698
45842
|
};
|
|
45699
45843
|
const context = /*#__PURE__*/React__namespace.createContext(defaultContext);
|
|
45700
45844
|
|
|
45701
|
-
const {
|
|
45702
|
-
mapObject
|
|
45703
|
-
} = objective_;
|
|
45704
45845
|
class ServerItemRenderer extends React__namespace.Component {
|
|
45705
45846
|
static contextType = PerseusI18nContext;
|
|
45706
45847
|
// @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -45916,20 +46057,6 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
45916
46057
|
return this.questionRenderer.getWidgetIds();
|
|
45917
46058
|
}
|
|
45918
46059
|
|
|
45919
|
-
/**
|
|
45920
|
-
* Returns an object mapping from widget ID to KE-style score.
|
|
45921
|
-
* The keys of this object are the values of the array returned
|
|
45922
|
-
* from `getWidgetIds`.
|
|
45923
|
-
*/
|
|
45924
|
-
scoreWidgets() {
|
|
45925
|
-
const qScore = this.questionRenderer.scoreWidgets();
|
|
45926
|
-
const qGuess = this.questionRenderer.getUserInputForWidgets();
|
|
45927
|
-
const state = this.questionRenderer.getSerializedState();
|
|
45928
|
-
return mapObject(qScore, (score, id) => {
|
|
45929
|
-
return Util.keScoreFromPerseusScore(score, qGuess[id], state);
|
|
45930
|
-
});
|
|
45931
|
-
}
|
|
45932
|
-
|
|
45933
46060
|
/**
|
|
45934
46061
|
* Get a representation of the current state of the item.
|
|
45935
46062
|
*/
|