@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/es/index.js
CHANGED
|
@@ -246,7 +246,10 @@ const mockStrings = {
|
|
|
246
246
|
cos: "cos",
|
|
247
247
|
tan: "tan",
|
|
248
248
|
simulationLoadFail: "Sorry, this simulation cannot load.",
|
|
249
|
-
simulationLocaleWarning: "Sorry, this simulation isn't available in your language."
|
|
249
|
+
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
250
|
+
addPoint: "Add Point",
|
|
251
|
+
removePoint: "Remove Point",
|
|
252
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
|
|
250
253
|
};
|
|
251
254
|
|
|
252
255
|
/**
|
|
@@ -749,7 +752,7 @@ const denylist = [
|
|
|
749
752
|
// (technically the renderer still adds them)
|
|
750
753
|
"key", "ref",
|
|
751
754
|
// added by src/renderer.jsx
|
|
752
|
-
"containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "questionCompleted", "findWidgets",
|
|
755
|
+
"containerSizeClass", "widgetId", "onChange", "problemNum", "apiOptions", "widgetIsOpen", "questionCompleted", "findWidgets",
|
|
753
756
|
// added by src/editor.jsx, for widgets removing themselves
|
|
754
757
|
// this is soooo not the right place for this, but alas.
|
|
755
758
|
"onRemove",
|
|
@@ -757,15 +760,6 @@ const denylist = [
|
|
|
757
760
|
"id",
|
|
758
761
|
// Callbacks and items for interaction handling
|
|
759
762
|
"onBlur", "onFocus", "trackInteraction", "keypadElement"];
|
|
760
|
-
const removeDenylistProps = props => {
|
|
761
|
-
const newProps = _extends({}, props);
|
|
762
|
-
for (const prop of denylist) {
|
|
763
|
-
if (prop in newProps) {
|
|
764
|
-
delete newProps[prop];
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return newProps;
|
|
768
|
-
};
|
|
769
763
|
|
|
770
764
|
/* eslint-disable @babel/no-invalid-this */
|
|
771
765
|
const USAGE = "Usage:\n" + " this.change({propName: 5}, callback);\n" + ' this.change("propName", 5, callback);\n' + ' this.change("propName")';
|
|
@@ -1030,8 +1024,6 @@ StubTagEditor.defaultProps = {
|
|
|
1030
1024
|
* * nothing if it is purely a bug fix.
|
|
1031
1025
|
*
|
|
1032
1026
|
* Callbacks passed to Renderer/ItemRenderer:
|
|
1033
|
-
* * onInputError:
|
|
1034
|
-
* Called when there is an error grading a widget
|
|
1035
1027
|
* * onFocusChange: (newFocusPath, oldFocusPath, keypadDOMNode)
|
|
1036
1028
|
* Called when the user focus changes. The first two parameters are `path`
|
|
1037
1029
|
* arrays uniquely identifying the respect inputs. The third parameter,
|
|
@@ -1054,7 +1046,6 @@ StubTagEditor.defaultProps = {
|
|
|
1054
1046
|
const ApiOptions = {
|
|
1055
1047
|
propTypes: PropTypes.shape({
|
|
1056
1048
|
isArticle: PropTypes.bool.isRequired,
|
|
1057
|
-
onInputError: PropTypes.func.isRequired,
|
|
1058
1049
|
onFocusChange: PropTypes.func.isRequired,
|
|
1059
1050
|
GroupMetadataEditor: PropTypes.func.isRequired,
|
|
1060
1051
|
showAlignmentOptions: PropTypes.bool.isRequired,
|
|
@@ -1138,7 +1129,6 @@ const ApiOptions = {
|
|
|
1138
1129
|
defaults: {
|
|
1139
1130
|
isArticle: false,
|
|
1140
1131
|
isMobile: false,
|
|
1141
|
-
onInputError: function () {},
|
|
1142
1132
|
onFocusChange: function () {},
|
|
1143
1133
|
GroupMetadataEditor: StubTagEditor,
|
|
1144
1134
|
showAlignmentOptions: false,
|
|
@@ -2150,9 +2140,7 @@ const getDecimalSeparator = locale => {
|
|
|
2150
2140
|
* show the user an error. TODO(joel) - what error?
|
|
2151
2141
|
* - Otherwise, pass through the resulting points and message.
|
|
2152
2142
|
*/
|
|
2153
|
-
function expressionValidator(userInput, rubric,
|
|
2154
|
-
// @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
|
|
2155
|
-
onInputError = function () {}, strings, locale) {
|
|
2143
|
+
function expressionValidator(userInput, rubric, strings, locale) {
|
|
2156
2144
|
const options = _.clone(rubric);
|
|
2157
2145
|
_.extend(options, {
|
|
2158
2146
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2252,14 +2240,9 @@ onInputError = function () {}, strings, locale) {
|
|
|
2252
2240
|
};
|
|
2253
2241
|
}
|
|
2254
2242
|
if (matchingAnswerForm.considered === "ungraded") {
|
|
2255
|
-
// We matched an ungraded answer form - return "invalid", which
|
|
2256
|
-
// will let the user try again with no penalty
|
|
2257
|
-
const apiResult = onInputError(null,
|
|
2258
|
-
// Reserved for some widget identifier
|
|
2259
|
-
userInput, matchMessage);
|
|
2260
2243
|
return {
|
|
2261
2244
|
type: "invalid",
|
|
2262
|
-
message:
|
|
2245
|
+
message: matchMessage
|
|
2263
2246
|
};
|
|
2264
2247
|
}
|
|
2265
2248
|
// We matched a graded answer form, so we can now tell the user
|
|
@@ -2348,14 +2331,9 @@ class Expression extends React.Component {
|
|
|
2348
2331
|
showErrorStyle: false
|
|
2349
2332
|
});
|
|
2350
2333
|
if (!this.parse(this.props.value, this.props).parsed) {
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
if (apiResult !== false) {
|
|
2355
|
-
this.setState({
|
|
2356
|
-
invalid: true
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2334
|
+
this.setState({
|
|
2335
|
+
invalid: true
|
|
2336
|
+
});
|
|
2359
2337
|
}
|
|
2360
2338
|
}
|
|
2361
2339
|
};
|
|
@@ -2369,12 +2347,12 @@ class Expression extends React.Component {
|
|
|
2369
2347
|
});
|
|
2370
2348
|
}
|
|
2371
2349
|
};
|
|
2372
|
-
this.simpleValidate =
|
|
2350
|
+
this.simpleValidate = _ref => {
|
|
2373
2351
|
let {
|
|
2374
2352
|
scoring
|
|
2375
2353
|
} = _ref,
|
|
2376
2354
|
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
|
|
2377
|
-
const score = expressionValidator(this.getUserInput(), rubric,
|
|
2355
|
+
const score = expressionValidator(this.getUserInput(), rubric, this.context.strings, this.context.locale);
|
|
2378
2356
|
|
|
2379
2357
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
2380
2358
|
// otherwise, we may just be checking validity after changes.
|
|
@@ -2390,9 +2368,6 @@ class Expression extends React.Component {
|
|
|
2390
2368
|
}
|
|
2391
2369
|
return score;
|
|
2392
2370
|
};
|
|
2393
|
-
this.getUserInput = () => {
|
|
2394
|
-
return Expression.getUserInputFromProps(this.props);
|
|
2395
|
-
};
|
|
2396
2371
|
this.change = (...args) => {
|
|
2397
2372
|
return change.apply(this, args);
|
|
2398
2373
|
};
|
|
@@ -2465,10 +2440,8 @@ class Expression extends React.Component {
|
|
|
2465
2440
|
};
|
|
2466
2441
|
}
|
|
2467
2442
|
// TODO remove this in favor of just using expressionValidator
|
|
2468
|
-
static validate(userInput, rubric,
|
|
2469
|
-
|
|
2470
|
-
onInputError = function () {}, strings, locale) {
|
|
2471
|
-
return expressionValidator(userInput, rubric, onInputError, strings, locale);
|
|
2443
|
+
static validate(userInput, rubric, strings, locale) {
|
|
2444
|
+
return expressionValidator(userInput, rubric, strings, locale);
|
|
2472
2445
|
}
|
|
2473
2446
|
static getUserInputFromProps(props) {
|
|
2474
2447
|
return normalizeTex(props.value);
|
|
@@ -2482,6 +2455,9 @@ class Expression extends React.Component {
|
|
|
2482
2455
|
}
|
|
2483
2456
|
//#endregion
|
|
2484
2457
|
|
|
2458
|
+
getUserInput() {
|
|
2459
|
+
return Expression.getUserInputFromProps(this.props);
|
|
2460
|
+
}
|
|
2485
2461
|
// HACK(joel)
|
|
2486
2462
|
insert(keyPressed) {
|
|
2487
2463
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -3170,7 +3146,7 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
3170
3146
|
/**
|
|
3171
3147
|
* Approximate equality on numbers and primitives.
|
|
3172
3148
|
*/
|
|
3173
|
-
function eq$
|
|
3149
|
+
function eq$4(x, y) {
|
|
3174
3150
|
if (typeof x === "number" && typeof y === "number") {
|
|
3175
3151
|
return Math.abs(x - y) < 1e-9;
|
|
3176
3152
|
}
|
|
@@ -3197,7 +3173,7 @@ function deepEq$5(x, y) {
|
|
|
3197
3173
|
return false;
|
|
3198
3174
|
}
|
|
3199
3175
|
if (typeof x === "function" && typeof y === "function") {
|
|
3200
|
-
return eq$
|
|
3176
|
+
return eq$4(x, y);
|
|
3201
3177
|
}
|
|
3202
3178
|
if (typeof x === "function" || typeof y === "function") {
|
|
3203
3179
|
return false;
|
|
@@ -3214,7 +3190,7 @@ function deepEq$5(x, y) {
|
|
|
3214
3190
|
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
3215
3191
|
return false;
|
|
3216
3192
|
}
|
|
3217
|
-
return eq$
|
|
3193
|
+
return eq$4(x, y);
|
|
3218
3194
|
}
|
|
3219
3195
|
|
|
3220
3196
|
/**
|
|
@@ -3514,7 +3490,7 @@ const Util = {
|
|
|
3514
3490
|
gridStepFromTickStep,
|
|
3515
3491
|
tickStepFromNumTicks,
|
|
3516
3492
|
constrainedTickStepsFromTickSteps,
|
|
3517
|
-
eq: eq$
|
|
3493
|
+
eq: eq$4,
|
|
3518
3494
|
deepEq: deepEq$5,
|
|
3519
3495
|
parseQueryString,
|
|
3520
3496
|
updateQueryString: updateQueryString$2,
|
|
@@ -10837,7 +10813,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10837
10813
|
* A collection of geomtry-related utility functions
|
|
10838
10814
|
*/
|
|
10839
10815
|
const {
|
|
10840
|
-
eq: eq$
|
|
10816
|
+
eq: eq$3,
|
|
10841
10817
|
deepEq: deepEq$4
|
|
10842
10818
|
} = Util;
|
|
10843
10819
|
|
|
@@ -10847,7 +10823,7 @@ const {
|
|
|
10847
10823
|
|
|
10848
10824
|
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10849
10825
|
function sign(val) {
|
|
10850
|
-
if (eq$
|
|
10826
|
+
if (eq$3(val, 0)) {
|
|
10851
10827
|
return 0;
|
|
10852
10828
|
}
|
|
10853
10829
|
return val > 0 ? 1 : -1;
|
|
@@ -10859,7 +10835,7 @@ function ccw(a, b, c) {
|
|
|
10859
10835
|
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10860
10836
|
}
|
|
10861
10837
|
function collinear(a, b, c) {
|
|
10862
|
-
return eq$
|
|
10838
|
+
return eq$3(ccw(a, b, c), 0);
|
|
10863
10839
|
}
|
|
10864
10840
|
|
|
10865
10841
|
// Given rect bounding points A and B, whether point C is inside the rect
|
|
@@ -11004,7 +10980,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
11004
10980
|
return pair[0] / pair[1];
|
|
11005
10981
|
});
|
|
11006
10982
|
const same = _.all(factors, function (factor) {
|
|
11007
|
-
return eq$
|
|
10983
|
+
return eq$3(factors[0], factor);
|
|
11008
10984
|
});
|
|
11009
10985
|
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
11010
10986
|
return number.equal(pair[0], pair[1], tolerance);
|
|
@@ -11056,7 +11032,7 @@ function rotate(array, n) {
|
|
|
11056
11032
|
return array.slice(n).concat(array.slice(0, n));
|
|
11057
11033
|
}
|
|
11058
11034
|
function getLineEquation(first, second) {
|
|
11059
|
-
if (eq$
|
|
11035
|
+
if (eq$3(first[0], second[0])) {
|
|
11060
11036
|
return "x = " + first[0].toFixed(3);
|
|
11061
11037
|
}
|
|
11062
11038
|
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
@@ -14677,7 +14653,7 @@ const pluck = function pluck(table, subKey) {
|
|
|
14677
14653
|
* });
|
|
14678
14654
|
* {a: 2, b: 3}
|
|
14679
14655
|
*/
|
|
14680
|
-
const mapObject$
|
|
14656
|
+
const mapObject$1 = function mapObject(obj, lambda) {
|
|
14681
14657
|
const result = {};
|
|
14682
14658
|
_.each(_.keys(obj), function (key) {
|
|
14683
14659
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
@@ -14703,7 +14679,7 @@ const mapObjectFromArray$1 = function mapObjectFromArray(arr, lambda) {
|
|
|
14703
14679
|
};
|
|
14704
14680
|
var objective_ = {
|
|
14705
14681
|
pluck: pluck,
|
|
14706
|
-
mapObject: mapObject$
|
|
14682
|
+
mapObject: mapObject$1,
|
|
14707
14683
|
mapObjectFromArray: mapObjectFromArray$1
|
|
14708
14684
|
};
|
|
14709
14685
|
|
|
@@ -19596,7 +19572,7 @@ WidgetContainer.defaultProps = {
|
|
|
19596
19572
|
};
|
|
19597
19573
|
|
|
19598
19574
|
const {
|
|
19599
|
-
mapObject
|
|
19575
|
+
mapObject,
|
|
19600
19576
|
mapObjectFromArray
|
|
19601
19577
|
} = objective_;
|
|
19602
19578
|
const rContainsNonWhitespace = /\S/;
|
|
@@ -19624,9 +19600,6 @@ const isIdPathPrefix = function isIdPathPrefix(prefixArray, wholeArray) {
|
|
|
19624
19600
|
});
|
|
19625
19601
|
};
|
|
19626
19602
|
|
|
19627
|
-
// The return type for getUserInput. Widgets have full control of what is
|
|
19628
|
-
// returned so it's not easily typed (some widgets return a scalar (string),
|
|
19629
|
-
// some return a custom-built object
|
|
19630
19603
|
/**
|
|
19631
19604
|
* The Widget type represents the common API that the Renderer uses to interact
|
|
19632
19605
|
* with all widgets. All widgets must implement the methods in this API, unless
|
|
@@ -19636,6 +19609,7 @@ const isIdPathPrefix = function isIdPathPrefix(prefixArray, wholeArray) {
|
|
|
19636
19609
|
* communicate with the individual widgets to coordinate actions such as
|
|
19637
19610
|
* scoring, state serialization/deserialization, and focus management.
|
|
19638
19611
|
*/
|
|
19612
|
+
|
|
19639
19613
|
class Renderer extends React.Component {
|
|
19640
19614
|
constructor(_props) {
|
|
19641
19615
|
super(_props);
|
|
@@ -19670,7 +19644,7 @@ class Renderer extends React.Component {
|
|
|
19670
19644
|
};
|
|
19671
19645
|
// @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; }'.
|
|
19672
19646
|
this._getAllWidgetsInfo = props => {
|
|
19673
|
-
return mapObject
|
|
19647
|
+
return mapObject(props.widgets, (widgetInfo, widgetId) => {
|
|
19674
19648
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19675
19649
|
const newValues = {};
|
|
19676
19650
|
if (!widgetInfo.type) {
|
|
@@ -19811,7 +19785,7 @@ class Renderer extends React.Component {
|
|
|
19811
19785
|
* state from that instead of the current widget props.
|
|
19812
19786
|
*/
|
|
19813
19787
|
this.getSerializedState = widgetProps => {
|
|
19814
|
-
return mapObject
|
|
19788
|
+
return mapObject(widgetProps || this.state.widgetProps, (props, widgetId) => {
|
|
19815
19789
|
const widget = this.getWidgetInstance(widgetId);
|
|
19816
19790
|
if (widget && widget.getSerializedState) {
|
|
19817
19791
|
return widget.getSerializedState();
|
|
@@ -19847,7 +19821,7 @@ class Renderer extends React.Component {
|
|
|
19847
19821
|
}
|
|
19848
19822
|
};
|
|
19849
19823
|
this.setState({
|
|
19850
|
-
widgetProps: mapObject
|
|
19824
|
+
widgetProps: mapObject(serializedState, (props, widgetId) => {
|
|
19851
19825
|
const widget = this.getWidgetInstance(widgetId);
|
|
19852
19826
|
if (widget && widget.restoreSerializedState) {
|
|
19853
19827
|
// Note that we probably can't call
|
|
@@ -20681,7 +20655,6 @@ class Renderer extends React.Component {
|
|
|
20681
20655
|
*/
|
|
20682
20656
|
this.scoreWidgets = () => {
|
|
20683
20657
|
const widgetProps = this.state.widgetInfo;
|
|
20684
|
-
const onInputError = this.getApiOptions().onInputError;
|
|
20685
20658
|
const gradedWidgetIds = _.filter(this.widgetIds, id => {
|
|
20686
20659
|
const props = widgetProps[id];
|
|
20687
20660
|
const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
|
|
@@ -20697,7 +20670,7 @@ class Renderer extends React.Component {
|
|
|
20697
20670
|
if (widget && widget.simpleValidate) {
|
|
20698
20671
|
widgetScores[id] = widget.simpleValidate(_extends({}, props == null ? void 0 : props.options, {
|
|
20699
20672
|
scoring: true
|
|
20700
|
-
})
|
|
20673
|
+
}));
|
|
20701
20674
|
}
|
|
20702
20675
|
});
|
|
20703
20676
|
return widgetScores;
|
|
@@ -21738,7 +21711,7 @@ const answerTypes = {
|
|
|
21738
21711
|
forms: "pi"
|
|
21739
21712
|
}
|
|
21740
21713
|
};
|
|
21741
|
-
function inputNumberValidator(state, rubric, strings
|
|
21714
|
+
function inputNumberValidator(state, rubric, strings) {
|
|
21742
21715
|
if (rubric.answerType == null) {
|
|
21743
21716
|
rubric.answerType = "number";
|
|
21744
21717
|
}
|
|
@@ -21762,13 +21735,9 @@ function inputNumberValidator(state, rubric, strings, onInputError = () => {}) {
|
|
|
21762
21735
|
// TODO(eater): Seems silly to translate result to this invalid/points
|
|
21763
21736
|
// thing and immediately translate it back in ItemRenderer.scoreInput()
|
|
21764
21737
|
if (result.empty) {
|
|
21765
|
-
// TODO(FEI-3867): remove null-check once we have APIOptionsInternal
|
|
21766
|
-
const apiResult = onInputError == null ? void 0 : onInputError(null,
|
|
21767
|
-
// reserved for some widget identifier
|
|
21768
|
-
state.currentValue, result.message);
|
|
21769
21738
|
return {
|
|
21770
21739
|
type: "invalid",
|
|
21771
|
-
message:
|
|
21740
|
+
message: result.message
|
|
21772
21741
|
};
|
|
21773
21742
|
}
|
|
21774
21743
|
return {
|
|
@@ -21864,13 +21833,6 @@ class InputNumber extends React.Component {
|
|
|
21864
21833
|
currentValue: newValue
|
|
21865
21834
|
}, cb);
|
|
21866
21835
|
};
|
|
21867
|
-
this.getUserInput = () => {
|
|
21868
|
-
return InputNumber.getUserInputFromProps(this.props);
|
|
21869
|
-
};
|
|
21870
|
-
this.simpleValidate = (rubric, onInputError) => {
|
|
21871
|
-
onInputError = onInputError || function () {};
|
|
21872
|
-
return inputNumberValidator(this.getUserInput(), rubric, this.context.strings, onInputError);
|
|
21873
|
-
};
|
|
21874
21836
|
this.examples = () => {
|
|
21875
21837
|
const {
|
|
21876
21838
|
strings
|
|
@@ -21881,8 +21843,8 @@ class InputNumber extends React.Component {
|
|
|
21881
21843
|
return [strings.yourAnswer].concat(examples);
|
|
21882
21844
|
};
|
|
21883
21845
|
}
|
|
21884
|
-
static validate(state, rubric, strings
|
|
21885
|
-
return inputNumberValidator(state, rubric, strings
|
|
21846
|
+
static validate(state, rubric, strings) {
|
|
21847
|
+
return inputNumberValidator(state, rubric, strings);
|
|
21886
21848
|
}
|
|
21887
21849
|
static getUserInputFromProps(props) {
|
|
21888
21850
|
return {
|
|
@@ -21899,6 +21861,12 @@ class InputNumber extends React.Component {
|
|
|
21899
21861
|
}
|
|
21900
21862
|
return answerString;
|
|
21901
21863
|
}
|
|
21864
|
+
getUserInput() {
|
|
21865
|
+
return InputNumber.getUserInputFromProps(this.props);
|
|
21866
|
+
}
|
|
21867
|
+
simpleValidate(rubric) {
|
|
21868
|
+
return inputNumberValidator(this.getUserInput(), rubric, this.context.strings);
|
|
21869
|
+
}
|
|
21902
21870
|
render() {
|
|
21903
21871
|
if (this.props.apiOptions.customKeypad) {
|
|
21904
21872
|
// TODO(charlie): Support "Review Mode".
|
|
@@ -22202,9 +22170,6 @@ class NumericInput extends React.Component {
|
|
|
22202
22170
|
const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
|
|
22203
22171
|
return !noFormsAccepted && !allFormsAccepted;
|
|
22204
22172
|
};
|
|
22205
|
-
this.simpleValidate = rubric => {
|
|
22206
|
-
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22207
|
-
};
|
|
22208
22173
|
this.focus = () => {
|
|
22209
22174
|
var _this$inputRef;
|
|
22210
22175
|
(_this$inputRef = this.inputRef) == null || _this$inputRef.focus();
|
|
@@ -22234,9 +22199,6 @@ class NumericInput extends React.Component {
|
|
|
22234
22199
|
currentValue: newValue
|
|
22235
22200
|
}, cb);
|
|
22236
22201
|
};
|
|
22237
|
-
this.getUserInput = () => {
|
|
22238
|
-
return NumericInput.getUserInputFromProps(this.props);
|
|
22239
|
-
};
|
|
22240
22202
|
this.handleChange = (newValue, cb) => {
|
|
22241
22203
|
this.props.onChange({
|
|
22242
22204
|
currentValue: newValue
|
|
@@ -22286,6 +22248,12 @@ class NumericInput extends React.Component {
|
|
|
22286
22248
|
static validate(userInput, rubric, strings) {
|
|
22287
22249
|
return numericInputValidator(userInput, rubric, strings);
|
|
22288
22250
|
}
|
|
22251
|
+
simpleValidate(rubric) {
|
|
22252
|
+
return numericInputValidator(this.getUserInput(), rubric, this.context.strings);
|
|
22253
|
+
}
|
|
22254
|
+
getUserInput() {
|
|
22255
|
+
return NumericInput.getUserInputFromProps(this.props);
|
|
22256
|
+
}
|
|
22289
22257
|
render() {
|
|
22290
22258
|
let labelText = this.props.labelText;
|
|
22291
22259
|
if (labelText == null || labelText === "") {
|
|
@@ -22442,11 +22410,6 @@ class PassageRef extends React.Component {
|
|
|
22442
22410
|
lineRange: null,
|
|
22443
22411
|
content: null
|
|
22444
22412
|
};
|
|
22445
|
-
// TODO(tamarab): getUserInput needs to be updated to only return
|
|
22446
|
-
// props input by the user. Currently returns all the widget's props.
|
|
22447
|
-
this.getUserInput = () => {
|
|
22448
|
-
return removeDenylistProps(this.props);
|
|
22449
|
-
};
|
|
22450
22413
|
this.change = (...args) => {
|
|
22451
22414
|
return change.apply(this, args);
|
|
22452
22415
|
};
|
|
@@ -22499,6 +22462,11 @@ class PassageRef extends React.Component {
|
|
|
22499
22462
|
window.removeEventListener("resize", this._throttledUpdateRange);
|
|
22500
22463
|
this._isMounted = false;
|
|
22501
22464
|
}
|
|
22465
|
+
|
|
22466
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22467
|
+
getUserInput() {
|
|
22468
|
+
return null;
|
|
22469
|
+
}
|
|
22502
22470
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
22503
22471
|
simpleValidate() {
|
|
22504
22472
|
return noopValidator();
|
|
@@ -23904,6 +23872,56 @@ const styles$t = StyleSheet.create({
|
|
|
23904
23872
|
}
|
|
23905
23873
|
});
|
|
23906
23874
|
|
|
23875
|
+
function radioValidator(userInput, rubric, strings) {
|
|
23876
|
+
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
23877
|
+
return sum + (selected ? 1 : 0);
|
|
23878
|
+
}, 0);
|
|
23879
|
+
if (numSelected === 0) {
|
|
23880
|
+
return {
|
|
23881
|
+
type: "invalid",
|
|
23882
|
+
message: null
|
|
23883
|
+
};
|
|
23884
|
+
}
|
|
23885
|
+
|
|
23886
|
+
// TODO: should numCorrect actually be on the rubric
|
|
23887
|
+
// instead of the userInput?
|
|
23888
|
+
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
23889
|
+
return {
|
|
23890
|
+
type: "invalid",
|
|
23891
|
+
message: strings.chooseCorrectNum
|
|
23892
|
+
};
|
|
23893
|
+
// If NOTA and some other answer are checked, ...
|
|
23894
|
+
}
|
|
23895
|
+
|
|
23896
|
+
// TODO: should noneOfTheAboveSelected be replaced with a
|
|
23897
|
+
// combination of choicesSelected and noneOfTheAboveIndex?
|
|
23898
|
+
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
23899
|
+
return {
|
|
23900
|
+
type: "invalid",
|
|
23901
|
+
message: strings.notNoneOfTheAbove
|
|
23902
|
+
};
|
|
23903
|
+
}
|
|
23904
|
+
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
23905
|
+
let isCorrect;
|
|
23906
|
+
// TODO: should noneOfTheAboveIndex actually be on the rubric
|
|
23907
|
+
// instead of the userInput?
|
|
23908
|
+
if (userInput.noneOfTheAboveIndex === i) {
|
|
23909
|
+
isCorrect = rubric.choices.every((choice, j) => {
|
|
23910
|
+
return i === j || !choice.correct;
|
|
23911
|
+
});
|
|
23912
|
+
} else {
|
|
23913
|
+
isCorrect = !!rubric.choices[i].correct;
|
|
23914
|
+
}
|
|
23915
|
+
return isCorrect === selected;
|
|
23916
|
+
});
|
|
23917
|
+
return {
|
|
23918
|
+
type: "points",
|
|
23919
|
+
earned: correct ? 1 : 0,
|
|
23920
|
+
total: 1,
|
|
23921
|
+
message: null
|
|
23922
|
+
};
|
|
23923
|
+
}
|
|
23924
|
+
|
|
23907
23925
|
// RenderProps is the return type for radio.jsx#transform
|
|
23908
23926
|
|
|
23909
23927
|
class Radio$1 extends React.Component {
|
|
@@ -23998,12 +24016,6 @@ class Radio$1 extends React.Component {
|
|
|
23998
24016
|
});
|
|
23999
24017
|
this.props.trackInteraction();
|
|
24000
24018
|
};
|
|
24001
|
-
this.getUserInput = () => {
|
|
24002
|
-
return Radio$1.getUserInputFromProps(this.props);
|
|
24003
|
-
};
|
|
24004
|
-
this.simpleValidate = rubric => {
|
|
24005
|
-
return Radio$1.validate(this.getUserInput(), rubric, this.context.strings);
|
|
24006
|
-
};
|
|
24007
24019
|
/**
|
|
24008
24020
|
* Turn on rationale display for the currently selected choices. Note that
|
|
24009
24021
|
* this leaves rationales on for choices that are already showing
|
|
@@ -24064,46 +24076,7 @@ class Radio$1 extends React.Component {
|
|
|
24064
24076
|
};
|
|
24065
24077
|
}
|
|
24066
24078
|
static validate(userInput, rubric, strings) {
|
|
24067
|
-
|
|
24068
|
-
return sum + (selected ? 1 : 0);
|
|
24069
|
-
}, 0);
|
|
24070
|
-
if (numSelected === 0) {
|
|
24071
|
-
return {
|
|
24072
|
-
type: "invalid",
|
|
24073
|
-
message: null
|
|
24074
|
-
};
|
|
24075
|
-
}
|
|
24076
|
-
if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
|
|
24077
|
-
return {
|
|
24078
|
-
type: "invalid",
|
|
24079
|
-
message: strings.chooseCorrectNum
|
|
24080
|
-
};
|
|
24081
|
-
// If NOTA and some other answer are checked, ...
|
|
24082
|
-
}
|
|
24083
|
-
|
|
24084
|
-
if (userInput.noneOfTheAboveSelected && numSelected > 1) {
|
|
24085
|
-
return {
|
|
24086
|
-
type: "invalid",
|
|
24087
|
-
message: strings.notNoneOfTheAbove
|
|
24088
|
-
};
|
|
24089
|
-
}
|
|
24090
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
24091
|
-
let isCorrect;
|
|
24092
|
-
if (userInput.noneOfTheAboveIndex === i) {
|
|
24093
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
24094
|
-
return i === j || !choice.correct;
|
|
24095
|
-
});
|
|
24096
|
-
} else {
|
|
24097
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
24098
|
-
}
|
|
24099
|
-
return isCorrect === selected;
|
|
24100
|
-
});
|
|
24101
|
-
return {
|
|
24102
|
-
type: "points",
|
|
24103
|
-
earned: correct ? 1 : 0,
|
|
24104
|
-
total: 1,
|
|
24105
|
-
message: null
|
|
24106
|
-
};
|
|
24079
|
+
return radioValidator(userInput, rubric, strings);
|
|
24107
24080
|
}
|
|
24108
24081
|
static getUserInputFromProps(props) {
|
|
24109
24082
|
// Return checked inputs in the form {choicesSelected: [bool]}. (Dear
|
|
@@ -24192,6 +24165,12 @@ class Radio$1 extends React.Component {
|
|
|
24192
24165
|
registerFocusFunction(fun) {
|
|
24193
24166
|
this.focusFunction = fun;
|
|
24194
24167
|
}
|
|
24168
|
+
getUserInput() {
|
|
24169
|
+
return Radio$1.getUserInputFromProps(this.props);
|
|
24170
|
+
}
|
|
24171
|
+
simpleValidate(rubric) {
|
|
24172
|
+
return radioValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24173
|
+
}
|
|
24195
24174
|
render() {
|
|
24196
24175
|
const {
|
|
24197
24176
|
choices
|
|
@@ -24468,12 +24447,6 @@ class Categorizer extends React.Component {
|
|
|
24468
24447
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
24469
24448
|
return change.apply(this, args);
|
|
24470
24449
|
};
|
|
24471
|
-
this.getUserInput = () => {
|
|
24472
|
-
return Categorizer.getUserInputFromProps(this.props);
|
|
24473
|
-
};
|
|
24474
|
-
this.simpleValidate = rubric => {
|
|
24475
|
-
return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24476
|
-
};
|
|
24477
24450
|
}
|
|
24478
24451
|
static validate(userInput, rubric, strings) {
|
|
24479
24452
|
return categorizerValidator(userInput, rubric, strings);
|
|
@@ -24483,6 +24456,9 @@ class Categorizer extends React.Component {
|
|
|
24483
24456
|
values: props.values
|
|
24484
24457
|
};
|
|
24485
24458
|
}
|
|
24459
|
+
getUserInput() {
|
|
24460
|
+
return Categorizer.getUserInputFromProps(this.props);
|
|
24461
|
+
}
|
|
24486
24462
|
onChange(itemNum, catNum) {
|
|
24487
24463
|
const values = [...this.props.values];
|
|
24488
24464
|
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'never'.
|
|
@@ -24490,6 +24466,9 @@ class Categorizer extends React.Component {
|
|
|
24490
24466
|
this.change("values", values);
|
|
24491
24467
|
this.props.trackInteraction();
|
|
24492
24468
|
}
|
|
24469
|
+
simpleValidate(rubric) {
|
|
24470
|
+
return categorizerValidator(this.getUserInput(), rubric, this.context.strings);
|
|
24471
|
+
}
|
|
24493
24472
|
render() {
|
|
24494
24473
|
const self = this;
|
|
24495
24474
|
|
|
@@ -24685,6 +24664,31 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
24685
24664
|
*/
|
|
24686
24665
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
24687
24666
|
|
|
24667
|
+
function csProgramValidator(state) {
|
|
24668
|
+
// The iframe can tell us whether it's correct or incorrect,
|
|
24669
|
+
// and pass an optional message
|
|
24670
|
+
if (state.status === "correct") {
|
|
24671
|
+
return {
|
|
24672
|
+
type: "points",
|
|
24673
|
+
earned: 1,
|
|
24674
|
+
total: 1,
|
|
24675
|
+
message: state.message || null
|
|
24676
|
+
};
|
|
24677
|
+
}
|
|
24678
|
+
if (state.status === "incorrect") {
|
|
24679
|
+
return {
|
|
24680
|
+
type: "points",
|
|
24681
|
+
earned: 0,
|
|
24682
|
+
total: 1,
|
|
24683
|
+
message: state.message || null
|
|
24684
|
+
};
|
|
24685
|
+
}
|
|
24686
|
+
return {
|
|
24687
|
+
type: "invalid",
|
|
24688
|
+
message: "Keep going, you're not there yet!"
|
|
24689
|
+
};
|
|
24690
|
+
}
|
|
24691
|
+
|
|
24688
24692
|
/**
|
|
24689
24693
|
* This widget is for embedding Khan Academy CS programs.
|
|
24690
24694
|
*/
|
|
@@ -24735,29 +24739,8 @@ class CSProgram extends React.Component {
|
|
|
24735
24739
|
};
|
|
24736
24740
|
}
|
|
24737
24741
|
// The widget's grading function
|
|
24738
|
-
static validate(state
|
|
24739
|
-
|
|
24740
|
-
// and pass an optional message
|
|
24741
|
-
if (state.status === "correct") {
|
|
24742
|
-
return {
|
|
24743
|
-
type: "points",
|
|
24744
|
-
earned: 1,
|
|
24745
|
-
total: 1,
|
|
24746
|
-
message: state.message || null
|
|
24747
|
-
};
|
|
24748
|
-
}
|
|
24749
|
-
if (state.status === "incorrect") {
|
|
24750
|
-
return {
|
|
24751
|
-
type: "points",
|
|
24752
|
-
earned: 0,
|
|
24753
|
-
total: 1,
|
|
24754
|
-
message: state.message || null
|
|
24755
|
-
};
|
|
24756
|
-
}
|
|
24757
|
-
return {
|
|
24758
|
-
type: "invalid",
|
|
24759
|
-
message: "Keep going, you're not there yet!"
|
|
24760
|
-
};
|
|
24742
|
+
static validate(state) {
|
|
24743
|
+
return csProgramValidator(state);
|
|
24761
24744
|
}
|
|
24762
24745
|
componentDidMount() {
|
|
24763
24746
|
$(window).on("message", this.handleMessageEvent);
|
|
@@ -24765,11 +24748,11 @@ class CSProgram extends React.Component {
|
|
|
24765
24748
|
componentWillUnmount() {
|
|
24766
24749
|
$(window).off("message", this.handleMessageEvent);
|
|
24767
24750
|
}
|
|
24768
|
-
simpleValidate(
|
|
24769
|
-
return
|
|
24751
|
+
simpleValidate() {
|
|
24752
|
+
return csProgramValidator({
|
|
24770
24753
|
status: this.props.status,
|
|
24771
24754
|
message: this.props.message
|
|
24772
|
-
}
|
|
24755
|
+
});
|
|
24773
24756
|
}
|
|
24774
24757
|
render() {
|
|
24775
24758
|
if (!this.props.programID) {
|
|
@@ -24856,16 +24839,14 @@ var CSProgram$1 = {
|
|
|
24856
24839
|
};
|
|
24857
24840
|
|
|
24858
24841
|
class Definition extends React.Component {
|
|
24859
|
-
constructor(...args) {
|
|
24860
|
-
super(...args);
|
|
24861
|
-
this.getUserInput = () => {
|
|
24862
|
-
return {};
|
|
24863
|
-
};
|
|
24864
|
-
}
|
|
24865
24842
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
24866
24843
|
static validate() {
|
|
24867
24844
|
return noopValidator();
|
|
24868
24845
|
}
|
|
24846
|
+
getUserInput() {
|
|
24847
|
+
return {};
|
|
24848
|
+
}
|
|
24849
|
+
|
|
24869
24850
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
24870
24851
|
simpleValidate() {
|
|
24871
24852
|
return noopValidator();
|
|
@@ -25002,18 +24983,18 @@ class Dropdown extends React.Component {
|
|
|
25002
24983
|
selected: selected
|
|
25003
24984
|
});
|
|
25004
24985
|
};
|
|
25005
|
-
this.getUserInput = () => {
|
|
25006
|
-
return {
|
|
25007
|
-
value: this.props.selected
|
|
25008
|
-
};
|
|
25009
|
-
};
|
|
25010
|
-
this.simpleValidate = rubric => {
|
|
25011
|
-
return dropdownValidator(this.getUserInput(), rubric);
|
|
25012
|
-
};
|
|
25013
24986
|
}
|
|
25014
24987
|
static validate(userInput, rubric) {
|
|
25015
24988
|
return dropdownValidator(userInput, rubric);
|
|
25016
24989
|
}
|
|
24990
|
+
getUserInput() {
|
|
24991
|
+
return {
|
|
24992
|
+
value: this.props.selected
|
|
24993
|
+
};
|
|
24994
|
+
}
|
|
24995
|
+
simpleValidate(rubric) {
|
|
24996
|
+
return dropdownValidator(this.getUserInput(), rubric);
|
|
24997
|
+
}
|
|
25017
24998
|
render() {
|
|
25018
24999
|
const children = [/*#__PURE__*/React.createElement(OptionItem, {
|
|
25019
25000
|
key: "placeholder",
|
|
@@ -25087,9 +25068,6 @@ class Explanation extends React.Component {
|
|
|
25087
25068
|
});
|
|
25088
25069
|
this.props.trackInteraction();
|
|
25089
25070
|
};
|
|
25090
|
-
this.getUserInput = () => {
|
|
25091
|
-
return {};
|
|
25092
|
-
};
|
|
25093
25071
|
}
|
|
25094
25072
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25095
25073
|
static validate() {
|
|
@@ -25101,6 +25079,10 @@ class Explanation extends React.Component {
|
|
|
25101
25079
|
componentWillUnmount() {
|
|
25102
25080
|
this._mounted = false;
|
|
25103
25081
|
}
|
|
25082
|
+
getUserInput() {
|
|
25083
|
+
return {};
|
|
25084
|
+
}
|
|
25085
|
+
|
|
25104
25086
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
25105
25087
|
simpleValidate() {
|
|
25106
25088
|
return noopValidator();
|
|
@@ -26361,49 +26343,6 @@ function functionForType(
|
|
|
26361
26343
|
type) {
|
|
26362
26344
|
return functionTypeMapping[type];
|
|
26363
26345
|
}
|
|
26364
|
-
const validate$1 = (state, rubric) => {
|
|
26365
|
-
if (state.type !== rubric.correct.type) {
|
|
26366
|
-
return {
|
|
26367
|
-
type: "points",
|
|
26368
|
-
earned: 0,
|
|
26369
|
-
total: 1,
|
|
26370
|
-
message: null
|
|
26371
|
-
};
|
|
26372
|
-
}
|
|
26373
|
-
|
|
26374
|
-
// We haven't moved the coords
|
|
26375
|
-
if (state.coords == null) {
|
|
26376
|
-
return {
|
|
26377
|
-
type: "invalid",
|
|
26378
|
-
message: null
|
|
26379
|
-
};
|
|
26380
|
-
}
|
|
26381
|
-
|
|
26382
|
-
// Get new function handler for grading
|
|
26383
|
-
const grader = functionForType(state.type);
|
|
26384
|
-
const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
|
|
26385
|
-
const correctCoeffs = grader.getCoefficients(rubric.correct.coords, rubric.correct.asymptote);
|
|
26386
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
26387
|
-
return {
|
|
26388
|
-
type: "invalid",
|
|
26389
|
-
message: null
|
|
26390
|
-
};
|
|
26391
|
-
}
|
|
26392
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
26393
|
-
return {
|
|
26394
|
-
type: "points",
|
|
26395
|
-
earned: 1,
|
|
26396
|
-
total: 1,
|
|
26397
|
-
message: null
|
|
26398
|
-
};
|
|
26399
|
-
}
|
|
26400
|
-
return {
|
|
26401
|
-
type: "points",
|
|
26402
|
-
earned: 0,
|
|
26403
|
-
total: 1,
|
|
26404
|
-
message: null
|
|
26405
|
-
};
|
|
26406
|
-
};
|
|
26407
26346
|
const getEquationString = props => {
|
|
26408
26347
|
const plot = props.plot;
|
|
26409
26348
|
if (plot.type && plot.coords) {
|
|
@@ -26521,7 +26460,6 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26521
26460
|
DEFAULT_BACKGROUND_IMAGE: DEFAULT_BACKGROUND_IMAGE,
|
|
26522
26461
|
allTypes: allTypes,
|
|
26523
26462
|
functionForType: functionForType,
|
|
26524
|
-
validate: validate$1,
|
|
26525
26463
|
getEquationString: getEquationString,
|
|
26526
26464
|
pointsFromNormalized: pointsFromNormalized,
|
|
26527
26465
|
maybePointsFromNormalized: maybePointsFromNormalized,
|
|
@@ -26532,6 +26470,52 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26532
26470
|
typeToButton: typeToButton
|
|
26533
26471
|
});
|
|
26534
26472
|
|
|
26473
|
+
function grapherValidator(state, rubric) {
|
|
26474
|
+
if (state.type !== rubric.correct.type) {
|
|
26475
|
+
return {
|
|
26476
|
+
type: "points",
|
|
26477
|
+
earned: 0,
|
|
26478
|
+
total: 1,
|
|
26479
|
+
message: null
|
|
26480
|
+
};
|
|
26481
|
+
}
|
|
26482
|
+
|
|
26483
|
+
// We haven't moved the coords
|
|
26484
|
+
if (state.coords == null) {
|
|
26485
|
+
return {
|
|
26486
|
+
type: "invalid",
|
|
26487
|
+
message: null
|
|
26488
|
+
};
|
|
26489
|
+
}
|
|
26490
|
+
|
|
26491
|
+
// Get new function handler for grading
|
|
26492
|
+
const grader = functionForType(state.type);
|
|
26493
|
+
const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
|
|
26494
|
+
const correctCoeffs = grader.getCoefficients(rubric.correct.coords,
|
|
26495
|
+
// @ts-expect-error - TS(2339) - Property 'asymptote' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; }'
|
|
26496
|
+
rubric.correct.asymptote);
|
|
26497
|
+
if (guessCoeffs == null || correctCoeffs == null) {
|
|
26498
|
+
return {
|
|
26499
|
+
type: "invalid",
|
|
26500
|
+
message: null
|
|
26501
|
+
};
|
|
26502
|
+
}
|
|
26503
|
+
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
26504
|
+
return {
|
|
26505
|
+
type: "points",
|
|
26506
|
+
earned: 1,
|
|
26507
|
+
total: 1,
|
|
26508
|
+
message: null
|
|
26509
|
+
};
|
|
26510
|
+
}
|
|
26511
|
+
return {
|
|
26512
|
+
type: "points",
|
|
26513
|
+
earned: 0,
|
|
26514
|
+
total: 1,
|
|
26515
|
+
message: null
|
|
26516
|
+
};
|
|
26517
|
+
}
|
|
26518
|
+
|
|
26535
26519
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
26536
26520
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
26537
26521
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -26724,7 +26708,7 @@ FunctionGrapher.defaultProps = {
|
|
|
26724
26708
|
coords: null,
|
|
26725
26709
|
asymptote: null,
|
|
26726
26710
|
isMobile: false
|
|
26727
|
-
};
|
|
26711
|
+
};
|
|
26728
26712
|
/* Widget and editor. */
|
|
26729
26713
|
class Grapher extends React.Component {
|
|
26730
26714
|
constructor(...args) {
|
|
@@ -26817,7 +26801,7 @@ class Grapher extends React.Component {
|
|
|
26817
26801
|
};
|
|
26818
26802
|
}
|
|
26819
26803
|
static validate(state, rubric) {
|
|
26820
|
-
return
|
|
26804
|
+
return grapherValidator(state, rubric);
|
|
26821
26805
|
}
|
|
26822
26806
|
static getUserInputFromProps(props) {
|
|
26823
26807
|
return props.plot;
|
|
@@ -26838,7 +26822,7 @@ class Grapher extends React.Component {
|
|
|
26838
26822
|
return tickStep;
|
|
26839
26823
|
}
|
|
26840
26824
|
simpleValidate(rubric) {
|
|
26841
|
-
return
|
|
26825
|
+
return grapherValidator(this.getUserInput(), rubric);
|
|
26842
26826
|
}
|
|
26843
26827
|
getUserInput() {
|
|
26844
26828
|
return Grapher.getUserInputFromProps(this.props);
|
|
@@ -26940,49 +26924,41 @@ class Group extends React.Component {
|
|
|
26940
26924
|
this.change = (...args) => {
|
|
26941
26925
|
return change.apply(this, args);
|
|
26942
26926
|
};
|
|
26943
|
-
this.getUserInput = () => {
|
|
26944
|
-
var _this$rendererRef;
|
|
26945
|
-
return (_this$rendererRef = this.rendererRef) == null ? void 0 : _this$rendererRef.getUserInput();
|
|
26946
|
-
};
|
|
26947
26927
|
this.getSerializedState = () => {
|
|
26948
|
-
var _this$
|
|
26949
|
-
return (_this$
|
|
26928
|
+
var _this$rendererRef;
|
|
26929
|
+
return (_this$rendererRef = this.rendererRef) == null ? void 0 : _this$rendererRef.getSerializedState();
|
|
26950
26930
|
};
|
|
26951
26931
|
this.restoreSerializedState = (state, callback) => {
|
|
26952
|
-
var _this$
|
|
26953
|
-
(_this$
|
|
26932
|
+
var _this$rendererRef2;
|
|
26933
|
+
(_this$rendererRef2 = this.rendererRef) == null || _this$rendererRef2.restoreSerializedState(state, callback);
|
|
26954
26934
|
// Tell our renderer that we have no props to change
|
|
26955
26935
|
// (all our changes were in state):
|
|
26956
26936
|
return null;
|
|
26957
26937
|
};
|
|
26958
|
-
this.simpleValidate = rubric => {
|
|
26959
|
-
var _this$rendererRef4;
|
|
26960
|
-
return (_this$rendererRef4 = this.rendererRef) == null ? void 0 : _this$rendererRef4.score();
|
|
26961
|
-
};
|
|
26962
26938
|
// Mobile API:
|
|
26963
26939
|
this.getInputPaths = () => {
|
|
26964
|
-
var _this$
|
|
26965
|
-
return (_this$
|
|
26940
|
+
var _this$rendererRef3;
|
|
26941
|
+
return (_this$rendererRef3 = this.rendererRef) == null ? void 0 : _this$rendererRef3.getInputPaths();
|
|
26966
26942
|
};
|
|
26967
26943
|
this.setInputValue = (path, newValue, callback) => {
|
|
26968
|
-
var _this$
|
|
26969
|
-
return (_this$
|
|
26944
|
+
var _this$rendererRef4;
|
|
26945
|
+
return (_this$rendererRef4 = this.rendererRef) == null ? void 0 : _this$rendererRef4.setInputValue(path, newValue, callback);
|
|
26970
26946
|
};
|
|
26971
26947
|
this.focus = () => {
|
|
26972
|
-
var _this$
|
|
26973
|
-
return (_this$
|
|
26948
|
+
var _this$rendererRef5;
|
|
26949
|
+
return (_this$rendererRef5 = this.rendererRef) == null ? void 0 : _this$rendererRef5.focus();
|
|
26974
26950
|
};
|
|
26975
26951
|
this.focusInputPath = path => {
|
|
26976
|
-
var _this$
|
|
26977
|
-
(_this$
|
|
26952
|
+
var _this$rendererRef6;
|
|
26953
|
+
(_this$rendererRef6 = this.rendererRef) == null || _this$rendererRef6.focusPath(path);
|
|
26978
26954
|
};
|
|
26979
26955
|
this.blurInputPath = path => {
|
|
26980
|
-
var _this$
|
|
26981
|
-
(_this$
|
|
26956
|
+
var _this$rendererRef7;
|
|
26957
|
+
(_this$rendererRef7 = this.rendererRef) == null || _this$rendererRef7.blurPath(path);
|
|
26982
26958
|
};
|
|
26983
26959
|
this.showRationalesForCurrentlySelectedChoices = () => {
|
|
26984
|
-
var _this$
|
|
26985
|
-
(_this$
|
|
26960
|
+
var _this$rendererRef8;
|
|
26961
|
+
(_this$rendererRef8 = this.rendererRef) == null || _this$rendererRef8.showRationalesForCurrentlySelectedChoices();
|
|
26986
26962
|
};
|
|
26987
26963
|
}
|
|
26988
26964
|
componentDidMount() {
|
|
@@ -26991,6 +26967,14 @@ class Group extends React.Component {
|
|
|
26991
26967
|
// the group with the correct number.
|
|
26992
26968
|
this.forceUpdate();
|
|
26993
26969
|
}
|
|
26970
|
+
getUserInput() {
|
|
26971
|
+
var _this$rendererRef9;
|
|
26972
|
+
return (_this$rendererRef9 = this.rendererRef) == null ? void 0 : _this$rendererRef9.getUserInput();
|
|
26973
|
+
}
|
|
26974
|
+
simpleValidate() {
|
|
26975
|
+
var _this$rendererRef10;
|
|
26976
|
+
return (_this$rendererRef10 = this.rendererRef) == null ? void 0 : _this$rendererRef10.score();
|
|
26977
|
+
}
|
|
26994
26978
|
render() {
|
|
26995
26979
|
var _this$rendererRef11;
|
|
26996
26980
|
const apiOptions = _extends({}, ApiOptions.defaults, this.props.apiOptions, {
|
|
@@ -27079,6 +27063,31 @@ var Group$1 = {
|
|
|
27079
27063
|
isLintable: true
|
|
27080
27064
|
};
|
|
27081
27065
|
|
|
27066
|
+
function iframeValidator(state) {
|
|
27067
|
+
// The iframe can tell us whether it's correct or incorrect,
|
|
27068
|
+
// and pass an optional message
|
|
27069
|
+
if (state.status === "correct") {
|
|
27070
|
+
return {
|
|
27071
|
+
type: "points",
|
|
27072
|
+
earned: 1,
|
|
27073
|
+
total: 1,
|
|
27074
|
+
message: state.message || null
|
|
27075
|
+
};
|
|
27076
|
+
}
|
|
27077
|
+
if (state.status === "incorrect") {
|
|
27078
|
+
return {
|
|
27079
|
+
type: "points",
|
|
27080
|
+
earned: 0,
|
|
27081
|
+
total: 1,
|
|
27082
|
+
message: state.message || null
|
|
27083
|
+
};
|
|
27084
|
+
}
|
|
27085
|
+
return {
|
|
27086
|
+
type: "invalid",
|
|
27087
|
+
message: "Keep going, you're not there yet!"
|
|
27088
|
+
};
|
|
27089
|
+
}
|
|
27090
|
+
|
|
27082
27091
|
/**
|
|
27083
27092
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
27084
27093
|
* then communicates its state via window.postMessage
|
|
@@ -27117,10 +27126,9 @@ class Iframe extends React.Component {
|
|
|
27117
27126
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
27118
27127
|
return change.apply(this, args);
|
|
27119
27128
|
};
|
|
27120
|
-
|
|
27121
|
-
|
|
27122
|
-
|
|
27123
|
-
};
|
|
27129
|
+
}
|
|
27130
|
+
static validate(state) {
|
|
27131
|
+
return iframeValidator(state);
|
|
27124
27132
|
}
|
|
27125
27133
|
componentDidMount() {
|
|
27126
27134
|
$(window).on("message", this.handleMessageEvent);
|
|
@@ -27134,6 +27142,9 @@ class Iframe extends React.Component {
|
|
|
27134
27142
|
message: this.props.message
|
|
27135
27143
|
};
|
|
27136
27144
|
}
|
|
27145
|
+
simpleValidate() {
|
|
27146
|
+
return iframeValidator(this.getUserInput());
|
|
27147
|
+
}
|
|
27137
27148
|
render() {
|
|
27138
27149
|
const style = {
|
|
27139
27150
|
width: String(this.props.width),
|
|
@@ -27195,10 +27206,6 @@ class Iframe extends React.Component {
|
|
|
27195
27206
|
});
|
|
27196
27207
|
}
|
|
27197
27208
|
}
|
|
27198
|
-
|
|
27199
|
-
/**
|
|
27200
|
-
* This is the widget's grading function
|
|
27201
|
-
*/
|
|
27202
27209
|
Iframe.defaultProps = {
|
|
27203
27210
|
status: "incomplete",
|
|
27204
27211
|
// optional message
|
|
@@ -27206,32 +27213,6 @@ Iframe.defaultProps = {
|
|
|
27206
27213
|
allowFullScreen: false,
|
|
27207
27214
|
allowTopNavigation: false
|
|
27208
27215
|
};
|
|
27209
|
-
_.extend(Iframe, {
|
|
27210
|
-
validate: function (state, rubric) {
|
|
27211
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
27212
|
-
// and pass an optional message
|
|
27213
|
-
if (state.status === "correct") {
|
|
27214
|
-
return {
|
|
27215
|
-
type: "points",
|
|
27216
|
-
earned: 1,
|
|
27217
|
-
total: 1,
|
|
27218
|
-
message: state.message || null
|
|
27219
|
-
};
|
|
27220
|
-
}
|
|
27221
|
-
if (state.status === "incorrect") {
|
|
27222
|
-
return {
|
|
27223
|
-
type: "points",
|
|
27224
|
-
earned: 0,
|
|
27225
|
-
total: 1,
|
|
27226
|
-
message: state.message || null
|
|
27227
|
-
};
|
|
27228
|
-
}
|
|
27229
|
-
return {
|
|
27230
|
-
type: "invalid",
|
|
27231
|
-
message: "Keep going, you're not there yet!"
|
|
27232
|
-
};
|
|
27233
|
-
}
|
|
27234
|
-
});
|
|
27235
27216
|
var Iframe$1 = {
|
|
27236
27217
|
name: "iframe",
|
|
27237
27218
|
displayName: "Iframe (deprecated)",
|
|
@@ -27258,15 +27239,16 @@ class ImageWidget extends React.Component {
|
|
|
27258
27239
|
this.change = (...args) => {
|
|
27259
27240
|
return change.apply(this, args);
|
|
27260
27241
|
};
|
|
27261
|
-
this.getUserInput = () => {
|
|
27262
|
-
return null;
|
|
27263
|
-
};
|
|
27264
27242
|
this.focus = () => {};
|
|
27265
27243
|
}
|
|
27266
27244
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27267
27245
|
static validate() {
|
|
27268
27246
|
return noopValidator();
|
|
27269
27247
|
}
|
|
27248
|
+
getUserInput() {
|
|
27249
|
+
return null;
|
|
27250
|
+
}
|
|
27251
|
+
|
|
27270
27252
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27271
27253
|
simpleValidate() {
|
|
27272
27254
|
return noopValidator();
|
|
@@ -27578,13 +27560,6 @@ class Interaction extends React.Component {
|
|
|
27578
27560
|
this.change = (...args) => {
|
|
27579
27561
|
return change.apply(this, args);
|
|
27580
27562
|
};
|
|
27581
|
-
this.getUserInput = () => {
|
|
27582
|
-
// TODO(eater): Perhaps we want to be able to record the state of the
|
|
27583
|
-
// user's interaction. Unfortunately sending all the props will
|
|
27584
|
-
// probably make the attempt payload too large. So for now, don't send
|
|
27585
|
-
// anything.
|
|
27586
|
-
return {};
|
|
27587
|
-
};
|
|
27588
27563
|
}
|
|
27589
27564
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27590
27565
|
static validate() {
|
|
@@ -27608,6 +27583,14 @@ class Interaction extends React.Component {
|
|
|
27608
27583
|
});
|
|
27609
27584
|
}
|
|
27610
27585
|
}
|
|
27586
|
+
getUserInput() {
|
|
27587
|
+
// TODO(eater): Perhaps we want to be able to record the state of the
|
|
27588
|
+
// user's interaction. Unfortunately sending all the props will
|
|
27589
|
+
// probably make the attempt payload too large. So for now, don't send
|
|
27590
|
+
// anything.
|
|
27591
|
+
return {};
|
|
27592
|
+
}
|
|
27593
|
+
|
|
27611
27594
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
27612
27595
|
simpleValidate() {
|
|
27613
27596
|
return noopValidator();
|
|
@@ -28736,7 +28719,8 @@ const tickSize = 10;
|
|
|
28736
28719
|
const tickLabelSize = 14;
|
|
28737
28720
|
const YGridTick = ({
|
|
28738
28721
|
y,
|
|
28739
|
-
range
|
|
28722
|
+
range,
|
|
28723
|
+
tickStep
|
|
28740
28724
|
}) => {
|
|
28741
28725
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
28742
28726
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -28771,9 +28755,10 @@ const YGridTick = ({
|
|
|
28771
28755
|
|
|
28772
28756
|
// If the graph displays both the y and x axis lines within the graph, we want
|
|
28773
28757
|
// to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
|
|
28774
|
-
const showLabel = shouldShowLabel(y, range);
|
|
28758
|
+
const showLabel = shouldShowLabel(y, range, tickStep);
|
|
28775
28759
|
return /*#__PURE__*/React.createElement("g", {
|
|
28776
|
-
className: "tick"
|
|
28760
|
+
className: "tick",
|
|
28761
|
+
"aria-hidden": true
|
|
28777
28762
|
}, /*#__PURE__*/React.createElement("line", {
|
|
28778
28763
|
x1: x1,
|
|
28779
28764
|
y1: y1,
|
|
@@ -28832,7 +28817,8 @@ const XGridTick = ({
|
|
|
28832
28817
|
const xPositionText = xPosition + xAdjustment;
|
|
28833
28818
|
const yPositionText = yPosition + yAdjustment;
|
|
28834
28819
|
return /*#__PURE__*/React.createElement("g", {
|
|
28835
|
-
className: "tick"
|
|
28820
|
+
className: "tick",
|
|
28821
|
+
"aria-hidden": true
|
|
28836
28822
|
}, /*#__PURE__*/React.createElement("line", {
|
|
28837
28823
|
x1: x1,
|
|
28838
28824
|
y1: y1,
|
|
@@ -28851,13 +28837,13 @@ const XGridTick = ({
|
|
|
28851
28837
|
};
|
|
28852
28838
|
|
|
28853
28839
|
// Determines whether to show the label for the given tick
|
|
28854
|
-
// Currently, the only condition is to hide the label at -
|
|
28855
|
-
// on the y-axis when the
|
|
28856
|
-
const shouldShowLabel = (
|
|
28840
|
+
// Currently, the only condition is to hide the label at -tickStep
|
|
28841
|
+
// on the y-axis when the y-axis is within the graph bounds
|
|
28842
|
+
const shouldShowLabel = (currentTick, range, tickStep) => {
|
|
28857
28843
|
let showLabel = true;
|
|
28858
28844
|
|
|
28859
|
-
// If the
|
|
28860
|
-
if (range[X][MIN] < -
|
|
28845
|
+
// If the y-axis is within the graph and currentTick equals -tickStep, hide the label
|
|
28846
|
+
if (range[X][MIN] < -tickStep && range[X][MAX] > 0 && currentTick === -tickStep) {
|
|
28861
28847
|
showLabel = false;
|
|
28862
28848
|
}
|
|
28863
28849
|
return showLabel;
|
|
@@ -28899,7 +28885,8 @@ const AxisTicks = () => {
|
|
|
28899
28885
|
return /*#__PURE__*/React.createElement(YGridTick, {
|
|
28900
28886
|
y: y,
|
|
28901
28887
|
key: `y-grid-tick-${y}`,
|
|
28902
|
-
range: range
|
|
28888
|
+
range: range,
|
|
28889
|
+
tickStep: tickStep[Y]
|
|
28903
28890
|
});
|
|
28904
28891
|
})), /*#__PURE__*/React.createElement("g", {
|
|
28905
28892
|
className: "x-axis-ticks"
|
|
@@ -29652,7 +29639,8 @@ function LockedLabel(props) {
|
|
|
29652
29639
|
color: lockedFigureColors[color],
|
|
29653
29640
|
fontSize: font.size[size],
|
|
29654
29641
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
29655
|
-
}
|
|
29642
|
+
},
|
|
29643
|
+
"aria-hidden": true
|
|
29656
29644
|
}, /*#__PURE__*/React.createElement(TeX, null, text));
|
|
29657
29645
|
}
|
|
29658
29646
|
|
|
@@ -29662,7 +29650,7 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29662
29650
|
lockedFigures
|
|
29663
29651
|
} = props;
|
|
29664
29652
|
return lockedFigures.map((figure, i) => {
|
|
29665
|
-
var _flags$mafs, _flags$mafs2;
|
|
29653
|
+
var _flags$mafs, _flags$mafs2, _flags$mafs3, _flags$mafs4, _flags$mafs5, _flags$mafs6;
|
|
29666
29654
|
if (figure.type === "label") {
|
|
29667
29655
|
return /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29668
29656
|
key: `label-${i}`
|
|
@@ -29672,14 +29660,23 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29672
29660
|
// Point flag + point type
|
|
29673
29661
|
flags != null && (_flags$mafs = flags["mafs"]) != null && _flags$mafs["locked-point-labels"] && figure.type === "point" ||
|
|
29674
29662
|
// Line flag + line type
|
|
29675
|
-
flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line"
|
|
29663
|
+
flags != null && (_flags$mafs2 = flags["mafs"]) != null && _flags$mafs2["locked-line-labels"] && figure.type === "line" ||
|
|
29664
|
+
// Vector flag + vector type
|
|
29665
|
+
flags != null && (_flags$mafs3 = flags["mafs"]) != null && _flags$mafs3["locked-vector-labels"] && figure.type === "vector" ||
|
|
29666
|
+
// Ellipse flag + ellipse type
|
|
29667
|
+
flags != null && (_flags$mafs4 = flags["mafs"]) != null && _flags$mafs4["locked-ellipse-labels"] && figure.type === "ellipse" ||
|
|
29668
|
+
// Polygon flag + polygon type
|
|
29669
|
+
flags != null && (_flags$mafs5 = flags["mafs"]) != null && _flags$mafs5["locked-polygon-labels"] && figure.type === "polygon" ||
|
|
29670
|
+
// Function flag + function type
|
|
29671
|
+
flags != null && (_flags$mafs6 = flags["mafs"]) != null && _flags$mafs6["locked-function-labels"] && figure.type === "function") {
|
|
29672
|
+
var _figure$labels, _figure$points$0$labe, _figure$points$1$labe;
|
|
29676
29673
|
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
29677
29674
|
key: i
|
|
29678
|
-
}, figure.labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29675
|
+
}, (_figure$labels = figure.labels) == null ? void 0 : _figure$labels.map((label, j) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29679
29676
|
key: `${i}-label-${j}`
|
|
29680
|
-
}, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, figure.points[0].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29677
|
+
}, label))), figure.type === "line" && /*#__PURE__*/React.createElement(React.Fragment, null, (_figure$points$0$labe = figure.points[0].labels) == null ? void 0 : _figure$points$0$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29681
29678
|
key: `locked-figure-${i}-point-1-label-${k}`
|
|
29682
|
-
}, label))), figure.points[1].labels.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29679
|
+
}, label))), (_figure$points$1$labe = figure.points[1].labels) == null ? void 0 : _figure$points$1$labe.map((label, k) => /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29683
29680
|
key: `locked-figure-${i}-point-2-label-${k}`
|
|
29684
29681
|
}, label)))));
|
|
29685
29682
|
}
|
|
@@ -29688,15 +29685,23 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29688
29685
|
}
|
|
29689
29686
|
|
|
29690
29687
|
const LockedEllipse = props => {
|
|
29688
|
+
var _flags$mafs;
|
|
29691
29689
|
const {
|
|
29692
29690
|
center,
|
|
29693
29691
|
radius,
|
|
29694
29692
|
angle,
|
|
29695
29693
|
color: color$1,
|
|
29696
29694
|
fillStyle,
|
|
29697
|
-
strokeStyle
|
|
29695
|
+
strokeStyle,
|
|
29696
|
+
ariaLabel,
|
|
29697
|
+
flags
|
|
29698
29698
|
} = props;
|
|
29699
|
-
|
|
29699
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29700
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
29701
|
+
className: "locked-ellipse",
|
|
29702
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
29703
|
+
"aria-hidden": !hasAria
|
|
29704
|
+
}, /*#__PURE__*/React.createElement(Ellipse, {
|
|
29700
29705
|
center: center,
|
|
29701
29706
|
radius: radius,
|
|
29702
29707
|
angle: angle,
|
|
@@ -29712,10 +29717,11 @@ const LockedEllipse = props => {
|
|
|
29712
29717
|
fill: fillStyle === "white" ? color.white : lockedFigureColors[color$1]
|
|
29713
29718
|
}
|
|
29714
29719
|
}
|
|
29715
|
-
});
|
|
29720
|
+
}));
|
|
29716
29721
|
};
|
|
29717
29722
|
|
|
29718
29723
|
const LockedFunction = props => {
|
|
29724
|
+
var _props$flags;
|
|
29719
29725
|
const [equation, setEquation] = useState();
|
|
29720
29726
|
const {
|
|
29721
29727
|
color,
|
|
@@ -29728,6 +29734,7 @@ const LockedFunction = props => {
|
|
|
29728
29734
|
style: strokeStyle,
|
|
29729
29735
|
domain
|
|
29730
29736
|
};
|
|
29737
|
+
const hasAria = props.ariaLabel && ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["locked-figures-aria"]);
|
|
29731
29738
|
useEffect(() => {
|
|
29732
29739
|
// Parsing the equation in a "useEffect" hook saves about 2ms each frame
|
|
29733
29740
|
// when the learner is interacting with the graph (i.e. moving points).
|
|
@@ -29737,7 +29744,9 @@ const LockedFunction = props => {
|
|
|
29737
29744
|
return null;
|
|
29738
29745
|
}
|
|
29739
29746
|
return /*#__PURE__*/React.createElement("g", {
|
|
29740
|
-
className: "locked-function"
|
|
29747
|
+
className: "locked-function",
|
|
29748
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
29749
|
+
"aria-hidden": !hasAria
|
|
29741
29750
|
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
|
|
29742
29751
|
y: x => equation.eval({
|
|
29743
29752
|
x
|
|
@@ -29830,6 +29839,7 @@ function isBetween(x, low, high) {
|
|
|
29830
29839
|
}
|
|
29831
29840
|
|
|
29832
29841
|
const LockedLine = props => {
|
|
29842
|
+
var _flags$mafs;
|
|
29833
29843
|
const {
|
|
29834
29844
|
color: color$1,
|
|
29835
29845
|
lineStyle,
|
|
@@ -29837,9 +29847,12 @@ const LockedLine = props => {
|
|
|
29837
29847
|
points,
|
|
29838
29848
|
showPoint1,
|
|
29839
29849
|
showPoint2,
|
|
29850
|
+
ariaLabel,
|
|
29851
|
+
flags,
|
|
29840
29852
|
range
|
|
29841
29853
|
} = props;
|
|
29842
29854
|
const [point1, point2] = points;
|
|
29855
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29843
29856
|
let line;
|
|
29844
29857
|
if (kind === "ray") {
|
|
29845
29858
|
// Rays extend to the end of the graph in one direction.
|
|
@@ -29879,7 +29892,9 @@ const LockedLine = props => {
|
|
|
29879
29892
|
}), endArrowHead);
|
|
29880
29893
|
}
|
|
29881
29894
|
return /*#__PURE__*/React.createElement("g", {
|
|
29882
|
-
className: kind === "ray" ? "locked-ray" : "locked-line"
|
|
29895
|
+
className: kind === "ray" ? "locked-ray" : "locked-line",
|
|
29896
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
29897
|
+
"aria-hidden": !hasAria
|
|
29883
29898
|
}, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
|
|
29884
29899
|
x: point1.coord[X],
|
|
29885
29900
|
y: point1.coord[Y],
|
|
@@ -29904,13 +29919,21 @@ const LockedLine = props => {
|
|
|
29904
29919
|
};
|
|
29905
29920
|
|
|
29906
29921
|
const LockedPoint = props => {
|
|
29922
|
+
var _flags$mafs;
|
|
29907
29923
|
const {
|
|
29924
|
+
flags,
|
|
29908
29925
|
color: color$1,
|
|
29909
29926
|
coord,
|
|
29910
|
-
filled
|
|
29927
|
+
filled,
|
|
29928
|
+
ariaLabel
|
|
29911
29929
|
} = props;
|
|
29912
29930
|
const [x, y] = coord;
|
|
29913
|
-
|
|
29931
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29932
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
29933
|
+
className: "locked-point",
|
|
29934
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
29935
|
+
"aria-hidden": !hasAria
|
|
29936
|
+
}, /*#__PURE__*/React.createElement(Point$2, {
|
|
29914
29937
|
x: x,
|
|
29915
29938
|
y: y,
|
|
29916
29939
|
svgCircleProps: {
|
|
@@ -29920,10 +29943,11 @@ const LockedPoint = props => {
|
|
|
29920
29943
|
strokeWidth: spacing.xxxxSmall_2
|
|
29921
29944
|
}
|
|
29922
29945
|
}
|
|
29923
|
-
});
|
|
29946
|
+
}));
|
|
29924
29947
|
};
|
|
29925
29948
|
|
|
29926
29949
|
const LockedPolygon = props => {
|
|
29950
|
+
var _props$flags;
|
|
29927
29951
|
const {
|
|
29928
29952
|
points,
|
|
29929
29953
|
color: color$1,
|
|
@@ -29931,8 +29955,11 @@ const LockedPolygon = props => {
|
|
|
29931
29955
|
fillStyle,
|
|
29932
29956
|
strokeStyle
|
|
29933
29957
|
} = props;
|
|
29958
|
+
const hasAria = props.ariaLabel && ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["locked-figures-aria"]);
|
|
29934
29959
|
return /*#__PURE__*/React.createElement("g", {
|
|
29935
|
-
className: "locked-polygon"
|
|
29960
|
+
className: "locked-polygon",
|
|
29961
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
29962
|
+
"aria-hidden": !hasAria
|
|
29936
29963
|
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
29937
29964
|
points: [...points],
|
|
29938
29965
|
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
@@ -29956,13 +29983,19 @@ const LockedPolygon = props => {
|
|
|
29956
29983
|
};
|
|
29957
29984
|
|
|
29958
29985
|
const LockedVector = props => {
|
|
29986
|
+
var _flags$mafs;
|
|
29959
29987
|
const {
|
|
29960
29988
|
color,
|
|
29961
|
-
points
|
|
29989
|
+
points,
|
|
29990
|
+
ariaLabel,
|
|
29991
|
+
flags
|
|
29962
29992
|
} = props;
|
|
29963
29993
|
const [tail, tip] = points;
|
|
29994
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29964
29995
|
return /*#__PURE__*/React.createElement("g", {
|
|
29965
|
-
className: "locked-vector"
|
|
29996
|
+
className: "locked-vector",
|
|
29997
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
29998
|
+
"aria-hidden": !hasAria
|
|
29966
29999
|
}, /*#__PURE__*/React.createElement(Vector, {
|
|
29967
30000
|
tail: tail,
|
|
29968
30001
|
tip: tip,
|
|
@@ -29972,6 +30005,7 @@ const LockedVector = props => {
|
|
|
29972
30005
|
|
|
29973
30006
|
const GraphLockedLayer = props => {
|
|
29974
30007
|
const {
|
|
30008
|
+
flags,
|
|
29975
30009
|
lockedFigures
|
|
29976
30010
|
} = props;
|
|
29977
30011
|
return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
|
|
@@ -29979,28 +30013,40 @@ const GraphLockedLayer = props => {
|
|
|
29979
30013
|
case "point":
|
|
29980
30014
|
return /*#__PURE__*/React.createElement(LockedPoint, _extends({
|
|
29981
30015
|
key: `point-${index}`
|
|
29982
|
-
}, figure
|
|
30016
|
+
}, figure, {
|
|
30017
|
+
flags: flags
|
|
30018
|
+
}));
|
|
29983
30019
|
case "line":
|
|
29984
30020
|
return /*#__PURE__*/React.createElement(LockedLine, _extends({
|
|
29985
30021
|
key: `line-${index}`,
|
|
29986
30022
|
range: props.range
|
|
29987
|
-
}, figure
|
|
30023
|
+
}, figure, {
|
|
30024
|
+
flags: flags
|
|
30025
|
+
}));
|
|
29988
30026
|
case "vector":
|
|
29989
30027
|
return /*#__PURE__*/React.createElement(LockedVector, _extends({
|
|
29990
30028
|
key: `vector-${index}`
|
|
29991
|
-
}, figure
|
|
30029
|
+
}, figure, {
|
|
30030
|
+
flags: flags
|
|
30031
|
+
}));
|
|
29992
30032
|
case "ellipse":
|
|
29993
30033
|
return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
|
|
29994
30034
|
key: `ellipse-${index}`
|
|
29995
|
-
}, figure
|
|
30035
|
+
}, figure, {
|
|
30036
|
+
flags: flags
|
|
30037
|
+
}));
|
|
29996
30038
|
case "polygon":
|
|
29997
30039
|
return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
|
|
29998
30040
|
key: `polygon-${index}`
|
|
29999
|
-
}, figure
|
|
30041
|
+
}, figure, {
|
|
30042
|
+
flags: flags
|
|
30043
|
+
}));
|
|
30000
30044
|
case "function":
|
|
30001
30045
|
return /*#__PURE__*/React.createElement(LockedFunction, _extends({
|
|
30002
30046
|
key: `function-${index}`
|
|
30003
|
-
}, figure
|
|
30047
|
+
}, figure, {
|
|
30048
|
+
flags: flags
|
|
30049
|
+
}));
|
|
30004
30050
|
case "label":
|
|
30005
30051
|
// This is rendered outside the SVG element, since
|
|
30006
30052
|
// TeX cannot be rendered inside an SVG.
|
|
@@ -31466,7 +31512,7 @@ function QuadraticGraph(props) {
|
|
|
31466
31512
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
31467
31513
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
31468
31514
|
const coeffRef = React.useRef([0, 0, 0]);
|
|
31469
|
-
const coeffs = getQuadraticCoefficients(coords);
|
|
31515
|
+
const coeffs = getQuadraticCoefficients$1(coords);
|
|
31470
31516
|
if (coeffs !== undefined) {
|
|
31471
31517
|
coeffRef.current = coeffs;
|
|
31472
31518
|
}
|
|
@@ -31490,7 +31536,7 @@ function QuadraticGraph(props) {
|
|
|
31490
31536
|
// These equations were originally set up in 2013 and may require some
|
|
31491
31537
|
// additional comments to help clarify the quadratic formula manipulations
|
|
31492
31538
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
31493
|
-
const getQuadraticCoefficients = coords => {
|
|
31539
|
+
const getQuadraticCoefficients$1 = coords => {
|
|
31494
31540
|
const p1 = coords[0];
|
|
31495
31541
|
const p2 = coords[1];
|
|
31496
31542
|
const p3 = coords[2];
|
|
@@ -31530,7 +31576,7 @@ function SinusoidGraph(props) {
|
|
|
31530
31576
|
phase: 1,
|
|
31531
31577
|
verticalOffset: 0
|
|
31532
31578
|
});
|
|
31533
|
-
const coeffs = getSinusoidCoefficients(coords);
|
|
31579
|
+
const coeffs = getSinusoidCoefficients$1(coords);
|
|
31534
31580
|
|
|
31535
31581
|
// If the coefficients are valid, update the reference
|
|
31536
31582
|
if (coeffs !== undefined) {
|
|
@@ -31559,7 +31605,7 @@ sinusoidCoefficients) {
|
|
|
31559
31605
|
} = sinusoidCoefficients;
|
|
31560
31606
|
return a * Math.sin(b * x - c) + d;
|
|
31561
31607
|
};
|
|
31562
|
-
const getSinusoidCoefficients = coords => {
|
|
31608
|
+
const getSinusoidCoefficients$1 = coords => {
|
|
31563
31609
|
// It's assumed that p1 is the root and p2 is the first peak
|
|
31564
31610
|
const p1 = coords[0];
|
|
31565
31611
|
const p2 = coords[1];
|
|
@@ -31777,7 +31823,7 @@ function Protractor() {
|
|
|
31777
31823
|
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
31778
31824
|
// Position the protractor in the center of the graph (horizontally), and
|
|
31779
31825
|
// 95% of the way to the bottom of the graph (vertically).
|
|
31780
|
-
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.
|
|
31826
|
+
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.25)];
|
|
31781
31827
|
const [center, setCenter] = useState(initialCenter);
|
|
31782
31828
|
const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
|
|
31783
31829
|
const draggableRef = useRef(null);
|
|
@@ -31939,6 +31985,9 @@ const MafsGraph = props => {
|
|
|
31939
31985
|
x: viewboxX,
|
|
31940
31986
|
y: viewboxY
|
|
31941
31987
|
};
|
|
31988
|
+
const {
|
|
31989
|
+
strings
|
|
31990
|
+
} = usePerseusI18n();
|
|
31942
31991
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
31943
31992
|
value: {
|
|
31944
31993
|
range: state.range,
|
|
@@ -32017,6 +32066,7 @@ const MafsGraph = props => {
|
|
|
32017
32066
|
})),
|
|
32018
32067
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
32019
32068
|
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)), /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
32069
|
+
flags: props.flags,
|
|
32020
32070
|
lockedFigures: props.lockedFigures,
|
|
32021
32071
|
range: state.range
|
|
32022
32072
|
}))), ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["interactive-graph-locked-features-labels"]) && props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLabelsLayer, {
|
|
@@ -32053,10 +32103,11 @@ const MafsGraph = props => {
|
|
|
32053
32103
|
top: "50%",
|
|
32054
32104
|
transform: "translateY(-50%)"
|
|
32055
32105
|
}
|
|
32056
|
-
}, /*#__PURE__*/React.createElement(LabelMedium, null,
|
|
32106
|
+
}, /*#__PURE__*/React.createElement(LabelMedium, null, strings.graphKeyboardPrompt))), renderGraphControls({
|
|
32057
32107
|
state,
|
|
32058
32108
|
dispatch,
|
|
32059
|
-
width
|
|
32109
|
+
width,
|
|
32110
|
+
strings
|
|
32060
32111
|
})));
|
|
32061
32112
|
};
|
|
32062
32113
|
const renderPointGraphControls = props => {
|
|
@@ -32065,6 +32116,10 @@ const renderPointGraphControls = props => {
|
|
|
32065
32116
|
showRemovePointButton,
|
|
32066
32117
|
focusedPointIndex
|
|
32067
32118
|
} = props.state;
|
|
32119
|
+
const {
|
|
32120
|
+
strings
|
|
32121
|
+
} = props;
|
|
32122
|
+
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
32068
32123
|
return /*#__PURE__*/React.createElement(View, {
|
|
32069
32124
|
style: {
|
|
32070
32125
|
flexDirection: "row",
|
|
@@ -32080,7 +32135,7 @@ const renderPointGraphControls = props => {
|
|
|
32080
32135
|
onClick: () => {
|
|
32081
32136
|
props.dispatch(actions.pointGraph.addPoint([0, 0]));
|
|
32082
32137
|
}
|
|
32083
|
-
},
|
|
32138
|
+
}, strings.addPoint), interactionMode === "mouse" && /*#__PURE__*/React.createElement(Button, {
|
|
32084
32139
|
id: REMOVE_BUTTON_ID,
|
|
32085
32140
|
kind: "secondary",
|
|
32086
32141
|
color: "destructive"
|
|
@@ -32090,18 +32145,20 @@ const renderPointGraphControls = props => {
|
|
|
32090
32145
|
tabIndex: -1,
|
|
32091
32146
|
style: {
|
|
32092
32147
|
width: "100%",
|
|
32093
|
-
marginLeft: "20px"
|
|
32148
|
+
marginLeft: "20px",
|
|
32149
|
+
visibility: shouldShowRemoveButton ? "visible" : "hidden"
|
|
32094
32150
|
},
|
|
32095
32151
|
onClick: event => {
|
|
32096
32152
|
props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));
|
|
32097
32153
|
}
|
|
32098
|
-
},
|
|
32154
|
+
}, strings.removePoint));
|
|
32099
32155
|
};
|
|
32100
32156
|
const renderGraphControls = props => {
|
|
32101
32157
|
const {
|
|
32102
32158
|
state,
|
|
32103
32159
|
dispatch,
|
|
32104
|
-
width
|
|
32160
|
+
width,
|
|
32161
|
+
strings
|
|
32105
32162
|
} = props;
|
|
32106
32163
|
const {
|
|
32107
32164
|
type
|
|
@@ -32112,7 +32169,8 @@ const renderGraphControls = props => {
|
|
|
32112
32169
|
return renderPointGraphControls({
|
|
32113
32170
|
state,
|
|
32114
32171
|
dispatch,
|
|
32115
|
-
width
|
|
32172
|
+
width,
|
|
32173
|
+
strings
|
|
32116
32174
|
});
|
|
32117
32175
|
}
|
|
32118
32176
|
return null;
|
|
@@ -32264,6 +32322,83 @@ const renderGraph = props => {
|
|
|
32264
32322
|
}
|
|
32265
32323
|
};
|
|
32266
32324
|
|
|
32325
|
+
// Converts the state of a StatefulMafsGraph back to the format used to
|
|
32326
|
+
// represent graph state in the widget JSON.
|
|
32327
|
+
//
|
|
32328
|
+
// Rather than be tightly bound to how data was structured in
|
|
32329
|
+
// the legacy interactive graph, this lets us store state
|
|
32330
|
+
// however we want and we just transform it before handing it off
|
|
32331
|
+
// the the parent InteractiveGraph.
|
|
32332
|
+
//
|
|
32333
|
+
// The transformed data is used in the interactive graph widget editor, to
|
|
32334
|
+
// set the `correct` field of the graph options. In the learner-facing UI, the
|
|
32335
|
+
// data is also stored by the Renderer, and passed back down to the graph
|
|
32336
|
+
// widget via the `graph` prop. Because the data returned by this function
|
|
32337
|
+
// completely replaces the Renderer's representation of the widget, rather than
|
|
32338
|
+
// being merged into it, we take it upon ourselves to merge in the original
|
|
32339
|
+
// widget data here. If we didn't do this merging, the graph's configuration
|
|
32340
|
+
// would reset to the defaults when the learner interacted with it.
|
|
32341
|
+
function mafsStateToInteractiveGraph(state, originalGraph) {
|
|
32342
|
+
switch (state.type) {
|
|
32343
|
+
case "angle":
|
|
32344
|
+
invariant(originalGraph.type === "angle");
|
|
32345
|
+
return _extends({}, originalGraph, {
|
|
32346
|
+
coords: state.coords
|
|
32347
|
+
});
|
|
32348
|
+
case "quadratic":
|
|
32349
|
+
invariant(originalGraph.type === "quadratic");
|
|
32350
|
+
return _extends({}, originalGraph, {
|
|
32351
|
+
coords: state.coords
|
|
32352
|
+
});
|
|
32353
|
+
case "circle":
|
|
32354
|
+
invariant(originalGraph.type === "circle");
|
|
32355
|
+
return _extends({}, originalGraph, {
|
|
32356
|
+
center: state.center,
|
|
32357
|
+
radius: getRadius(state)
|
|
32358
|
+
});
|
|
32359
|
+
case "linear":
|
|
32360
|
+
invariant(originalGraph.type === "linear");
|
|
32361
|
+
return _extends({}, originalGraph, {
|
|
32362
|
+
coords: state.coords
|
|
32363
|
+
});
|
|
32364
|
+
case "ray":
|
|
32365
|
+
invariant(originalGraph.type === "ray");
|
|
32366
|
+
return _extends({}, originalGraph, {
|
|
32367
|
+
coords: state.coords
|
|
32368
|
+
});
|
|
32369
|
+
case "sinusoid":
|
|
32370
|
+
invariant(originalGraph.type === "sinusoid");
|
|
32371
|
+
return _extends({}, originalGraph, {
|
|
32372
|
+
coords: state.coords
|
|
32373
|
+
});
|
|
32374
|
+
case "segment":
|
|
32375
|
+
invariant(originalGraph.type === "segment");
|
|
32376
|
+
return _extends({}, originalGraph, {
|
|
32377
|
+
coords: state.coords
|
|
32378
|
+
});
|
|
32379
|
+
case "linear-system":
|
|
32380
|
+
invariant(originalGraph.type === "linear-system");
|
|
32381
|
+
return _extends({}, originalGraph, {
|
|
32382
|
+
coords: state.coords
|
|
32383
|
+
});
|
|
32384
|
+
case "polygon":
|
|
32385
|
+
invariant(originalGraph.type === "polygon");
|
|
32386
|
+
return _extends({}, originalGraph, {
|
|
32387
|
+
coords: state.coords
|
|
32388
|
+
});
|
|
32389
|
+
case "point":
|
|
32390
|
+
invariant(originalGraph.type === "point");
|
|
32391
|
+
return _extends({}, originalGraph, {
|
|
32392
|
+
coords: state.coords
|
|
32393
|
+
});
|
|
32394
|
+
case "none":
|
|
32395
|
+
invariant(originalGraph.type === "none");
|
|
32396
|
+
return _extends({}, originalGraph);
|
|
32397
|
+
default:
|
|
32398
|
+
throw new UnreachableCaseError(state);
|
|
32399
|
+
}
|
|
32400
|
+
}
|
|
32401
|
+
|
|
32267
32402
|
function initializeGraphState(params) {
|
|
32268
32403
|
var _graph$snapTo;
|
|
32269
32404
|
const {
|
|
@@ -32889,7 +33024,7 @@ function doMovePoint(state, action) {
|
|
|
32889
33024
|
// Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
|
|
32890
33025
|
const boundDestination = boundAndSnapToGrid(action.destination, state);
|
|
32891
33026
|
_newCoords2[action.index] = boundDestination;
|
|
32892
|
-
const QuadraticCoefficients = getQuadraticCoefficients(_newCoords2);
|
|
33027
|
+
const QuadraticCoefficients = getQuadraticCoefficients$1(_newCoords2);
|
|
32893
33028
|
|
|
32894
33029
|
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
32895
33030
|
if (QuadraticCoefficients === undefined) {
|
|
@@ -33036,11 +33171,11 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
33036
33171
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
33037
33172
|
return [dx, dy];
|
|
33038
33173
|
};
|
|
33039
|
-
const eq$
|
|
33174
|
+
const eq$2 = Util.eq;
|
|
33040
33175
|
|
|
33041
33176
|
// Less than or approximately equal
|
|
33042
33177
|
function leq$1(a, b) {
|
|
33043
|
-
return a < b || eq$
|
|
33178
|
+
return a < b || eq$2(a, b);
|
|
33044
33179
|
}
|
|
33045
33180
|
function boundAndSnapToGrid(point, {
|
|
33046
33181
|
snapStep,
|
|
@@ -33343,20 +33478,6 @@ function setAtIndex(args) {
|
|
|
33343
33478
|
return copy;
|
|
33344
33479
|
}
|
|
33345
33480
|
|
|
33346
|
-
// Rather than be tightly bound to how data was structured in
|
|
33347
|
-
// the legacy interactive graph, this lets us store state
|
|
33348
|
-
// however we want and we just transform it before handing it off
|
|
33349
|
-
// the the parent InteractiveGraph
|
|
33350
|
-
function mafsStateToInteractiveGraph(state) {
|
|
33351
|
-
if (state.graph.type === "circle") {
|
|
33352
|
-
return _extends({}, state, {
|
|
33353
|
-
graph: _extends({}, state.graph, {
|
|
33354
|
-
radius: getRadius(state.graph)
|
|
33355
|
-
})
|
|
33356
|
-
});
|
|
33357
|
-
}
|
|
33358
|
-
return _extends({}, state);
|
|
33359
|
-
}
|
|
33360
33481
|
const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
33361
33482
|
const {
|
|
33362
33483
|
onChange,
|
|
@@ -33369,12 +33490,12 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33369
33490
|
const prevState = useRef(state);
|
|
33370
33491
|
useEffect(() => {
|
|
33371
33492
|
if (prevState.current !== state) {
|
|
33372
|
-
onChange(
|
|
33373
|
-
graph: state
|
|
33374
|
-
})
|
|
33493
|
+
onChange({
|
|
33494
|
+
graph: mafsStateToInteractiveGraph(state, graph)
|
|
33495
|
+
});
|
|
33375
33496
|
}
|
|
33376
33497
|
prevState.current = state;
|
|
33377
|
-
}, [onChange, state]);
|
|
33498
|
+
}, [onChange, state, graph]);
|
|
33378
33499
|
|
|
33379
33500
|
// Destructuring first to keep useEffect from making excess calls
|
|
33380
33501
|
const [xSnap, ySnap] = props.snapStep;
|
|
@@ -33426,12 +33547,234 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33426
33547
|
}));
|
|
33427
33548
|
});
|
|
33428
33549
|
|
|
33550
|
+
const eq$1 = Util.eq;
|
|
33551
|
+
const deepEq$1 = Util.deepEq;
|
|
33552
|
+
function interactiveGraphValidator(userInput, rubric) {
|
|
33553
|
+
// None-type graphs are not graded
|
|
33554
|
+
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
33555
|
+
return {
|
|
33556
|
+
type: "points",
|
|
33557
|
+
earned: 0,
|
|
33558
|
+
total: 0,
|
|
33559
|
+
message: null
|
|
33560
|
+
};
|
|
33561
|
+
}
|
|
33562
|
+
|
|
33563
|
+
// When nothing has moved, there will neither be coords nor the
|
|
33564
|
+
// circle's center/radius fields. When those fields are absent, skip
|
|
33565
|
+
// all these checks; just go mark the answer as empty.
|
|
33566
|
+
const hasValue = Boolean(
|
|
33567
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
33568
|
+
userInput.coords ||
|
|
33569
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
33570
|
+
userInput.center && userInput.radius);
|
|
33571
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
33572
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
33573
|
+
const guess = userInput.coords;
|
|
33574
|
+
const correct = rubric.correct.coords;
|
|
33575
|
+
|
|
33576
|
+
// If both of the guess points are on the correct line, it's
|
|
33577
|
+
// correct.
|
|
33578
|
+
if (
|
|
33579
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33580
|
+
collinear(correct[0], correct[1], guess[0]) &&
|
|
33581
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33582
|
+
collinear(correct[0], correct[1], guess[1])) {
|
|
33583
|
+
return {
|
|
33584
|
+
type: "points",
|
|
33585
|
+
earned: 1,
|
|
33586
|
+
total: 1,
|
|
33587
|
+
message: null
|
|
33588
|
+
};
|
|
33589
|
+
}
|
|
33590
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
33591
|
+
const guess = userInput.coords;
|
|
33592
|
+
const correct = rubric.correct.coords;
|
|
33593
|
+
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])) {
|
|
33594
|
+
return {
|
|
33595
|
+
type: "points",
|
|
33596
|
+
earned: 1,
|
|
33597
|
+
total: 1,
|
|
33598
|
+
message: null
|
|
33599
|
+
};
|
|
33600
|
+
}
|
|
33601
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
33602
|
+
// If the parabola coefficients match, it's correct.
|
|
33603
|
+
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
33604
|
+
const correctCoeffs = getQuadraticCoefficients(
|
|
33605
|
+
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
33606
|
+
rubric.correct.coords);
|
|
33607
|
+
if (deepEq$1(guessCoeffs, correctCoeffs)) {
|
|
33608
|
+
return {
|
|
33609
|
+
type: "points",
|
|
33610
|
+
earned: 1,
|
|
33611
|
+
total: 1,
|
|
33612
|
+
message: null
|
|
33613
|
+
};
|
|
33614
|
+
}
|
|
33615
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
33616
|
+
const guessCoeffs = getSinusoidCoefficients(userInput.coords);
|
|
33617
|
+
const correctCoeffs = getSinusoidCoefficients(
|
|
33618
|
+
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
33619
|
+
rubric.correct.coords);
|
|
33620
|
+
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
33621
|
+
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
33622
|
+
// If the canonical coefficients match, it's correct.
|
|
33623
|
+
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
33624
|
+
return {
|
|
33625
|
+
type: "points",
|
|
33626
|
+
earned: 1,
|
|
33627
|
+
total: 1,
|
|
33628
|
+
message: null
|
|
33629
|
+
};
|
|
33630
|
+
}
|
|
33631
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
33632
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
33633
|
+
return {
|
|
33634
|
+
type: "points",
|
|
33635
|
+
earned: 1,
|
|
33636
|
+
total: 1,
|
|
33637
|
+
message: null
|
|
33638
|
+
};
|
|
33639
|
+
}
|
|
33640
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
33641
|
+
let correct = rubric.correct.coords;
|
|
33642
|
+
if (correct == null) {
|
|
33643
|
+
throw new Error("Point graph rubric has null coords");
|
|
33644
|
+
}
|
|
33645
|
+
const guess = userInput.coords.slice();
|
|
33646
|
+
correct = correct.slice();
|
|
33647
|
+
// Everything's already rounded so we shouldn't need to do an
|
|
33648
|
+
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
33649
|
+
// eq() anyway. The sort should be fine because it'll stringify
|
|
33650
|
+
// it and -0 converted to a string is "0"
|
|
33651
|
+
guess == null || guess.sort();
|
|
33652
|
+
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
33653
|
+
correct.sort();
|
|
33654
|
+
if (deepEq$1(guess, correct)) {
|
|
33655
|
+
return {
|
|
33656
|
+
type: "points",
|
|
33657
|
+
earned: 1,
|
|
33658
|
+
total: 1,
|
|
33659
|
+
message: null
|
|
33660
|
+
};
|
|
33661
|
+
}
|
|
33662
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
33663
|
+
var _userInput$coords, _rubric$correct$coord;
|
|
33664
|
+
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
33665
|
+
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
33666
|
+
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
33667
|
+
let match;
|
|
33668
|
+
if (rubric.correct.match === "similar") {
|
|
33669
|
+
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
33670
|
+
} else if (rubric.correct.match === "congruent") {
|
|
33671
|
+
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
33672
|
+
} else if (rubric.correct.match === "approx") {
|
|
33673
|
+
match = similar(guess, correct, 0.1);
|
|
33674
|
+
} else {
|
|
33675
|
+
/* exact */
|
|
33676
|
+
guess.sort();
|
|
33677
|
+
correct.sort();
|
|
33678
|
+
match = deepEq$1(guess, correct);
|
|
33679
|
+
}
|
|
33680
|
+
if (match) {
|
|
33681
|
+
return {
|
|
33682
|
+
type: "points",
|
|
33683
|
+
earned: 1,
|
|
33684
|
+
total: 1,
|
|
33685
|
+
message: null
|
|
33686
|
+
};
|
|
33687
|
+
}
|
|
33688
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
33689
|
+
var _rubric$correct;
|
|
33690
|
+
let guess = Util.deepClone(userInput.coords);
|
|
33691
|
+
let correct = Util.deepClone((_rubric$correct = rubric.correct) == null ? void 0 : _rubric$correct.coords);
|
|
33692
|
+
guess = _.invoke(guess, "sort").sort();
|
|
33693
|
+
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
33694
|
+
correct = _.invoke(correct, "sort").sort();
|
|
33695
|
+
if (deepEq$1(guess, correct)) {
|
|
33696
|
+
return {
|
|
33697
|
+
type: "points",
|
|
33698
|
+
earned: 1,
|
|
33699
|
+
total: 1,
|
|
33700
|
+
message: null
|
|
33701
|
+
};
|
|
33702
|
+
}
|
|
33703
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
33704
|
+
const guess = userInput.coords;
|
|
33705
|
+
const correct = rubric.correct.coords;
|
|
33706
|
+
if (
|
|
33707
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
33708
|
+
deepEq$1(guess[0], correct[0]) &&
|
|
33709
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33710
|
+
collinear(correct[0], correct[1], guess[1])) {
|
|
33711
|
+
return {
|
|
33712
|
+
type: "points",
|
|
33713
|
+
earned: 1,
|
|
33714
|
+
total: 1,
|
|
33715
|
+
message: null
|
|
33716
|
+
};
|
|
33717
|
+
}
|
|
33718
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
33719
|
+
const guess = userInput.coords;
|
|
33720
|
+
const correct = rubric.correct.coords;
|
|
33721
|
+
let match;
|
|
33722
|
+
if (rubric.correct.match === "congruent") {
|
|
33723
|
+
const angles = _.map([guess, correct], function (coords) {
|
|
33724
|
+
const angle = GraphUtils.findAngle(
|
|
33725
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
33726
|
+
coords[2],
|
|
33727
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
33728
|
+
coords[0],
|
|
33729
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
33730
|
+
coords[1]);
|
|
33731
|
+
return (angle + 360) % 360;
|
|
33732
|
+
});
|
|
33733
|
+
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
33734
|
+
match = eq$1(...angles);
|
|
33735
|
+
} else {
|
|
33736
|
+
/* exact */
|
|
33737
|
+
match =
|
|
33738
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33739
|
+
deepEq$1(guess[1], correct[1]) &&
|
|
33740
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33741
|
+
collinear(correct[1], correct[0], guess[0]) &&
|
|
33742
|
+
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
33743
|
+
collinear(correct[1], correct[2], guess[2]);
|
|
33744
|
+
}
|
|
33745
|
+
if (match) {
|
|
33746
|
+
return {
|
|
33747
|
+
type: "points",
|
|
33748
|
+
earned: 1,
|
|
33749
|
+
total: 1,
|
|
33750
|
+
message: null
|
|
33751
|
+
};
|
|
33752
|
+
}
|
|
33753
|
+
}
|
|
33754
|
+
}
|
|
33755
|
+
|
|
33756
|
+
// The input wasn't correct, so check if it's a blank input or if it's
|
|
33757
|
+
// actually just wrong
|
|
33758
|
+
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
33759
|
+
// We're where we started.
|
|
33760
|
+
return {
|
|
33761
|
+
type: "invalid",
|
|
33762
|
+
message: null
|
|
33763
|
+
};
|
|
33764
|
+
}
|
|
33765
|
+
return {
|
|
33766
|
+
type: "points",
|
|
33767
|
+
earned: 0,
|
|
33768
|
+
total: 1,
|
|
33769
|
+
message: null
|
|
33770
|
+
};
|
|
33771
|
+
}
|
|
33772
|
+
|
|
33429
33773
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
33430
33774
|
const defaultBackgroundImage = {
|
|
33431
33775
|
url: null
|
|
33432
33776
|
};
|
|
33433
33777
|
const eq = Util.eq;
|
|
33434
|
-
const deepEq$1 = Util.deepEq;
|
|
33435
33778
|
const UNLIMITED = "unlimited";
|
|
33436
33779
|
|
|
33437
33780
|
// Sample background image:
|
|
@@ -33472,6 +33815,39 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
33472
33815
|
|
|
33473
33816
|
// There's no transform function in exports
|
|
33474
33817
|
|
|
33818
|
+
// TODO: there's another, very similar getSinusoidCoefficients function
|
|
33819
|
+
// they should probably be merged
|
|
33820
|
+
function getSinusoidCoefficients(coords) {
|
|
33821
|
+
// It's assumed that p1 is the root and p2 is the first peak
|
|
33822
|
+
const p1 = coords[0];
|
|
33823
|
+
const p2 = coords[1];
|
|
33824
|
+
|
|
33825
|
+
// Resulting coefficients are canonical for this sine curve
|
|
33826
|
+
const amplitude = p2[1] - p1[1];
|
|
33827
|
+
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
33828
|
+
const phase = p1[0] * angularFrequency;
|
|
33829
|
+
const verticalOffset = p1[1];
|
|
33830
|
+
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
33831
|
+
}
|
|
33832
|
+
|
|
33833
|
+
// TODO: there's another, very similar getQuadraticCoefficients function
|
|
33834
|
+
// they should probably be merged
|
|
33835
|
+
function getQuadraticCoefficients(coords) {
|
|
33836
|
+
const p1 = coords[0];
|
|
33837
|
+
const p2 = coords[1];
|
|
33838
|
+
const p3 = coords[2];
|
|
33839
|
+
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
33840
|
+
if (denom === 0) {
|
|
33841
|
+
// Many of the callers assume that the return value is always defined.
|
|
33842
|
+
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
|
|
33843
|
+
return;
|
|
33844
|
+
}
|
|
33845
|
+
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
33846
|
+
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;
|
|
33847
|
+
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;
|
|
33848
|
+
return [a, b, c];
|
|
33849
|
+
}
|
|
33850
|
+
|
|
33475
33851
|
// (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
|
|
33476
33852
|
// to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
|
|
33477
33853
|
// for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
|
|
@@ -34555,7 +34931,6 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
34555
34931
|
$(this.angle).on("move", () => {
|
|
34556
34932
|
var _this$angle;
|
|
34557
34933
|
this.onChange({
|
|
34558
|
-
// @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'.
|
|
34559
34934
|
graph: _extends({}, graph, {
|
|
34560
34935
|
coords: (_this$angle = this.angle) == null ? void 0 : _this$angle.getClockwiseCoords()
|
|
34561
34936
|
})
|
|
@@ -34629,7 +35004,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
34629
35004
|
return InteractiveGraph.getUserInputFromProps(this.props);
|
|
34630
35005
|
}
|
|
34631
35006
|
simpleValidate(rubric) {
|
|
34632
|
-
return
|
|
35007
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
34633
35008
|
}
|
|
34634
35009
|
render() {
|
|
34635
35010
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
@@ -34696,20 +35071,19 @@ class InteractiveGraph extends React.Component {
|
|
|
34696
35071
|
super(...args);
|
|
34697
35072
|
this.legacyGraphRef = /*#__PURE__*/React.createRef();
|
|
34698
35073
|
this.mafsRef = /*#__PURE__*/React.createRef();
|
|
34699
|
-
|
|
34700
|
-
|
|
34701
|
-
|
|
34702
|
-
|
|
34703
|
-
|
|
34704
|
-
|
|
34705
|
-
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
35074
|
+
}
|
|
35075
|
+
getUserInput() {
|
|
35076
|
+
var _this$mafsRef$current, _this$legacyGraphRef$;
|
|
35077
|
+
if ((_this$mafsRef$current = this.mafsRef.current) != null && _this$mafsRef$current.getUserInput) {
|
|
35078
|
+
return this.mafsRef.current.getUserInput();
|
|
35079
|
+
}
|
|
35080
|
+
if ((_this$legacyGraphRef$ = this.legacyGraphRef.current) != null && _this$legacyGraphRef$.getUserInput) {
|
|
35081
|
+
return this.legacyGraphRef.current.getUserInput();
|
|
35082
|
+
}
|
|
35083
|
+
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
34709
35084
|
}
|
|
34710
35085
|
simpleValidate(rubric) {
|
|
34711
|
-
|
|
34712
|
-
return InteractiveGraph.validate((_this$getUserInput = this.getUserInput) == null ? void 0 : _this$getUserInput.call(this), rubric, this);
|
|
35086
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
34713
35087
|
}
|
|
34714
35088
|
render() {
|
|
34715
35089
|
var _this$props$apiOption;
|
|
@@ -34734,33 +35108,6 @@ class InteractiveGraph extends React.Component {
|
|
|
34734
35108
|
ref: this.legacyGraphRef
|
|
34735
35109
|
}, this.props));
|
|
34736
35110
|
}
|
|
34737
|
-
static getQuadraticCoefficients(coords) {
|
|
34738
|
-
const p1 = coords[0];
|
|
34739
|
-
const p2 = coords[1];
|
|
34740
|
-
const p3 = coords[2];
|
|
34741
|
-
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
34742
|
-
if (denom === 0) {
|
|
34743
|
-
// Many of the callers assume that the return value is always defined.
|
|
34744
|
-
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
|
|
34745
|
-
return;
|
|
34746
|
-
}
|
|
34747
|
-
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
34748
|
-
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;
|
|
34749
|
-
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;
|
|
34750
|
-
return [a, b, c];
|
|
34751
|
-
}
|
|
34752
|
-
static getSinusoidCoefficients(coords) {
|
|
34753
|
-
// It's assumed that p1 is the root and p2 is the first peak
|
|
34754
|
-
const p1 = coords[0];
|
|
34755
|
-
const p2 = coords[1];
|
|
34756
|
-
|
|
34757
|
-
// Resulting coefficients are canonical for this sine curve
|
|
34758
|
-
const amplitude = p2[1] - p1[1];
|
|
34759
|
-
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
34760
|
-
const phase = p1[0] * angularFrequency;
|
|
34761
|
-
const verticalOffset = p1[1];
|
|
34762
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
34763
|
-
}
|
|
34764
35111
|
|
|
34765
35112
|
/**
|
|
34766
35113
|
* @param {object} graph Like props.graph or props.correct
|
|
@@ -34974,7 +35321,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34974
35321
|
const coords =
|
|
34975
35322
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
34976
35323
|
props.graph.coords || InteractiveGraph.defaultQuadraticCoords(props);
|
|
34977
|
-
return
|
|
35324
|
+
return getQuadraticCoefficients(coords);
|
|
34978
35325
|
}
|
|
34979
35326
|
static defaultQuadraticCoords(props) {
|
|
34980
35327
|
const coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
|
|
@@ -34989,7 +35336,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34989
35336
|
const coords =
|
|
34990
35337
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
34991
35338
|
props.graph.coords || InteractiveGraph.defaultSinusoidCoords(props);
|
|
34992
|
-
return
|
|
35339
|
+
return getSinusoidCoefficients(coords);
|
|
34993
35340
|
}
|
|
34994
35341
|
static defaultSinusoidCoords(props) {
|
|
34995
35342
|
const coords = [[0.5, 0.5], [0.65, 0.6]];
|
|
@@ -35069,222 +35416,8 @@ class InteractiveGraph extends React.Component {
|
|
|
35069
35416
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
35070
35417
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
35071
35418
|
}
|
|
35072
|
-
static validate(userInput, rubric
|
|
35073
|
-
|
|
35074
|
-
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35075
|
-
return {
|
|
35076
|
-
type: "points",
|
|
35077
|
-
earned: 0,
|
|
35078
|
-
total: 0,
|
|
35079
|
-
message: null
|
|
35080
|
-
};
|
|
35081
|
-
}
|
|
35082
|
-
|
|
35083
|
-
// When nothing has moved, there will neither be coords nor the
|
|
35084
|
-
// circle's center/radius fields. When those fields are absent, skip
|
|
35085
|
-
// all these checks; just go mark the answer as empty.
|
|
35086
|
-
const hasValue = Boolean(
|
|
35087
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35088
|
-
userInput.coords ||
|
|
35089
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
35090
|
-
userInput.center && userInput.radius);
|
|
35091
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
35092
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
35093
|
-
const guess = userInput.coords;
|
|
35094
|
-
const correct = rubric.correct.coords;
|
|
35095
|
-
|
|
35096
|
-
// If both of the guess points are on the correct line, it's
|
|
35097
|
-
// correct.
|
|
35098
|
-
if (
|
|
35099
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35100
|
-
collinear(correct[0], correct[1], guess[0]) &&
|
|
35101
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35102
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35103
|
-
return {
|
|
35104
|
-
type: "points",
|
|
35105
|
-
earned: 1,
|
|
35106
|
-
total: 1,
|
|
35107
|
-
message: null
|
|
35108
|
-
};
|
|
35109
|
-
}
|
|
35110
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
35111
|
-
const guess = userInput.coords;
|
|
35112
|
-
const correct = rubric.correct.coords;
|
|
35113
|
-
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])) {
|
|
35114
|
-
return {
|
|
35115
|
-
type: "points",
|
|
35116
|
-
earned: 1,
|
|
35117
|
-
total: 1,
|
|
35118
|
-
message: null
|
|
35119
|
-
};
|
|
35120
|
-
}
|
|
35121
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
35122
|
-
// If the parabola coefficients match, it's correct.
|
|
35123
|
-
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
35124
|
-
const correctCoeffs = this.getQuadraticCoefficients(
|
|
35125
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35126
|
-
rubric.correct.coords);
|
|
35127
|
-
if (deepEq$1(guessCoeffs, correctCoeffs)) {
|
|
35128
|
-
return {
|
|
35129
|
-
type: "points",
|
|
35130
|
-
earned: 1,
|
|
35131
|
-
total: 1,
|
|
35132
|
-
message: null
|
|
35133
|
-
};
|
|
35134
|
-
}
|
|
35135
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
35136
|
-
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
35137
|
-
const correctCoeffs = this.getSinusoidCoefficients(
|
|
35138
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35139
|
-
rubric.correct.coords);
|
|
35140
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
35141
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
35142
|
-
// If the canonical coefficients match, it's correct.
|
|
35143
|
-
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
35144
|
-
return {
|
|
35145
|
-
type: "points",
|
|
35146
|
-
earned: 1,
|
|
35147
|
-
total: 1,
|
|
35148
|
-
message: null
|
|
35149
|
-
};
|
|
35150
|
-
}
|
|
35151
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
35152
|
-
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
35153
|
-
return {
|
|
35154
|
-
type: "points",
|
|
35155
|
-
earned: 1,
|
|
35156
|
-
total: 1,
|
|
35157
|
-
message: null
|
|
35158
|
-
};
|
|
35159
|
-
}
|
|
35160
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
35161
|
-
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
35162
|
-
const guess = userInput.coords.slice();
|
|
35163
|
-
correct = correct.slice();
|
|
35164
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
35165
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
35166
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
35167
|
-
// it and -0 converted to a string is "0"
|
|
35168
|
-
guess == null || guess.sort();
|
|
35169
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
35170
|
-
correct.sort();
|
|
35171
|
-
if (deepEq$1(guess, correct)) {
|
|
35172
|
-
return {
|
|
35173
|
-
type: "points",
|
|
35174
|
-
earned: 1,
|
|
35175
|
-
total: 1,
|
|
35176
|
-
message: null
|
|
35177
|
-
};
|
|
35178
|
-
}
|
|
35179
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
35180
|
-
var _userInput$coords, _rubric$correct$coord;
|
|
35181
|
-
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
35182
|
-
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
35183
|
-
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
35184
|
-
let match;
|
|
35185
|
-
if (rubric.correct.match === "similar") {
|
|
35186
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
35187
|
-
} else if (rubric.correct.match === "congruent") {
|
|
35188
|
-
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
35189
|
-
} else if (rubric.correct.match === "approx") {
|
|
35190
|
-
match = similar(guess, correct, 0.1);
|
|
35191
|
-
} else {
|
|
35192
|
-
/* exact */
|
|
35193
|
-
guess.sort();
|
|
35194
|
-
correct.sort();
|
|
35195
|
-
match = deepEq$1(guess, correct);
|
|
35196
|
-
}
|
|
35197
|
-
if (match) {
|
|
35198
|
-
return {
|
|
35199
|
-
type: "points",
|
|
35200
|
-
earned: 1,
|
|
35201
|
-
total: 1,
|
|
35202
|
-
message: null
|
|
35203
|
-
};
|
|
35204
|
-
}
|
|
35205
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
35206
|
-
var _rubric$correct;
|
|
35207
|
-
let guess = Util.deepClone(userInput.coords);
|
|
35208
|
-
let correct = Util.deepClone((_rubric$correct = rubric.correct) == null ? void 0 : _rubric$correct.coords);
|
|
35209
|
-
guess = _.invoke(guess, "sort").sort();
|
|
35210
|
-
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
35211
|
-
correct = _.invoke(correct, "sort").sort();
|
|
35212
|
-
if (deepEq$1(guess, correct)) {
|
|
35213
|
-
return {
|
|
35214
|
-
type: "points",
|
|
35215
|
-
earned: 1,
|
|
35216
|
-
total: 1,
|
|
35217
|
-
message: null
|
|
35218
|
-
};
|
|
35219
|
-
}
|
|
35220
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
35221
|
-
const guess = userInput.coords;
|
|
35222
|
-
const correct = rubric.correct.coords;
|
|
35223
|
-
if (
|
|
35224
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35225
|
-
deepEq$1(guess[0], correct[0]) &&
|
|
35226
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35227
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35228
|
-
return {
|
|
35229
|
-
type: "points",
|
|
35230
|
-
earned: 1,
|
|
35231
|
-
total: 1,
|
|
35232
|
-
message: null
|
|
35233
|
-
};
|
|
35234
|
-
}
|
|
35235
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
35236
|
-
const guess = userInput.coords;
|
|
35237
|
-
const correct = rubric.correct.coords;
|
|
35238
|
-
let match;
|
|
35239
|
-
if (rubric.correct.match === "congruent") {
|
|
35240
|
-
const angles = _.map([guess, correct], function (coords) {
|
|
35241
|
-
const angle = GraphUtils.findAngle(
|
|
35242
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35243
|
-
coords[2],
|
|
35244
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35245
|
-
coords[0],
|
|
35246
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35247
|
-
coords[1]);
|
|
35248
|
-
return (angle + 360) % 360;
|
|
35249
|
-
});
|
|
35250
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
35251
|
-
match = eq(...angles);
|
|
35252
|
-
} else {
|
|
35253
|
-
/* exact */
|
|
35254
|
-
match =
|
|
35255
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35256
|
-
deepEq$1(guess[1], correct[1]) &&
|
|
35257
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35258
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
35259
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35260
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
35261
|
-
}
|
|
35262
|
-
if (match) {
|
|
35263
|
-
return {
|
|
35264
|
-
type: "points",
|
|
35265
|
-
earned: 1,
|
|
35266
|
-
total: 1,
|
|
35267
|
-
message: null
|
|
35268
|
-
};
|
|
35269
|
-
}
|
|
35270
|
-
}
|
|
35271
|
-
}
|
|
35272
|
-
|
|
35273
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
35274
|
-
// actually just wrong
|
|
35275
|
-
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
35276
|
-
// We're where we started.
|
|
35277
|
-
return {
|
|
35278
|
-
type: "invalid",
|
|
35279
|
-
message: null
|
|
35280
|
-
};
|
|
35281
|
-
}
|
|
35282
|
-
return {
|
|
35283
|
-
type: "points",
|
|
35284
|
-
earned: 0,
|
|
35285
|
-
total: 1,
|
|
35286
|
-
message: null
|
|
35287
|
-
};
|
|
35419
|
+
static validate(userInput, rubric) {
|
|
35420
|
+
return interactiveGraphValidator(userInput, rubric);
|
|
35288
35421
|
}
|
|
35289
35422
|
static getUserInputFromProps(props) {
|
|
35290
35423
|
return props.graph;
|
|
@@ -36387,6 +36520,57 @@ const styles$h = StyleSheet.create({
|
|
|
36387
36520
|
}
|
|
36388
36521
|
});
|
|
36389
36522
|
|
|
36523
|
+
// Question state for marker as result of user selected answers.
|
|
36524
|
+
|
|
36525
|
+
function scoreMarker(marker) {
|
|
36526
|
+
const score = {
|
|
36527
|
+
hasAnswers: false,
|
|
36528
|
+
isCorrect: false
|
|
36529
|
+
};
|
|
36530
|
+
if (marker.selected && marker.selected.length > 0) {
|
|
36531
|
+
score.hasAnswers = true;
|
|
36532
|
+
}
|
|
36533
|
+
if (marker.answers.length > 0) {
|
|
36534
|
+
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
36535
|
+
// All correct answers are selected by the user.
|
|
36536
|
+
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
36537
|
+
}
|
|
36538
|
+
} else if (!marker.selected || marker.selected.length === 0) {
|
|
36539
|
+
// Correct as no answers should be selected by the user.
|
|
36540
|
+
score.isCorrect = true;
|
|
36541
|
+
}
|
|
36542
|
+
return score;
|
|
36543
|
+
}
|
|
36544
|
+
function labelImageValidator(state, rubric) {
|
|
36545
|
+
let numAnswered = 0;
|
|
36546
|
+
let numCorrect = 0;
|
|
36547
|
+
for (const marker of state.markers) {
|
|
36548
|
+
const score = scoreMarker(marker);
|
|
36549
|
+
if (score.hasAnswers) {
|
|
36550
|
+
numAnswered++;
|
|
36551
|
+
}
|
|
36552
|
+
if (score.isCorrect) {
|
|
36553
|
+
numCorrect++;
|
|
36554
|
+
}
|
|
36555
|
+
}
|
|
36556
|
+
|
|
36557
|
+
// We expect all question markers to be answered before grading.
|
|
36558
|
+
if (numAnswered !== state.markers.length) {
|
|
36559
|
+
return {
|
|
36560
|
+
type: "invalid",
|
|
36561
|
+
message: null
|
|
36562
|
+
};
|
|
36563
|
+
}
|
|
36564
|
+
return {
|
|
36565
|
+
type: "points",
|
|
36566
|
+
// Markers with no expected answers are graded as correct if user
|
|
36567
|
+
// makes no answer selection.
|
|
36568
|
+
earned: numCorrect === state.markers.length ? 1 : 0,
|
|
36569
|
+
total: 1,
|
|
36570
|
+
message: null
|
|
36571
|
+
};
|
|
36572
|
+
}
|
|
36573
|
+
|
|
36390
36574
|
const BringToFront = {
|
|
36391
36575
|
boxShadow: `0 8px 8px ${color.offBlack64}`,
|
|
36392
36576
|
zIndex: 1000
|
|
@@ -36645,57 +36829,11 @@ const styles$f = StyleSheet.create({
|
|
|
36645
36829
|
* No diagonals allowed.
|
|
36646
36830
|
*/
|
|
36647
36831
|
|
|
36832
|
+
// TODO: should this be using WidgetProps / PerseusLabelImageWidgetOptions?
|
|
36833
|
+
|
|
36648
36834
|
class LabelImage extends React.Component {
|
|
36649
|
-
static gradeMarker(marker) {
|
|
36650
|
-
const score = {
|
|
36651
|
-
hasAnswers: false,
|
|
36652
|
-
isCorrect: false
|
|
36653
|
-
};
|
|
36654
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
36655
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'hasAnswers' because it is a read-only property.
|
|
36656
|
-
score.hasAnswers = true;
|
|
36657
|
-
}
|
|
36658
|
-
if (marker.answers.length > 0) {
|
|
36659
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
36660
|
-
// All correct answers are selected by the user.
|
|
36661
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
|
|
36662
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
36663
|
-
}
|
|
36664
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
36665
|
-
// Correct as no answers should be selected by the user.
|
|
36666
|
-
// @ts-expect-error - TS2540 - Cannot assign to 'isCorrect' because it is a read-only property.
|
|
36667
|
-
score.isCorrect = true;
|
|
36668
|
-
}
|
|
36669
|
-
return score;
|
|
36670
|
-
}
|
|
36671
36835
|
static validate(state, rubric) {
|
|
36672
|
-
|
|
36673
|
-
let numCorrect = 0;
|
|
36674
|
-
for (const marker of state.markers) {
|
|
36675
|
-
const score = LabelImage.gradeMarker(marker);
|
|
36676
|
-
if (score.hasAnswers) {
|
|
36677
|
-
numAnswered++;
|
|
36678
|
-
}
|
|
36679
|
-
if (score.isCorrect) {
|
|
36680
|
-
numCorrect++;
|
|
36681
|
-
}
|
|
36682
|
-
}
|
|
36683
|
-
|
|
36684
|
-
// We expect all question markers to be answered before grading.
|
|
36685
|
-
if (numAnswered !== state.markers.length) {
|
|
36686
|
-
return {
|
|
36687
|
-
type: "invalid",
|
|
36688
|
-
message: null
|
|
36689
|
-
};
|
|
36690
|
-
}
|
|
36691
|
-
return {
|
|
36692
|
-
type: "points",
|
|
36693
|
-
// Markers with no expected answers are graded as correct if user
|
|
36694
|
-
// makes no answer selection.
|
|
36695
|
-
earned: numCorrect === state.markers.length ? 1 : 0,
|
|
36696
|
-
total: 1,
|
|
36697
|
-
message: null
|
|
36698
|
-
};
|
|
36836
|
+
return labelImageValidator(state);
|
|
36699
36837
|
}
|
|
36700
36838
|
|
|
36701
36839
|
/**
|
|
@@ -36893,7 +37031,7 @@ class LabelImage extends React.Component {
|
|
|
36893
37031
|
this._mounted = false;
|
|
36894
37032
|
}
|
|
36895
37033
|
simpleValidate(rubric) {
|
|
36896
|
-
return
|
|
37034
|
+
return labelImageValidator(this.getUserInput());
|
|
36897
37035
|
}
|
|
36898
37036
|
getUserInput() {
|
|
36899
37037
|
const {
|
|
@@ -36911,7 +37049,7 @@ class LabelImage extends React.Component {
|
|
|
36911
37049
|
onChange
|
|
36912
37050
|
} = this.props;
|
|
36913
37051
|
const updatedMarkers = markers.map(marker => {
|
|
36914
|
-
const score =
|
|
37052
|
+
const score = scoreMarker(marker);
|
|
36915
37053
|
return _extends({}, marker, {
|
|
36916
37054
|
// Reveal correctness state for markers with answers.
|
|
36917
37055
|
showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
|
|
@@ -37058,7 +37196,7 @@ class LabelImage extends React.Component {
|
|
|
37058
37196
|
bottom: "top"
|
|
37059
37197
|
}[markerPosition];
|
|
37060
37198
|
}
|
|
37061
|
-
const score =
|
|
37199
|
+
const score = scoreMarker(marker);
|
|
37062
37200
|
// Once the question is answered, show markers
|
|
37063
37201
|
// with correct answers, otherwise passthrough
|
|
37064
37202
|
// the correctness state.
|
|
@@ -37257,9 +37395,7 @@ const LabelImageWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) =>
|
|
|
37257
37395
|
// methods and instead adjust Peresus to provide these facilities through
|
|
37258
37396
|
// instance methods on our Renderers.
|
|
37259
37397
|
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type
|
|
37260
|
-
LabelImageWithDependencies.validate =
|
|
37261
|
-
// @ts-expect-error - TS2339 - Property 'gradeMarker' does not exist on type
|
|
37262
|
-
LabelImageWithDependencies.gradeMarker = LabelImage.gradeMarker;
|
|
37398
|
+
LabelImageWithDependencies.validate = labelImageValidator;
|
|
37263
37399
|
var LabelImage$1 = {
|
|
37264
37400
|
name: "label-image",
|
|
37265
37401
|
displayName: "Label Image",
|
|
@@ -37977,6 +38113,16 @@ const styles$d = StyleSheet.create({
|
|
|
37977
38113
|
}
|
|
37978
38114
|
});
|
|
37979
38115
|
|
|
38116
|
+
function matcherValidator(state, rubric) {
|
|
38117
|
+
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
38118
|
+
return {
|
|
38119
|
+
type: "points",
|
|
38120
|
+
earned: correct ? 1 : 0,
|
|
38121
|
+
total: 1,
|
|
38122
|
+
message: null
|
|
38123
|
+
};
|
|
38124
|
+
}
|
|
38125
|
+
|
|
37980
38126
|
const {
|
|
37981
38127
|
shuffle: shuffle$1,
|
|
37982
38128
|
seededRNG
|
|
@@ -38039,10 +38185,12 @@ class Matcher extends React.Component {
|
|
|
38039
38185
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
38040
38186
|
this.refs.right.moveOptionToIndex(option, index);
|
|
38041
38187
|
};
|
|
38042
|
-
|
|
38043
|
-
|
|
38044
|
-
|
|
38045
|
-
|
|
38188
|
+
}
|
|
38189
|
+
static validate(state, rubric) {
|
|
38190
|
+
return matcherValidator(state, rubric);
|
|
38191
|
+
}
|
|
38192
|
+
simpleValidate(rubric) {
|
|
38193
|
+
return matcherValidator(this.getUserInput(), rubric);
|
|
38046
38194
|
}
|
|
38047
38195
|
render() {
|
|
38048
38196
|
// To minimize layout shift, we display a spinner until our math
|
|
@@ -38149,17 +38297,6 @@ Matcher.defaultProps = {
|
|
|
38149
38297
|
onChange: function () {},
|
|
38150
38298
|
linterContext: linterContextDefault
|
|
38151
38299
|
};
|
|
38152
|
-
_.extend(Matcher, {
|
|
38153
|
-
validate: function (state, rubric) {
|
|
38154
|
-
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
38155
|
-
return {
|
|
38156
|
-
type: "points",
|
|
38157
|
-
earned: correct ? 1 : 0,
|
|
38158
|
-
total: 1,
|
|
38159
|
-
message: null
|
|
38160
|
-
};
|
|
38161
|
-
}
|
|
38162
|
-
});
|
|
38163
38300
|
const padding = 5;
|
|
38164
38301
|
const border = "1px solid #444";
|
|
38165
38302
|
const styles$c = StyleSheet.create({
|
|
@@ -38195,7 +38332,59 @@ var Matcher$1 = {
|
|
|
38195
38332
|
isLintable: true
|
|
38196
38333
|
};
|
|
38197
38334
|
|
|
38198
|
-
|
|
38335
|
+
function matrixValidator(state, rubric, strings) {
|
|
38336
|
+
const solution = rubric.answers;
|
|
38337
|
+
const supplied = state.answers;
|
|
38338
|
+
const solutionSize = getMatrixSize(solution);
|
|
38339
|
+
const suppliedSize = getMatrixSize(supplied);
|
|
38340
|
+
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
38341
|
+
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
38342
|
+
let message = null;
|
|
38343
|
+
let hasEmptyCell = false;
|
|
38344
|
+
let incorrect = false;
|
|
38345
|
+
_(suppliedSize[0]).times(row => {
|
|
38346
|
+
_(suppliedSize[1]).times(col => {
|
|
38347
|
+
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
38348
|
+
hasEmptyCell = true;
|
|
38349
|
+
}
|
|
38350
|
+
if (!incorrectSize) {
|
|
38351
|
+
const validator = createValidator(
|
|
38352
|
+
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
38353
|
+
solution[row][col], {
|
|
38354
|
+
simplify: true
|
|
38355
|
+
}, strings);
|
|
38356
|
+
const result = validator(supplied[row][col]);
|
|
38357
|
+
if (result.message) {
|
|
38358
|
+
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
38359
|
+
message = result.message;
|
|
38360
|
+
}
|
|
38361
|
+
if (!result.correct) {
|
|
38362
|
+
incorrect = true;
|
|
38363
|
+
}
|
|
38364
|
+
}
|
|
38365
|
+
});
|
|
38366
|
+
});
|
|
38367
|
+
if (hasEmptyCell) {
|
|
38368
|
+
return {
|
|
38369
|
+
type: "invalid",
|
|
38370
|
+
message: strings.fillAllCells
|
|
38371
|
+
};
|
|
38372
|
+
}
|
|
38373
|
+
if (incorrectSize) {
|
|
38374
|
+
return {
|
|
38375
|
+
type: "points",
|
|
38376
|
+
earned: 0,
|
|
38377
|
+
total: 1,
|
|
38378
|
+
message: null
|
|
38379
|
+
};
|
|
38380
|
+
}
|
|
38381
|
+
return {
|
|
38382
|
+
type: "points",
|
|
38383
|
+
earned: incorrect ? 0 : 1,
|
|
38384
|
+
total: 1,
|
|
38385
|
+
message: message
|
|
38386
|
+
};
|
|
38387
|
+
}
|
|
38199
38388
|
|
|
38200
38389
|
const {
|
|
38201
38390
|
assert: assert$2
|
|
@@ -38245,7 +38434,7 @@ const getRefForPath$1 = function getRefForPath(path) {
|
|
|
38245
38434
|
const column = getColumnFromPath$1(path);
|
|
38246
38435
|
return "answer" + row + "," + column;
|
|
38247
38436
|
};
|
|
38248
|
-
|
|
38437
|
+
function getMatrixSize(matrix) {
|
|
38249
38438
|
const matrixSize = [1, 1];
|
|
38250
38439
|
|
|
38251
38440
|
// We need to find the widest row and tallest column to get the correct
|
|
@@ -38267,7 +38456,7 @@ const getMatrixSize = function getMatrixSize(matrix) {
|
|
|
38267
38456
|
}
|
|
38268
38457
|
});
|
|
38269
38458
|
return matrixSize;
|
|
38270
|
-
}
|
|
38459
|
+
}
|
|
38271
38460
|
class Matrix extends React.Component {
|
|
38272
38461
|
constructor(...args) {
|
|
38273
38462
|
super(...args);
|
|
@@ -38406,70 +38595,22 @@ class Matrix extends React.Component {
|
|
|
38406
38595
|
}, cb);
|
|
38407
38596
|
this.props.trackInteraction();
|
|
38408
38597
|
};
|
|
38409
|
-
this.getUserInput = () => {
|
|
38410
|
-
return {
|
|
38411
|
-
answers: this.props.answers
|
|
38412
|
-
};
|
|
38413
|
-
};
|
|
38414
|
-
this.simpleValidate = rubric => {
|
|
38415
|
-
return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
|
|
38416
|
-
};
|
|
38417
38598
|
}
|
|
38418
38599
|
static validate(state, rubric, strings) {
|
|
38419
|
-
|
|
38420
|
-
const supplied = state.answers;
|
|
38421
|
-
const solutionSize = getMatrixSize(solution);
|
|
38422
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
38423
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
38424
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
38425
|
-
let message = null;
|
|
38426
|
-
let hasEmptyCell = false;
|
|
38427
|
-
let incorrect = false;
|
|
38428
|
-
_(suppliedSize[0]).times(row => {
|
|
38429
|
-
_(suppliedSize[1]).times(col => {
|
|
38430
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
38431
|
-
hasEmptyCell = true;
|
|
38432
|
-
}
|
|
38433
|
-
if (!incorrectSize) {
|
|
38434
|
-
const validator = createValidator(solution[row][col], {
|
|
38435
|
-
simplify: true
|
|
38436
|
-
}, strings);
|
|
38437
|
-
const result = validator(supplied[row][col]);
|
|
38438
|
-
if (result.message) {
|
|
38439
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
38440
|
-
message = result.message;
|
|
38441
|
-
}
|
|
38442
|
-
if (!result.correct) {
|
|
38443
|
-
incorrect = true;
|
|
38444
|
-
}
|
|
38445
|
-
}
|
|
38446
|
-
});
|
|
38447
|
-
});
|
|
38448
|
-
if (hasEmptyCell) {
|
|
38449
|
-
return {
|
|
38450
|
-
type: "invalid",
|
|
38451
|
-
message: strings.fillAllCells
|
|
38452
|
-
};
|
|
38453
|
-
}
|
|
38454
|
-
if (incorrectSize) {
|
|
38455
|
-
return {
|
|
38456
|
-
type: "points",
|
|
38457
|
-
earned: 0,
|
|
38458
|
-
total: 1,
|
|
38459
|
-
message: null
|
|
38460
|
-
};
|
|
38461
|
-
}
|
|
38462
|
-
return {
|
|
38463
|
-
type: "points",
|
|
38464
|
-
earned: incorrect ? 0 : 1,
|
|
38465
|
-
total: 1,
|
|
38466
|
-
message: message
|
|
38467
|
-
};
|
|
38600
|
+
return matrixValidator(state, rubric, strings);
|
|
38468
38601
|
}
|
|
38469
38602
|
componentDidMount() {
|
|
38470
38603
|
// Used in the `onBlur` and `onFocus` handlers
|
|
38471
38604
|
this.cursorPosition = [0, 0];
|
|
38472
38605
|
}
|
|
38606
|
+
getUserInput() {
|
|
38607
|
+
return {
|
|
38608
|
+
answers: this.props.answers
|
|
38609
|
+
};
|
|
38610
|
+
}
|
|
38611
|
+
simpleValidate(rubric) {
|
|
38612
|
+
return matrixValidator(this.getUserInput(), rubric, this.context.strings);
|
|
38613
|
+
}
|
|
38473
38614
|
render() {
|
|
38474
38615
|
// Set the input sizes through JS so we can control the size of the
|
|
38475
38616
|
// brackets. (If we set them in CSS we won't know values until the
|
|
@@ -39680,16 +39821,14 @@ class Molecule extends React.Component {
|
|
|
39680
39821
|
}
|
|
39681
39822
|
Molecule.contextType = PerseusI18nContext;
|
|
39682
39823
|
class MoleculeWidget extends React.Component {
|
|
39683
|
-
constructor(...args) {
|
|
39684
|
-
super(...args);
|
|
39685
|
-
this.getUserInput = () => {
|
|
39686
|
-
return [];
|
|
39687
|
-
};
|
|
39688
|
-
}
|
|
39689
39824
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
39690
39825
|
simpleValidate() {
|
|
39691
39826
|
return noopValidator();
|
|
39692
39827
|
}
|
|
39828
|
+
getUserInput() {
|
|
39829
|
+
return [];
|
|
39830
|
+
}
|
|
39831
|
+
|
|
39693
39832
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
39694
39833
|
validate() {
|
|
39695
39834
|
return noopValidator();
|
|
@@ -39712,6 +39851,45 @@ var Molecule$1 = {
|
|
|
39712
39851
|
widget: MoleculeWidget
|
|
39713
39852
|
};
|
|
39714
39853
|
|
|
39854
|
+
function numberLineValidator(state, rubric) {
|
|
39855
|
+
const range = rubric.range;
|
|
39856
|
+
const divisionRange = state.divisionRange;
|
|
39857
|
+
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
39858
|
+
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
39859
|
+
const correctRel = rubric.correctRel || "eq";
|
|
39860
|
+
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
39861
|
+
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
39862
|
+
|
|
39863
|
+
// TODO: I don't think isTickCrtl is a thing anymore
|
|
39864
|
+
if (state.isTickCrtl && outsideAllowedRange) {
|
|
39865
|
+
return {
|
|
39866
|
+
type: "invalid",
|
|
39867
|
+
message: "Number of divisions is outside the allowed range."
|
|
39868
|
+
};
|
|
39869
|
+
}
|
|
39870
|
+
if (correctPos && correctRel === state.rel) {
|
|
39871
|
+
return {
|
|
39872
|
+
type: "points",
|
|
39873
|
+
earned: 1,
|
|
39874
|
+
total: 1,
|
|
39875
|
+
message: null
|
|
39876
|
+
};
|
|
39877
|
+
}
|
|
39878
|
+
if (state.numLinePosition === start && state.rel === startRel) {
|
|
39879
|
+
// We're where we started.
|
|
39880
|
+
return {
|
|
39881
|
+
type: "invalid",
|
|
39882
|
+
message: null
|
|
39883
|
+
};
|
|
39884
|
+
}
|
|
39885
|
+
return {
|
|
39886
|
+
type: "points",
|
|
39887
|
+
earned: 0,
|
|
39888
|
+
total: 1,
|
|
39889
|
+
message: null
|
|
39890
|
+
};
|
|
39891
|
+
}
|
|
39892
|
+
|
|
39715
39893
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
39716
39894
|
const MovablePoint = Graphie.MovablePoint;
|
|
39717
39895
|
// @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
|
|
@@ -39837,6 +40015,9 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
39837
40015
|
});
|
|
39838
40016
|
return results;
|
|
39839
40017
|
});
|
|
40018
|
+
|
|
40019
|
+
// TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
|
|
40020
|
+
// should this one?
|
|
39840
40021
|
class NumberLine extends React.Component {
|
|
39841
40022
|
constructor(..._args) {
|
|
39842
40023
|
super(..._args);
|
|
@@ -40116,19 +40297,21 @@ class NumberLine extends React.Component {
|
|
|
40116
40297
|
arrows: "->"
|
|
40117
40298
|
});
|
|
40118
40299
|
};
|
|
40119
|
-
|
|
40120
|
-
|
|
40121
|
-
|
|
40122
|
-
|
|
40123
|
-
|
|
40124
|
-
|
|
40125
|
-
|
|
40126
|
-
|
|
40127
|
-
|
|
40128
|
-
|
|
40129
|
-
return NumberLine.validate(this.getUserInput(), rubric);
|
|
40300
|
+
}
|
|
40301
|
+
static validate(state, rubric) {
|
|
40302
|
+
return numberLineValidator(state, rubric);
|
|
40303
|
+
}
|
|
40304
|
+
getUserInput() {
|
|
40305
|
+
return {
|
|
40306
|
+
numLinePosition: this.props.numLinePosition,
|
|
40307
|
+
rel: this.props.isInequality ? this.props.rel : "eq",
|
|
40308
|
+
numDivisions: this.props.numDivisions,
|
|
40309
|
+
divisionRange: this.props.divisionRange
|
|
40130
40310
|
};
|
|
40131
40311
|
}
|
|
40312
|
+
simpleValidate(rubric) {
|
|
40313
|
+
return numberLineValidator(this.getUserInput(), rubric);
|
|
40314
|
+
}
|
|
40132
40315
|
render() {
|
|
40133
40316
|
const {
|
|
40134
40317
|
strings
|
|
@@ -40194,44 +40377,6 @@ NumberLine.defaultProps = {
|
|
|
40194
40377
|
rel: "ge",
|
|
40195
40378
|
apiOptions: ApiOptions.defaults
|
|
40196
40379
|
};
|
|
40197
|
-
_.extend(NumberLine, {
|
|
40198
|
-
validate: function (state, rubric) {
|
|
40199
|
-
const range = rubric.range;
|
|
40200
|
-
const divisionRange = state.divisionRange;
|
|
40201
|
-
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
40202
|
-
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
40203
|
-
const correctRel = rubric.correctRel || "eq";
|
|
40204
|
-
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
40205
|
-
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
40206
|
-
if (state.isTickCrtl && outsideAllowedRange) {
|
|
40207
|
-
return {
|
|
40208
|
-
type: "invalid",
|
|
40209
|
-
message: "Number of divisions is outside the allowed range."
|
|
40210
|
-
};
|
|
40211
|
-
}
|
|
40212
|
-
if (correctPos && correctRel === state.rel) {
|
|
40213
|
-
return {
|
|
40214
|
-
type: "points",
|
|
40215
|
-
earned: 1,
|
|
40216
|
-
total: 1,
|
|
40217
|
-
message: null
|
|
40218
|
-
};
|
|
40219
|
-
}
|
|
40220
|
-
if (state.numLinePosition === start && state.rel === startRel) {
|
|
40221
|
-
// We're where we started.
|
|
40222
|
-
return {
|
|
40223
|
-
type: "invalid",
|
|
40224
|
-
message: null
|
|
40225
|
-
};
|
|
40226
|
-
}
|
|
40227
|
-
return {
|
|
40228
|
-
type: "points",
|
|
40229
|
-
earned: 0,
|
|
40230
|
-
total: 1,
|
|
40231
|
-
message: null
|
|
40232
|
-
};
|
|
40233
|
-
}
|
|
40234
|
-
});
|
|
40235
40380
|
const numberLineTransform = editorProps => {
|
|
40236
40381
|
const props = _.pick(editorProps, ["range", "labelRange", "labelStyle", "labelTicks", "divisionRange", "snapDivisions", "isTickCtrl", "isInequality", "showTooltips"]);
|
|
40237
40382
|
const numLinePosition = editorProps.initialX != null ? editorProps.initialX : editorProps.range[0];
|
|
@@ -40459,8 +40604,8 @@ Card.defaultProps = {
|
|
|
40459
40604
|
animating: false,
|
|
40460
40605
|
linterContext: linterContextDefault
|
|
40461
40606
|
};
|
|
40462
|
-
const NORMAL = "normal"
|
|
40463
|
-
|
|
40607
|
+
const NORMAL = "normal";
|
|
40608
|
+
const HORIZONTAL = "horizontal";
|
|
40464
40609
|
class Orderer extends React.Component {
|
|
40465
40610
|
constructor(...args) {
|
|
40466
40611
|
super(...args);
|
|
@@ -40701,17 +40846,6 @@ class Orderer extends React.Component {
|
|
|
40701
40846
|
current: list
|
|
40702
40847
|
});
|
|
40703
40848
|
};
|
|
40704
|
-
this.getUserInput = () => {
|
|
40705
|
-
return {
|
|
40706
|
-
current: _.map(this.props.current, function (v) {
|
|
40707
|
-
return v.content;
|
|
40708
|
-
})
|
|
40709
|
-
};
|
|
40710
|
-
};
|
|
40711
|
-
this.simpleValidate = rubric => {
|
|
40712
|
-
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
|
|
40713
|
-
return Orderer.validate(this.getUserInput(), rubric);
|
|
40714
|
-
};
|
|
40715
40849
|
}
|
|
40716
40850
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
40717
40851
|
if (!_.isEqual(this.props.current, nextProps.current)) {
|
|
@@ -40720,6 +40854,17 @@ class Orderer extends React.Component {
|
|
|
40720
40854
|
});
|
|
40721
40855
|
}
|
|
40722
40856
|
}
|
|
40857
|
+
getUserInput() {
|
|
40858
|
+
return {
|
|
40859
|
+
current: _.map(this.props.current, function (v) {
|
|
40860
|
+
return v.content;
|
|
40861
|
+
})
|
|
40862
|
+
};
|
|
40863
|
+
}
|
|
40864
|
+
simpleValidate(rubric) {
|
|
40865
|
+
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Orderer'.
|
|
40866
|
+
return Orderer.validate(this.getUserInput(), rubric);
|
|
40867
|
+
}
|
|
40723
40868
|
render() {
|
|
40724
40869
|
// This is the card we are currently dragging
|
|
40725
40870
|
const dragging = this.state.dragging && /*#__PURE__*/React.createElement(Card
|
|
@@ -40833,14 +40978,14 @@ Orderer.defaultProps = {
|
|
|
40833
40978
|
linterContext: linterContextDefault
|
|
40834
40979
|
};
|
|
40835
40980
|
_.extend(Orderer, {
|
|
40836
|
-
validate: function (
|
|
40837
|
-
if (
|
|
40981
|
+
validate: function (userInput, rubric) {
|
|
40982
|
+
if (userInput.current.length === 0) {
|
|
40838
40983
|
return {
|
|
40839
40984
|
type: "invalid",
|
|
40840
40985
|
message: null
|
|
40841
40986
|
};
|
|
40842
40987
|
}
|
|
40843
|
-
const correct = _.isEqual(
|
|
40988
|
+
const correct = _.isEqual(userInput.current, _.pluck(rubric.correctOptions, "content"));
|
|
40844
40989
|
return {
|
|
40845
40990
|
type: "points",
|
|
40846
40991
|
earned: correct ? 1 : 0,
|
|
@@ -43039,10 +43184,6 @@ class PassageRefTarget extends React.Component {
|
|
|
43039
43184
|
constructor(..._args) {
|
|
43040
43185
|
super(..._args);
|
|
43041
43186
|
// TODO passage-ref-target isn't interactive; remove
|
|
43042
|
-
this.getUserInput = () => {
|
|
43043
|
-
return;
|
|
43044
|
-
};
|
|
43045
|
-
// TODO passage-ref-target isn't interactive; remove
|
|
43046
43187
|
this.change = (...args) => {
|
|
43047
43188
|
return change.apply(this, args);
|
|
43048
43189
|
};
|
|
@@ -43051,6 +43192,11 @@ class PassageRefTarget extends React.Component {
|
|
|
43051
43192
|
static validate() {
|
|
43052
43193
|
return noopValidator();
|
|
43053
43194
|
}
|
|
43195
|
+
|
|
43196
|
+
// TODO passage-ref-target isn't interactive; remove
|
|
43197
|
+
getUserInput() {
|
|
43198
|
+
return null;
|
|
43199
|
+
}
|
|
43054
43200
|
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43055
43201
|
simpleValidate() {
|
|
43056
43202
|
return noopValidator();
|
|
@@ -43095,9 +43241,6 @@ var PassageRefTarget$1 = {
|
|
|
43095
43241
|
* This is a PhET simulation widget. It is used for rendering simulations
|
|
43096
43242
|
* from https://phet.colorado.edu/.
|
|
43097
43243
|
*/
|
|
43098
|
-
|
|
43099
|
-
// For returning user input, but currently the PhET widget
|
|
43100
|
-
// does not support accessing user input
|
|
43101
43244
|
// This renders the PhET sim
|
|
43102
43245
|
class PhetSimulation extends React.Component {
|
|
43103
43246
|
constructor(props) {
|
|
@@ -43146,6 +43289,8 @@ class PhetSimulation extends React.Component {
|
|
|
43146
43289
|
await this.updateSimState(this.props.url);
|
|
43147
43290
|
}
|
|
43148
43291
|
}
|
|
43292
|
+
|
|
43293
|
+
// TODO (LEMS-2396): remove validation logic from widgets that don't validate
|
|
43149
43294
|
getUserInput() {
|
|
43150
43295
|
return null;
|
|
43151
43296
|
}
|
|
@@ -43297,10 +43442,25 @@ var PhetSimulation$1 = {
|
|
|
43297
43442
|
isLintable: true
|
|
43298
43443
|
};
|
|
43299
43444
|
|
|
43300
|
-
/* eslint-disable react/no-unsafe */
|
|
43301
43445
|
const {
|
|
43302
43446
|
deepEq
|
|
43303
43447
|
} = Util;
|
|
43448
|
+
function plotterValidator(userInput, rubric) {
|
|
43449
|
+
if (deepEq(userInput, rubric.starting)) {
|
|
43450
|
+
return {
|
|
43451
|
+
type: "invalid",
|
|
43452
|
+
message: null
|
|
43453
|
+
};
|
|
43454
|
+
}
|
|
43455
|
+
return {
|
|
43456
|
+
type: "points",
|
|
43457
|
+
earned: deepEq(userInput, rubric.correct) ? 1 : 0,
|
|
43458
|
+
total: 1,
|
|
43459
|
+
message: null
|
|
43460
|
+
};
|
|
43461
|
+
}
|
|
43462
|
+
|
|
43463
|
+
/* eslint-disable react/no-unsafe */
|
|
43304
43464
|
class Plotter extends React.Component {
|
|
43305
43465
|
constructor(..._args) {
|
|
43306
43466
|
super(..._args);
|
|
@@ -44031,13 +44191,9 @@ class Plotter extends React.Component {
|
|
|
44031
44191
|
});
|
|
44032
44192
|
});
|
|
44033
44193
|
};
|
|
44034
|
-
|
|
44035
|
-
|
|
44036
|
-
|
|
44037
|
-
this.simpleValidate = rubric => {
|
|
44038
|
-
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
|
|
44039
|
-
return Plotter.validate(this.getUserInput(), rubric);
|
|
44040
|
-
};
|
|
44194
|
+
}
|
|
44195
|
+
static validate(userInput, rubric) {
|
|
44196
|
+
return plotterValidator(userInput, rubric);
|
|
44041
44197
|
}
|
|
44042
44198
|
componentDidMount() {
|
|
44043
44199
|
this._isMounted = true;
|
|
@@ -44062,6 +44218,12 @@ class Plotter extends React.Component {
|
|
|
44062
44218
|
componentWillUnmount() {
|
|
44063
44219
|
this._isMounted = false;
|
|
44064
44220
|
}
|
|
44221
|
+
getUserInput() {
|
|
44222
|
+
return this.state.values;
|
|
44223
|
+
}
|
|
44224
|
+
simpleValidate(rubric) {
|
|
44225
|
+
return plotterValidator(this.getUserInput(), rubric);
|
|
44226
|
+
}
|
|
44065
44227
|
render() {
|
|
44066
44228
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
44067
44229
|
// make it that size so we don't have to add extra padding. The value
|
|
@@ -44079,6 +44241,8 @@ class Plotter extends React.Component {
|
|
|
44079
44241
|
});
|
|
44080
44242
|
}
|
|
44081
44243
|
}
|
|
44244
|
+
|
|
44245
|
+
// We don't need to change any of the original props for static mode
|
|
44082
44246
|
Plotter.contextType = PerseusI18nContext;
|
|
44083
44247
|
Plotter.defaultProps = {
|
|
44084
44248
|
type: "bar",
|
|
@@ -44093,24 +44257,6 @@ Plotter.defaultProps = {
|
|
|
44093
44257
|
plotDimensions: [380, 300],
|
|
44094
44258
|
labelInterval: 1
|
|
44095
44259
|
};
|
|
44096
|
-
_.extend(Plotter, {
|
|
44097
|
-
validate: function (guess, rubric) {
|
|
44098
|
-
if (deepEq(guess, rubric.starting)) {
|
|
44099
|
-
return {
|
|
44100
|
-
type: "invalid",
|
|
44101
|
-
message: null
|
|
44102
|
-
};
|
|
44103
|
-
}
|
|
44104
|
-
return {
|
|
44105
|
-
type: "points",
|
|
44106
|
-
earned: deepEq(guess, rubric.correct) ? 1 : 0,
|
|
44107
|
-
total: 1,
|
|
44108
|
-
message: null
|
|
44109
|
-
};
|
|
44110
|
-
}
|
|
44111
|
-
});
|
|
44112
|
-
|
|
44113
|
-
// We don't need to change any of the original props for static mode
|
|
44114
44260
|
const staticTransform = _.identity;
|
|
44115
44261
|
var Plotter$1 = {
|
|
44116
44262
|
name: "plotter",
|
|
@@ -44166,6 +44312,28 @@ var PythonProgram$1 = {
|
|
|
44166
44312
|
widget: PythonProgram
|
|
44167
44313
|
};
|
|
44168
44314
|
|
|
44315
|
+
function sorterValidator(userInput, rubric) {
|
|
44316
|
+
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
44317
|
+
// prevents the "Check" button from becoming active. We want the user
|
|
44318
|
+
// to make a change before trying to move forward. This makes an
|
|
44319
|
+
// assumption that the initial order isn't the correct order! However,
|
|
44320
|
+
// this should be rare if it happens, and interacting with the list
|
|
44321
|
+
// will enable the button, so they won't be locked out of progressing.
|
|
44322
|
+
if (!userInput.changed) {
|
|
44323
|
+
return {
|
|
44324
|
+
type: "invalid",
|
|
44325
|
+
message: null
|
|
44326
|
+
};
|
|
44327
|
+
}
|
|
44328
|
+
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
44329
|
+
return {
|
|
44330
|
+
type: "points",
|
|
44331
|
+
earned: correct ? 1 : 0,
|
|
44332
|
+
total: 1,
|
|
44333
|
+
message: null
|
|
44334
|
+
};
|
|
44335
|
+
}
|
|
44336
|
+
|
|
44169
44337
|
const {
|
|
44170
44338
|
shuffle
|
|
44171
44339
|
} = Util;
|
|
@@ -44195,42 +44363,14 @@ class Sorter extends React.Component {
|
|
|
44195
44363
|
}, 0);
|
|
44196
44364
|
});
|
|
44197
44365
|
};
|
|
44198
|
-
this.getUserInput = () => {
|
|
44199
|
-
// eslint-disable-next-line react/no-string-refs
|
|
44200
|
-
// @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
|
|
44201
|
-
return {
|
|
44202
|
-
options: this.refs.sortable.getOptions()
|
|
44203
|
-
};
|
|
44204
|
-
};
|
|
44205
44366
|
this.moveOptionToIndex = (option, index) => {
|
|
44206
44367
|
// eslint-disable-next-line react/no-string-refs
|
|
44207
44368
|
// @ts-expect-error - TS2339 - Property 'moveOptionToIndex' does not exist on type 'ReactInstance'.
|
|
44208
44369
|
this.refs.sortable.moveOptionToIndex(option, index);
|
|
44209
44370
|
};
|
|
44210
|
-
this.simpleValidate = rubric => {
|
|
44211
|
-
// If this widget hasn't been changed yet, we treat it as "empty" which
|
|
44212
|
-
// prevents the "Check" button from becoming active. We want the user
|
|
44213
|
-
// to make a change before trying to move forward. This makes an
|
|
44214
|
-
// assumption that the initial order isn't the correct order! However,
|
|
44215
|
-
// this should be rare if it happens, and interacting with the list
|
|
44216
|
-
// will enable the button, so they won't be locked out of progressing.
|
|
44217
|
-
if (!this.state.changed) {
|
|
44218
|
-
return {
|
|
44219
|
-
type: "invalid",
|
|
44220
|
-
message: null
|
|
44221
|
-
};
|
|
44222
|
-
}
|
|
44223
|
-
return Sorter.validate(this.getUserInput(), rubric);
|
|
44224
|
-
};
|
|
44225
44371
|
}
|
|
44226
|
-
static validate(
|
|
44227
|
-
|
|
44228
|
-
return {
|
|
44229
|
-
type: "points",
|
|
44230
|
-
earned: correct ? 1 : 0,
|
|
44231
|
-
total: 1,
|
|
44232
|
-
message: null
|
|
44233
|
-
};
|
|
44372
|
+
static validate(userInput, rubric) {
|
|
44373
|
+
return sorterValidator(userInput, rubric);
|
|
44234
44374
|
}
|
|
44235
44375
|
componentDidMount() {
|
|
44236
44376
|
this._isMounted = true;
|
|
@@ -44238,6 +44378,18 @@ class Sorter extends React.Component {
|
|
|
44238
44378
|
componentWillUnmount() {
|
|
44239
44379
|
this._isMounted = false;
|
|
44240
44380
|
}
|
|
44381
|
+
getUserInput() {
|
|
44382
|
+
// eslint-disable-next-line react/no-string-refs
|
|
44383
|
+
// @ts-expect-error - TS2339 - Property 'getOptions' does not exist on type 'ReactInstance'.
|
|
44384
|
+
const options = this.refs.sortable.getOptions();
|
|
44385
|
+
return {
|
|
44386
|
+
options,
|
|
44387
|
+
changed: this.state.changed
|
|
44388
|
+
};
|
|
44389
|
+
}
|
|
44390
|
+
simpleValidate(rubric) {
|
|
44391
|
+
return sorterValidator(this.getUserInput(), rubric);
|
|
44392
|
+
}
|
|
44241
44393
|
render() {
|
|
44242
44394
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
44243
44395
|
const {
|
|
@@ -44274,6 +44426,66 @@ var Sorter$1 = {
|
|
|
44274
44426
|
isLintable: true
|
|
44275
44427
|
};
|
|
44276
44428
|
|
|
44429
|
+
function tableValidator(state, rubric, strings) {
|
|
44430
|
+
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44431
|
+
return _.filter(table, function (row) {
|
|
44432
|
+
// Check if row has a cell that is nonempty
|
|
44433
|
+
return _.some(row, _.identity);
|
|
44434
|
+
});
|
|
44435
|
+
};
|
|
44436
|
+
const solution = filterNonEmpty(rubric.answers);
|
|
44437
|
+
const supplied = filterNonEmpty(state);
|
|
44438
|
+
const hasEmptyCell = _.some(supplied, function (row) {
|
|
44439
|
+
return _.some(row, function (cell) {
|
|
44440
|
+
return cell === "";
|
|
44441
|
+
});
|
|
44442
|
+
});
|
|
44443
|
+
if (hasEmptyCell || !supplied.length) {
|
|
44444
|
+
return {
|
|
44445
|
+
type: "invalid",
|
|
44446
|
+
message: null
|
|
44447
|
+
};
|
|
44448
|
+
}
|
|
44449
|
+
if (supplied.length !== solution.length) {
|
|
44450
|
+
return {
|
|
44451
|
+
type: "points",
|
|
44452
|
+
earned: 0,
|
|
44453
|
+
total: 1,
|
|
44454
|
+
message: null
|
|
44455
|
+
};
|
|
44456
|
+
}
|
|
44457
|
+
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44458
|
+
let message = null;
|
|
44459
|
+
const allCorrect = _.every(solution, function (rowSolution) {
|
|
44460
|
+
for (let i = 0; i < supplied.length; i++) {
|
|
44461
|
+
const rowSupplied = supplied[i];
|
|
44462
|
+
const correct = _.every(rowSupplied, function (cellSupplied, i) {
|
|
44463
|
+
const cellSolution = rowSolution[i];
|
|
44464
|
+
const validator = createValidator(cellSolution, {
|
|
44465
|
+
simplify: true
|
|
44466
|
+
}, strings);
|
|
44467
|
+
const result = validator(cellSupplied);
|
|
44468
|
+
if (result.message) {
|
|
44469
|
+
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
44470
|
+
message = result.message;
|
|
44471
|
+
}
|
|
44472
|
+
return result.correct;
|
|
44473
|
+
});
|
|
44474
|
+
if (correct) {
|
|
44475
|
+
supplied.splice(i, 1);
|
|
44476
|
+
return true;
|
|
44477
|
+
}
|
|
44478
|
+
}
|
|
44479
|
+
return false;
|
|
44480
|
+
});
|
|
44481
|
+
return {
|
|
44482
|
+
type: "points",
|
|
44483
|
+
earned: allCorrect ? 1 : 0,
|
|
44484
|
+
total: 1,
|
|
44485
|
+
message: message
|
|
44486
|
+
};
|
|
44487
|
+
}
|
|
44488
|
+
|
|
44277
44489
|
const {
|
|
44278
44490
|
assert
|
|
44279
44491
|
} = InteractiveUtil;
|
|
@@ -44310,9 +44522,6 @@ class Table extends React.Component {
|
|
|
44310
44522
|
this._getColumns = () => {
|
|
44311
44523
|
return this.props.answers[0].length;
|
|
44312
44524
|
};
|
|
44313
|
-
this.getUserInput = () => {
|
|
44314
|
-
return _.map(this.props.answers, _.clone);
|
|
44315
|
-
};
|
|
44316
44525
|
this.onValueChange = (row, column, eventOrValue) => {
|
|
44317
44526
|
const answers = _.map(this.props.answers, _.clone);
|
|
44318
44527
|
|
|
@@ -44332,10 +44541,6 @@ class Table extends React.Component {
|
|
|
44332
44541
|
headers: headers
|
|
44333
44542
|
});
|
|
44334
44543
|
};
|
|
44335
|
-
this.simpleValidate = rubric => {
|
|
44336
|
-
// @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
|
|
44337
|
-
return Table.validate(this.getUserInput(), rubric, this.context.strings);
|
|
44338
|
-
};
|
|
44339
44544
|
this._handleFocus = inputPath => {
|
|
44340
44545
|
this.props.onFocus(inputPath);
|
|
44341
44546
|
};
|
|
@@ -44402,6 +44607,15 @@ class Table extends React.Component {
|
|
|
44402
44607
|
}, cb);
|
|
44403
44608
|
};
|
|
44404
44609
|
}
|
|
44610
|
+
static validate(state, rubric, strings) {
|
|
44611
|
+
return tableValidator(state, rubric, strings);
|
|
44612
|
+
}
|
|
44613
|
+
getUserInput() {
|
|
44614
|
+
return _.map(this.props.answers, _.clone);
|
|
44615
|
+
}
|
|
44616
|
+
simpleValidate(rubric) {
|
|
44617
|
+
return tableValidator(this.getUserInput(), rubric, this.context.strings);
|
|
44618
|
+
}
|
|
44405
44619
|
render() {
|
|
44406
44620
|
const rows = this._getRows();
|
|
44407
44621
|
const columns = this._getColumns();
|
|
@@ -44479,68 +44693,6 @@ Table.defaultProps = function () {
|
|
|
44479
44693
|
linterContext: linterContextDefault
|
|
44480
44694
|
};
|
|
44481
44695
|
}();
|
|
44482
|
-
_.extend(Table, {
|
|
44483
|
-
validate: function (state, rubric, strings) {
|
|
44484
|
-
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44485
|
-
return _.filter(table, function (row) {
|
|
44486
|
-
// Check if row has a cell that is nonempty
|
|
44487
|
-
return _.some(row, _.identity);
|
|
44488
|
-
});
|
|
44489
|
-
};
|
|
44490
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44491
|
-
const supplied = filterNonEmpty(state);
|
|
44492
|
-
const hasEmptyCell = _.some(supplied, function (row) {
|
|
44493
|
-
return _.some(row, function (cell) {
|
|
44494
|
-
return cell === "";
|
|
44495
|
-
});
|
|
44496
|
-
});
|
|
44497
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44498
|
-
return {
|
|
44499
|
-
type: "invalid",
|
|
44500
|
-
message: null
|
|
44501
|
-
};
|
|
44502
|
-
}
|
|
44503
|
-
if (supplied.length !== solution.length) {
|
|
44504
|
-
return {
|
|
44505
|
-
type: "points",
|
|
44506
|
-
earned: 0,
|
|
44507
|
-
total: 1,
|
|
44508
|
-
message: null
|
|
44509
|
-
};
|
|
44510
|
-
}
|
|
44511
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44512
|
-
let message = null;
|
|
44513
|
-
const allCorrect = _.every(solution, function (rowSolution) {
|
|
44514
|
-
let i;
|
|
44515
|
-
for (i = 0; i < supplied.length; i++) {
|
|
44516
|
-
const rowSupplied = supplied[i];
|
|
44517
|
-
const correct = _.every(rowSupplied, function (cellSupplied, i) {
|
|
44518
|
-
const cellSolution = rowSolution[i];
|
|
44519
|
-
const validator = createValidator(cellSolution, {
|
|
44520
|
-
simplify: true
|
|
44521
|
-
}, strings);
|
|
44522
|
-
const result = validator(cellSupplied);
|
|
44523
|
-
if (result.message) {
|
|
44524
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
44525
|
-
message = result.message;
|
|
44526
|
-
}
|
|
44527
|
-
return result.correct;
|
|
44528
|
-
});
|
|
44529
|
-
if (correct) {
|
|
44530
|
-
supplied.splice(i, 1);
|
|
44531
|
-
return true;
|
|
44532
|
-
}
|
|
44533
|
-
}
|
|
44534
|
-
return false;
|
|
44535
|
-
});
|
|
44536
|
-
return {
|
|
44537
|
-
type: "points",
|
|
44538
|
-
earned: allCorrect ? 1 : 0,
|
|
44539
|
-
total: 1,
|
|
44540
|
-
message: message
|
|
44541
|
-
};
|
|
44542
|
-
}
|
|
44543
|
-
});
|
|
44544
44696
|
const propTransform = editorProps => {
|
|
44545
44697
|
// Remove answers before passing to widget
|
|
44546
44698
|
const rows = editorProps.answers.length;
|
|
@@ -44770,7 +44922,7 @@ const init = function init(options) {
|
|
|
44770
44922
|
|
|
44771
44923
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44772
44924
|
const libName = "@khanacademy/perseus";
|
|
44773
|
-
const libVersion = "
|
|
44925
|
+
const libVersion = "35.1.1";
|
|
44774
44926
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44775
44927
|
|
|
44776
44928
|
const apiVersion = {
|
|
@@ -45321,9 +45473,6 @@ const defaultContext = {
|
|
|
45321
45473
|
};
|
|
45322
45474
|
const context = /*#__PURE__*/React.createContext(defaultContext);
|
|
45323
45475
|
|
|
45324
|
-
const {
|
|
45325
|
-
mapObject
|
|
45326
|
-
} = objective_;
|
|
45327
45476
|
class ServerItemRenderer extends React.Component {
|
|
45328
45477
|
constructor(props) {
|
|
45329
45478
|
super(props);
|
|
@@ -45543,20 +45692,6 @@ class ServerItemRenderer extends React.Component {
|
|
|
45543
45692
|
return this.questionRenderer.getWidgetIds();
|
|
45544
45693
|
}
|
|
45545
45694
|
|
|
45546
|
-
/**
|
|
45547
|
-
* Returns an object mapping from widget ID to KE-style score.
|
|
45548
|
-
* The keys of this object are the values of the array returned
|
|
45549
|
-
* from `getWidgetIds`.
|
|
45550
|
-
*/
|
|
45551
|
-
scoreWidgets() {
|
|
45552
|
-
const qScore = this.questionRenderer.scoreWidgets();
|
|
45553
|
-
const qGuess = this.questionRenderer.getUserInputForWidgets();
|
|
45554
|
-
const state = this.questionRenderer.getSerializedState();
|
|
45555
|
-
return mapObject(qScore, (score, id) => {
|
|
45556
|
-
return Util.keScoreFromPerseusScore(score, qGuess[id], state);
|
|
45557
|
-
});
|
|
45558
|
-
}
|
|
45559
|
-
|
|
45560
45695
|
/**
|
|
45561
45696
|
* Get a representation of the current state of the item.
|
|
45562
45697
|
*/
|