@khanacademy/perseus 35.0.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 +756 -615
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +756 -613
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -0
- package/dist/perseus-types.d.ts +7 -0
- package/dist/server-item-renderer.d.ts +2 -0
- package/dist/types.d.ts +3 -4
- package/dist/validation.types.d.ts +5 -2
- package/dist/widgets/cs-program/cs-program-validator.d.ts +3 -0
- package/dist/widgets/cs-program/cs-program.d.ts +2 -2
- package/dist/widgets/expression/expression.d.ts +14 -2
- package/dist/widgets/grapher/grapher-validator.d.ts +4 -0
- package/dist/widgets/grapher/util.d.ts +0 -2
- package/dist/widgets/iframe/iframe-validator.d.ts +3 -0
- package/dist/widgets/iframe/iframe.d.ts +3 -2
- package/dist/widgets/interactive-graph.d.ts +3 -3
- 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 +7 -0
- 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/label-image/label-image.d.ts +4 -0
- package/dist/widgets/matcher/matcher-validator.d.ts +4 -0
- package/dist/widgets/matcher/matcher.d.ts +3 -2
- package/dist/widgets/matrix/matrix-validator.d.ts +5 -0
- package/dist/widgets/matrix/matrix.d.ts +1 -0
- package/dist/widgets/number-line/number-line-validator.d.ts +4 -0
- package/dist/widgets/number-line/number-line.d.ts +3 -2
- package/dist/widgets/plotter/plotter-validator.d.ts +4 -0
- package/dist/widgets/plotter/plotter.d.ts +1 -0
- package/dist/widgets/table/table-validator.d.ts +5 -0
- package/dist/widgets/table/table.d.ts +1 -0
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -3146,7 +3146,7 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
3146
3146
|
/**
|
|
3147
3147
|
* Approximate equality on numbers and primitives.
|
|
3148
3148
|
*/
|
|
3149
|
-
function eq$
|
|
3149
|
+
function eq$4(x, y) {
|
|
3150
3150
|
if (typeof x === "number" && typeof y === "number") {
|
|
3151
3151
|
return Math.abs(x - y) < 1e-9;
|
|
3152
3152
|
}
|
|
@@ -3173,7 +3173,7 @@ function deepEq$5(x, y) {
|
|
|
3173
3173
|
return false;
|
|
3174
3174
|
}
|
|
3175
3175
|
if (typeof x === "function" && typeof y === "function") {
|
|
3176
|
-
return eq$
|
|
3176
|
+
return eq$4(x, y);
|
|
3177
3177
|
}
|
|
3178
3178
|
if (typeof x === "function" || typeof y === "function") {
|
|
3179
3179
|
return false;
|
|
@@ -3190,7 +3190,7 @@ function deepEq$5(x, y) {
|
|
|
3190
3190
|
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
3191
3191
|
return false;
|
|
3192
3192
|
}
|
|
3193
|
-
return eq$
|
|
3193
|
+
return eq$4(x, y);
|
|
3194
3194
|
}
|
|
3195
3195
|
|
|
3196
3196
|
/**
|
|
@@ -3490,7 +3490,7 @@ const Util = {
|
|
|
3490
3490
|
gridStepFromTickStep,
|
|
3491
3491
|
tickStepFromNumTicks,
|
|
3492
3492
|
constrainedTickStepsFromTickSteps,
|
|
3493
|
-
eq: eq$
|
|
3493
|
+
eq: eq$4,
|
|
3494
3494
|
deepEq: deepEq$5,
|
|
3495
3495
|
parseQueryString,
|
|
3496
3496
|
updateQueryString: updateQueryString$2,
|
|
@@ -10813,7 +10813,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10813
10813
|
* A collection of geomtry-related utility functions
|
|
10814
10814
|
*/
|
|
10815
10815
|
const {
|
|
10816
|
-
eq: eq$
|
|
10816
|
+
eq: eq$3,
|
|
10817
10817
|
deepEq: deepEq$4
|
|
10818
10818
|
} = Util;
|
|
10819
10819
|
|
|
@@ -10823,7 +10823,7 @@ const {
|
|
|
10823
10823
|
|
|
10824
10824
|
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10825
10825
|
function sign(val) {
|
|
10826
|
-
if (eq$
|
|
10826
|
+
if (eq$3(val, 0)) {
|
|
10827
10827
|
return 0;
|
|
10828
10828
|
}
|
|
10829
10829
|
return val > 0 ? 1 : -1;
|
|
@@ -10835,7 +10835,7 @@ function ccw(a, b, c) {
|
|
|
10835
10835
|
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10836
10836
|
}
|
|
10837
10837
|
function collinear(a, b, c) {
|
|
10838
|
-
return eq$
|
|
10838
|
+
return eq$3(ccw(a, b, c), 0);
|
|
10839
10839
|
}
|
|
10840
10840
|
|
|
10841
10841
|
// Given rect bounding points A and B, whether point C is inside the rect
|
|
@@ -10980,7 +10980,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
10980
10980
|
return pair[0] / pair[1];
|
|
10981
10981
|
});
|
|
10982
10982
|
const same = _.all(factors, function (factor) {
|
|
10983
|
-
return eq$
|
|
10983
|
+
return eq$3(factors[0], factor);
|
|
10984
10984
|
});
|
|
10985
10985
|
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
10986
10986
|
return number.equal(pair[0], pair[1], tolerance);
|
|
@@ -11032,7 +11032,7 @@ function rotate(array, n) {
|
|
|
11032
11032
|
return array.slice(n).concat(array.slice(0, n));
|
|
11033
11033
|
}
|
|
11034
11034
|
function getLineEquation(first, second) {
|
|
11035
|
-
if (eq$
|
|
11035
|
+
if (eq$3(first[0], second[0])) {
|
|
11036
11036
|
return "x = " + first[0].toFixed(3);
|
|
11037
11037
|
}
|
|
11038
11038
|
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
@@ -24664,6 +24664,31 @@ const parseUrl = url => new URL(url, location().origin);
|
|
|
24664
24664
|
*/
|
|
24665
24665
|
const toAbsoluteUrl = url => parseUrl(url).href;
|
|
24666
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
|
+
|
|
24667
24692
|
/**
|
|
24668
24693
|
* This widget is for embedding Khan Academy CS programs.
|
|
24669
24694
|
*/
|
|
@@ -24714,29 +24739,8 @@ class CSProgram extends React.Component {
|
|
|
24714
24739
|
};
|
|
24715
24740
|
}
|
|
24716
24741
|
// The widget's grading function
|
|
24717
|
-
static validate(state
|
|
24718
|
-
|
|
24719
|
-
// and pass an optional message
|
|
24720
|
-
if (state.status === "correct") {
|
|
24721
|
-
return {
|
|
24722
|
-
type: "points",
|
|
24723
|
-
earned: 1,
|
|
24724
|
-
total: 1,
|
|
24725
|
-
message: state.message || null
|
|
24726
|
-
};
|
|
24727
|
-
}
|
|
24728
|
-
if (state.status === "incorrect") {
|
|
24729
|
-
return {
|
|
24730
|
-
type: "points",
|
|
24731
|
-
earned: 0,
|
|
24732
|
-
total: 1,
|
|
24733
|
-
message: state.message || null
|
|
24734
|
-
};
|
|
24735
|
-
}
|
|
24736
|
-
return {
|
|
24737
|
-
type: "invalid",
|
|
24738
|
-
message: "Keep going, you're not there yet!"
|
|
24739
|
-
};
|
|
24742
|
+
static validate(state) {
|
|
24743
|
+
return csProgramValidator(state);
|
|
24740
24744
|
}
|
|
24741
24745
|
componentDidMount() {
|
|
24742
24746
|
$(window).on("message", this.handleMessageEvent);
|
|
@@ -24744,11 +24748,11 @@ class CSProgram extends React.Component {
|
|
|
24744
24748
|
componentWillUnmount() {
|
|
24745
24749
|
$(window).off("message", this.handleMessageEvent);
|
|
24746
24750
|
}
|
|
24747
|
-
simpleValidate(
|
|
24748
|
-
return
|
|
24751
|
+
simpleValidate() {
|
|
24752
|
+
return csProgramValidator({
|
|
24749
24753
|
status: this.props.status,
|
|
24750
24754
|
message: this.props.message
|
|
24751
|
-
}
|
|
24755
|
+
});
|
|
24752
24756
|
}
|
|
24753
24757
|
render() {
|
|
24754
24758
|
if (!this.props.programID) {
|
|
@@ -26339,49 +26343,6 @@ function functionForType(
|
|
|
26339
26343
|
type) {
|
|
26340
26344
|
return functionTypeMapping[type];
|
|
26341
26345
|
}
|
|
26342
|
-
const validate$1 = (state, rubric) => {
|
|
26343
|
-
if (state.type !== rubric.correct.type) {
|
|
26344
|
-
return {
|
|
26345
|
-
type: "points",
|
|
26346
|
-
earned: 0,
|
|
26347
|
-
total: 1,
|
|
26348
|
-
message: null
|
|
26349
|
-
};
|
|
26350
|
-
}
|
|
26351
|
-
|
|
26352
|
-
// We haven't moved the coords
|
|
26353
|
-
if (state.coords == null) {
|
|
26354
|
-
return {
|
|
26355
|
-
type: "invalid",
|
|
26356
|
-
message: null
|
|
26357
|
-
};
|
|
26358
|
-
}
|
|
26359
|
-
|
|
26360
|
-
// Get new function handler for grading
|
|
26361
|
-
const grader = functionForType(state.type);
|
|
26362
|
-
const guessCoeffs = grader.getCoefficients(state.coords, state.asymptote);
|
|
26363
|
-
const correctCoeffs = grader.getCoefficients(rubric.correct.coords, rubric.correct.asymptote);
|
|
26364
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
26365
|
-
return {
|
|
26366
|
-
type: "invalid",
|
|
26367
|
-
message: null
|
|
26368
|
-
};
|
|
26369
|
-
}
|
|
26370
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
26371
|
-
return {
|
|
26372
|
-
type: "points",
|
|
26373
|
-
earned: 1,
|
|
26374
|
-
total: 1,
|
|
26375
|
-
message: null
|
|
26376
|
-
};
|
|
26377
|
-
}
|
|
26378
|
-
return {
|
|
26379
|
-
type: "points",
|
|
26380
|
-
earned: 0,
|
|
26381
|
-
total: 1,
|
|
26382
|
-
message: null
|
|
26383
|
-
};
|
|
26384
|
-
};
|
|
26385
26346
|
const getEquationString = props => {
|
|
26386
26347
|
const plot = props.plot;
|
|
26387
26348
|
if (plot.type && plot.coords) {
|
|
@@ -26499,7 +26460,6 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26499
26460
|
DEFAULT_BACKGROUND_IMAGE: DEFAULT_BACKGROUND_IMAGE,
|
|
26500
26461
|
allTypes: allTypes,
|
|
26501
26462
|
functionForType: functionForType,
|
|
26502
|
-
validate: validate$1,
|
|
26503
26463
|
getEquationString: getEquationString,
|
|
26504
26464
|
pointsFromNormalized: pointsFromNormalized,
|
|
26505
26465
|
maybePointsFromNormalized: maybePointsFromNormalized,
|
|
@@ -26510,6 +26470,52 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
26510
26470
|
typeToButton: typeToButton
|
|
26511
26471
|
});
|
|
26512
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
|
+
|
|
26513
26519
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
26514
26520
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
26515
26521
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -26795,7 +26801,7 @@ class Grapher extends React.Component {
|
|
|
26795
26801
|
};
|
|
26796
26802
|
}
|
|
26797
26803
|
static validate(state, rubric) {
|
|
26798
|
-
return
|
|
26804
|
+
return grapherValidator(state, rubric);
|
|
26799
26805
|
}
|
|
26800
26806
|
static getUserInputFromProps(props) {
|
|
26801
26807
|
return props.plot;
|
|
@@ -26816,7 +26822,7 @@ class Grapher extends React.Component {
|
|
|
26816
26822
|
return tickStep;
|
|
26817
26823
|
}
|
|
26818
26824
|
simpleValidate(rubric) {
|
|
26819
|
-
return
|
|
26825
|
+
return grapherValidator(this.getUserInput(), rubric);
|
|
26820
26826
|
}
|
|
26821
26827
|
getUserInput() {
|
|
26822
26828
|
return Grapher.getUserInputFromProps(this.props);
|
|
@@ -27057,6 +27063,31 @@ var Group$1 = {
|
|
|
27057
27063
|
isLintable: true
|
|
27058
27064
|
};
|
|
27059
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
|
+
|
|
27060
27091
|
/**
|
|
27061
27092
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
27062
27093
|
* then communicates its state via window.postMessage
|
|
@@ -27095,10 +27126,9 @@ class Iframe extends React.Component {
|
|
|
27095
27126
|
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
27096
27127
|
return change.apply(this, args);
|
|
27097
27128
|
};
|
|
27098
|
-
|
|
27099
|
-
|
|
27100
|
-
|
|
27101
|
-
};
|
|
27129
|
+
}
|
|
27130
|
+
static validate(state) {
|
|
27131
|
+
return iframeValidator(state);
|
|
27102
27132
|
}
|
|
27103
27133
|
componentDidMount() {
|
|
27104
27134
|
$(window).on("message", this.handleMessageEvent);
|
|
@@ -27112,6 +27142,9 @@ class Iframe extends React.Component {
|
|
|
27112
27142
|
message: this.props.message
|
|
27113
27143
|
};
|
|
27114
27144
|
}
|
|
27145
|
+
simpleValidate() {
|
|
27146
|
+
return iframeValidator(this.getUserInput());
|
|
27147
|
+
}
|
|
27115
27148
|
render() {
|
|
27116
27149
|
const style = {
|
|
27117
27150
|
width: String(this.props.width),
|
|
@@ -27173,10 +27206,6 @@ class Iframe extends React.Component {
|
|
|
27173
27206
|
});
|
|
27174
27207
|
}
|
|
27175
27208
|
}
|
|
27176
|
-
|
|
27177
|
-
/**
|
|
27178
|
-
* This is the widget's grading function
|
|
27179
|
-
*/
|
|
27180
27209
|
Iframe.defaultProps = {
|
|
27181
27210
|
status: "incomplete",
|
|
27182
27211
|
// optional message
|
|
@@ -27184,32 +27213,6 @@ Iframe.defaultProps = {
|
|
|
27184
27213
|
allowFullScreen: false,
|
|
27185
27214
|
allowTopNavigation: false
|
|
27186
27215
|
};
|
|
27187
|
-
_.extend(Iframe, {
|
|
27188
|
-
validate: function (state, rubric) {
|
|
27189
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
27190
|
-
// and pass an optional message
|
|
27191
|
-
if (state.status === "correct") {
|
|
27192
|
-
return {
|
|
27193
|
-
type: "points",
|
|
27194
|
-
earned: 1,
|
|
27195
|
-
total: 1,
|
|
27196
|
-
message: state.message || null
|
|
27197
|
-
};
|
|
27198
|
-
}
|
|
27199
|
-
if (state.status === "incorrect") {
|
|
27200
|
-
return {
|
|
27201
|
-
type: "points",
|
|
27202
|
-
earned: 0,
|
|
27203
|
-
total: 1,
|
|
27204
|
-
message: state.message || null
|
|
27205
|
-
};
|
|
27206
|
-
}
|
|
27207
|
-
return {
|
|
27208
|
-
type: "invalid",
|
|
27209
|
-
message: "Keep going, you're not there yet!"
|
|
27210
|
-
};
|
|
27211
|
-
}
|
|
27212
|
-
});
|
|
27213
27216
|
var Iframe$1 = {
|
|
27214
27217
|
name: "iframe",
|
|
27215
27218
|
displayName: "Iframe (deprecated)",
|
|
@@ -28716,7 +28719,8 @@ const tickSize = 10;
|
|
|
28716
28719
|
const tickLabelSize = 14;
|
|
28717
28720
|
const YGridTick = ({
|
|
28718
28721
|
y,
|
|
28719
|
-
range
|
|
28722
|
+
range,
|
|
28723
|
+
tickStep
|
|
28720
28724
|
}) => {
|
|
28721
28725
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
28722
28726
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -28751,9 +28755,10 @@ const YGridTick = ({
|
|
|
28751
28755
|
|
|
28752
28756
|
// If the graph displays both the y and x axis lines within the graph, we want
|
|
28753
28757
|
// to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
|
|
28754
|
-
const showLabel = shouldShowLabel(y, range);
|
|
28758
|
+
const showLabel = shouldShowLabel(y, range, tickStep);
|
|
28755
28759
|
return /*#__PURE__*/React.createElement("g", {
|
|
28756
|
-
className: "tick"
|
|
28760
|
+
className: "tick",
|
|
28761
|
+
"aria-hidden": true
|
|
28757
28762
|
}, /*#__PURE__*/React.createElement("line", {
|
|
28758
28763
|
x1: x1,
|
|
28759
28764
|
y1: y1,
|
|
@@ -28812,7 +28817,8 @@ const XGridTick = ({
|
|
|
28812
28817
|
const xPositionText = xPosition + xAdjustment;
|
|
28813
28818
|
const yPositionText = yPosition + yAdjustment;
|
|
28814
28819
|
return /*#__PURE__*/React.createElement("g", {
|
|
28815
|
-
className: "tick"
|
|
28820
|
+
className: "tick",
|
|
28821
|
+
"aria-hidden": true
|
|
28816
28822
|
}, /*#__PURE__*/React.createElement("line", {
|
|
28817
28823
|
x1: x1,
|
|
28818
28824
|
y1: y1,
|
|
@@ -28831,13 +28837,13 @@ const XGridTick = ({
|
|
|
28831
28837
|
};
|
|
28832
28838
|
|
|
28833
28839
|
// Determines whether to show the label for the given tick
|
|
28834
|
-
// Currently, the only condition is to hide the label at -
|
|
28835
|
-
// on the y-axis when the
|
|
28836
|
-
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) => {
|
|
28837
28843
|
let showLabel = true;
|
|
28838
28844
|
|
|
28839
|
-
// If the
|
|
28840
|
-
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) {
|
|
28841
28847
|
showLabel = false;
|
|
28842
28848
|
}
|
|
28843
28849
|
return showLabel;
|
|
@@ -28879,7 +28885,8 @@ const AxisTicks = () => {
|
|
|
28879
28885
|
return /*#__PURE__*/React.createElement(YGridTick, {
|
|
28880
28886
|
y: y,
|
|
28881
28887
|
key: `y-grid-tick-${y}`,
|
|
28882
|
-
range: range
|
|
28888
|
+
range: range,
|
|
28889
|
+
tickStep: tickStep[Y]
|
|
28883
28890
|
});
|
|
28884
28891
|
})), /*#__PURE__*/React.createElement("g", {
|
|
28885
28892
|
className: "x-axis-ticks"
|
|
@@ -29632,7 +29639,8 @@ function LockedLabel(props) {
|
|
|
29632
29639
|
color: lockedFigureColors[color],
|
|
29633
29640
|
fontSize: font.size[size],
|
|
29634
29641
|
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
29635
|
-
}
|
|
29642
|
+
},
|
|
29643
|
+
"aria-hidden": true
|
|
29636
29644
|
}, /*#__PURE__*/React.createElement(TeX, null, text));
|
|
29637
29645
|
}
|
|
29638
29646
|
|
|
@@ -29642,7 +29650,7 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29642
29650
|
lockedFigures
|
|
29643
29651
|
} = props;
|
|
29644
29652
|
return lockedFigures.map((figure, i) => {
|
|
29645
|
-
var _flags$mafs, _flags$mafs2, _flags$mafs3, _flags$mafs4, _flags$mafs5;
|
|
29653
|
+
var _flags$mafs, _flags$mafs2, _flags$mafs3, _flags$mafs4, _flags$mafs5, _flags$mafs6;
|
|
29646
29654
|
if (figure.type === "label") {
|
|
29647
29655
|
return /*#__PURE__*/React.createElement(LockedLabel, _extends({
|
|
29648
29656
|
key: `label-${i}`
|
|
@@ -29657,8 +29665,10 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29657
29665
|
flags != null && (_flags$mafs3 = flags["mafs"]) != null && _flags$mafs3["locked-vector-labels"] && figure.type === "vector" ||
|
|
29658
29666
|
// Ellipse flag + ellipse type
|
|
29659
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" ||
|
|
29660
29670
|
// Function flag + function type
|
|
29661
|
-
flags != null && (_flags$
|
|
29671
|
+
flags != null && (_flags$mafs6 = flags["mafs"]) != null && _flags$mafs6["locked-function-labels"] && figure.type === "function") {
|
|
29662
29672
|
var _figure$labels, _figure$points$0$labe, _figure$points$1$labe;
|
|
29663
29673
|
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
29664
29674
|
key: i
|
|
@@ -29675,15 +29685,23 @@ function GraphLockedLabelsLayer(props) {
|
|
|
29675
29685
|
}
|
|
29676
29686
|
|
|
29677
29687
|
const LockedEllipse = props => {
|
|
29688
|
+
var _flags$mafs;
|
|
29678
29689
|
const {
|
|
29679
29690
|
center,
|
|
29680
29691
|
radius,
|
|
29681
29692
|
angle,
|
|
29682
29693
|
color: color$1,
|
|
29683
29694
|
fillStyle,
|
|
29684
|
-
strokeStyle
|
|
29695
|
+
strokeStyle,
|
|
29696
|
+
ariaLabel,
|
|
29697
|
+
flags
|
|
29685
29698
|
} = props;
|
|
29686
|
-
|
|
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, {
|
|
29687
29705
|
center: center,
|
|
29688
29706
|
radius: radius,
|
|
29689
29707
|
angle: angle,
|
|
@@ -29699,10 +29717,11 @@ const LockedEllipse = props => {
|
|
|
29699
29717
|
fill: fillStyle === "white" ? color.white : lockedFigureColors[color$1]
|
|
29700
29718
|
}
|
|
29701
29719
|
}
|
|
29702
|
-
});
|
|
29720
|
+
}));
|
|
29703
29721
|
};
|
|
29704
29722
|
|
|
29705
29723
|
const LockedFunction = props => {
|
|
29724
|
+
var _props$flags;
|
|
29706
29725
|
const [equation, setEquation] = useState();
|
|
29707
29726
|
const {
|
|
29708
29727
|
color,
|
|
@@ -29715,6 +29734,7 @@ const LockedFunction = props => {
|
|
|
29715
29734
|
style: strokeStyle,
|
|
29716
29735
|
domain
|
|
29717
29736
|
};
|
|
29737
|
+
const hasAria = props.ariaLabel && ((_props$flags = props.flags) == null || (_props$flags = _props$flags["mafs"]) == null ? void 0 : _props$flags["locked-figures-aria"]);
|
|
29718
29738
|
useEffect(() => {
|
|
29719
29739
|
// Parsing the equation in a "useEffect" hook saves about 2ms each frame
|
|
29720
29740
|
// when the learner is interacting with the graph (i.e. moving points).
|
|
@@ -29724,7 +29744,9 @@ const LockedFunction = props => {
|
|
|
29724
29744
|
return null;
|
|
29725
29745
|
}
|
|
29726
29746
|
return /*#__PURE__*/React.createElement("g", {
|
|
29727
|
-
className: "locked-function"
|
|
29747
|
+
className: "locked-function",
|
|
29748
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
29749
|
+
"aria-hidden": !hasAria
|
|
29728
29750
|
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
|
|
29729
29751
|
y: x => equation.eval({
|
|
29730
29752
|
x
|
|
@@ -29817,6 +29839,7 @@ function isBetween(x, low, high) {
|
|
|
29817
29839
|
}
|
|
29818
29840
|
|
|
29819
29841
|
const LockedLine = props => {
|
|
29842
|
+
var _flags$mafs;
|
|
29820
29843
|
const {
|
|
29821
29844
|
color: color$1,
|
|
29822
29845
|
lineStyle,
|
|
@@ -29824,9 +29847,12 @@ const LockedLine = props => {
|
|
|
29824
29847
|
points,
|
|
29825
29848
|
showPoint1,
|
|
29826
29849
|
showPoint2,
|
|
29850
|
+
ariaLabel,
|
|
29851
|
+
flags,
|
|
29827
29852
|
range
|
|
29828
29853
|
} = props;
|
|
29829
29854
|
const [point1, point2] = points;
|
|
29855
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29830
29856
|
let line;
|
|
29831
29857
|
if (kind === "ray") {
|
|
29832
29858
|
// Rays extend to the end of the graph in one direction.
|
|
@@ -29866,7 +29892,9 @@ const LockedLine = props => {
|
|
|
29866
29892
|
}), endArrowHead);
|
|
29867
29893
|
}
|
|
29868
29894
|
return /*#__PURE__*/React.createElement("g", {
|
|
29869
|
-
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
|
|
29870
29898
|
}, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
|
|
29871
29899
|
x: point1.coord[X],
|
|
29872
29900
|
y: point1.coord[Y],
|
|
@@ -29891,13 +29919,21 @@ const LockedLine = props => {
|
|
|
29891
29919
|
};
|
|
29892
29920
|
|
|
29893
29921
|
const LockedPoint = props => {
|
|
29922
|
+
var _flags$mafs;
|
|
29894
29923
|
const {
|
|
29924
|
+
flags,
|
|
29895
29925
|
color: color$1,
|
|
29896
29926
|
coord,
|
|
29897
|
-
filled
|
|
29927
|
+
filled,
|
|
29928
|
+
ariaLabel
|
|
29898
29929
|
} = props;
|
|
29899
29930
|
const [x, y] = coord;
|
|
29900
|
-
|
|
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, {
|
|
29901
29937
|
x: x,
|
|
29902
29938
|
y: y,
|
|
29903
29939
|
svgCircleProps: {
|
|
@@ -29907,10 +29943,11 @@ const LockedPoint = props => {
|
|
|
29907
29943
|
strokeWidth: spacing.xxxxSmall_2
|
|
29908
29944
|
}
|
|
29909
29945
|
}
|
|
29910
|
-
});
|
|
29946
|
+
}));
|
|
29911
29947
|
};
|
|
29912
29948
|
|
|
29913
29949
|
const LockedPolygon = props => {
|
|
29950
|
+
var _props$flags;
|
|
29914
29951
|
const {
|
|
29915
29952
|
points,
|
|
29916
29953
|
color: color$1,
|
|
@@ -29918,8 +29955,11 @@ const LockedPolygon = props => {
|
|
|
29918
29955
|
fillStyle,
|
|
29919
29956
|
strokeStyle
|
|
29920
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"]);
|
|
29921
29959
|
return /*#__PURE__*/React.createElement("g", {
|
|
29922
|
-
className: "locked-polygon"
|
|
29960
|
+
className: "locked-polygon",
|
|
29961
|
+
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
29962
|
+
"aria-hidden": !hasAria
|
|
29923
29963
|
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
29924
29964
|
points: [...points],
|
|
29925
29965
|
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
@@ -29943,13 +29983,19 @@ const LockedPolygon = props => {
|
|
|
29943
29983
|
};
|
|
29944
29984
|
|
|
29945
29985
|
const LockedVector = props => {
|
|
29986
|
+
var _flags$mafs;
|
|
29946
29987
|
const {
|
|
29947
29988
|
color,
|
|
29948
|
-
points
|
|
29989
|
+
points,
|
|
29990
|
+
ariaLabel,
|
|
29991
|
+
flags
|
|
29949
29992
|
} = props;
|
|
29950
29993
|
const [tail, tip] = points;
|
|
29994
|
+
const hasAria = ariaLabel && (flags == null || (_flags$mafs = flags["mafs"]) == null ? void 0 : _flags$mafs["locked-figures-aria"]);
|
|
29951
29995
|
return /*#__PURE__*/React.createElement("g", {
|
|
29952
|
-
className: "locked-vector"
|
|
29996
|
+
className: "locked-vector",
|
|
29997
|
+
"aria-label": hasAria ? ariaLabel : undefined,
|
|
29998
|
+
"aria-hidden": !hasAria
|
|
29953
29999
|
}, /*#__PURE__*/React.createElement(Vector, {
|
|
29954
30000
|
tail: tail,
|
|
29955
30001
|
tip: tip,
|
|
@@ -29959,6 +30005,7 @@ const LockedVector = props => {
|
|
|
29959
30005
|
|
|
29960
30006
|
const GraphLockedLayer = props => {
|
|
29961
30007
|
const {
|
|
30008
|
+
flags,
|
|
29962
30009
|
lockedFigures
|
|
29963
30010
|
} = props;
|
|
29964
30011
|
return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
|
|
@@ -29966,28 +30013,40 @@ const GraphLockedLayer = props => {
|
|
|
29966
30013
|
case "point":
|
|
29967
30014
|
return /*#__PURE__*/React.createElement(LockedPoint, _extends({
|
|
29968
30015
|
key: `point-${index}`
|
|
29969
|
-
}, figure
|
|
30016
|
+
}, figure, {
|
|
30017
|
+
flags: flags
|
|
30018
|
+
}));
|
|
29970
30019
|
case "line":
|
|
29971
30020
|
return /*#__PURE__*/React.createElement(LockedLine, _extends({
|
|
29972
30021
|
key: `line-${index}`,
|
|
29973
30022
|
range: props.range
|
|
29974
|
-
}, figure
|
|
30023
|
+
}, figure, {
|
|
30024
|
+
flags: flags
|
|
30025
|
+
}));
|
|
29975
30026
|
case "vector":
|
|
29976
30027
|
return /*#__PURE__*/React.createElement(LockedVector, _extends({
|
|
29977
30028
|
key: `vector-${index}`
|
|
29978
|
-
}, figure
|
|
30029
|
+
}, figure, {
|
|
30030
|
+
flags: flags
|
|
30031
|
+
}));
|
|
29979
30032
|
case "ellipse":
|
|
29980
30033
|
return /*#__PURE__*/React.createElement(LockedEllipse, _extends({
|
|
29981
30034
|
key: `ellipse-${index}`
|
|
29982
|
-
}, figure
|
|
30035
|
+
}, figure, {
|
|
30036
|
+
flags: flags
|
|
30037
|
+
}));
|
|
29983
30038
|
case "polygon":
|
|
29984
30039
|
return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
|
|
29985
30040
|
key: `polygon-${index}`
|
|
29986
|
-
}, figure
|
|
30041
|
+
}, figure, {
|
|
30042
|
+
flags: flags
|
|
30043
|
+
}));
|
|
29987
30044
|
case "function":
|
|
29988
30045
|
return /*#__PURE__*/React.createElement(LockedFunction, _extends({
|
|
29989
30046
|
key: `function-${index}`
|
|
29990
|
-
}, figure
|
|
30047
|
+
}, figure, {
|
|
30048
|
+
flags: flags
|
|
30049
|
+
}));
|
|
29991
30050
|
case "label":
|
|
29992
30051
|
// This is rendered outside the SVG element, since
|
|
29993
30052
|
// TeX cannot be rendered inside an SVG.
|
|
@@ -31453,7 +31512,7 @@ function QuadraticGraph(props) {
|
|
|
31453
31512
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
31454
31513
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
31455
31514
|
const coeffRef = React.useRef([0, 0, 0]);
|
|
31456
|
-
const coeffs = getQuadraticCoefficients(coords);
|
|
31515
|
+
const coeffs = getQuadraticCoefficients$1(coords);
|
|
31457
31516
|
if (coeffs !== undefined) {
|
|
31458
31517
|
coeffRef.current = coeffs;
|
|
31459
31518
|
}
|
|
@@ -31477,7 +31536,7 @@ function QuadraticGraph(props) {
|
|
|
31477
31536
|
// These equations were originally set up in 2013 and may require some
|
|
31478
31537
|
// additional comments to help clarify the quadratic formula manipulations
|
|
31479
31538
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
31480
|
-
const getQuadraticCoefficients = coords => {
|
|
31539
|
+
const getQuadraticCoefficients$1 = coords => {
|
|
31481
31540
|
const p1 = coords[0];
|
|
31482
31541
|
const p2 = coords[1];
|
|
31483
31542
|
const p3 = coords[2];
|
|
@@ -31517,7 +31576,7 @@ function SinusoidGraph(props) {
|
|
|
31517
31576
|
phase: 1,
|
|
31518
31577
|
verticalOffset: 0
|
|
31519
31578
|
});
|
|
31520
|
-
const coeffs = getSinusoidCoefficients(coords);
|
|
31579
|
+
const coeffs = getSinusoidCoefficients$1(coords);
|
|
31521
31580
|
|
|
31522
31581
|
// If the coefficients are valid, update the reference
|
|
31523
31582
|
if (coeffs !== undefined) {
|
|
@@ -31546,7 +31605,7 @@ sinusoidCoefficients) {
|
|
|
31546
31605
|
} = sinusoidCoefficients;
|
|
31547
31606
|
return a * Math.sin(b * x - c) + d;
|
|
31548
31607
|
};
|
|
31549
|
-
const getSinusoidCoefficients = coords => {
|
|
31608
|
+
const getSinusoidCoefficients$1 = coords => {
|
|
31550
31609
|
// It's assumed that p1 is the root and p2 is the first peak
|
|
31551
31610
|
const p1 = coords[0];
|
|
31552
31611
|
const p2 = coords[1];
|
|
@@ -32007,6 +32066,7 @@ const MafsGraph = props => {
|
|
|
32007
32066
|
})),
|
|
32008
32067
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
32009
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,
|
|
32010
32070
|
lockedFigures: props.lockedFigures,
|
|
32011
32071
|
range: state.range
|
|
32012
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, {
|
|
@@ -32262,6 +32322,83 @@ const renderGraph = props => {
|
|
|
32262
32322
|
}
|
|
32263
32323
|
};
|
|
32264
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
|
+
|
|
32265
32402
|
function initializeGraphState(params) {
|
|
32266
32403
|
var _graph$snapTo;
|
|
32267
32404
|
const {
|
|
@@ -32887,7 +33024,7 @@ function doMovePoint(state, action) {
|
|
|
32887
33024
|
// Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
|
|
32888
33025
|
const boundDestination = boundAndSnapToGrid(action.destination, state);
|
|
32889
33026
|
_newCoords2[action.index] = boundDestination;
|
|
32890
|
-
const QuadraticCoefficients = getQuadraticCoefficients(_newCoords2);
|
|
33027
|
+
const QuadraticCoefficients = getQuadraticCoefficients$1(_newCoords2);
|
|
32891
33028
|
|
|
32892
33029
|
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
32893
33030
|
if (QuadraticCoefficients === undefined) {
|
|
@@ -33034,11 +33171,11 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
33034
33171
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
33035
33172
|
return [dx, dy];
|
|
33036
33173
|
};
|
|
33037
|
-
const eq$
|
|
33174
|
+
const eq$2 = Util.eq;
|
|
33038
33175
|
|
|
33039
33176
|
// Less than or approximately equal
|
|
33040
33177
|
function leq$1(a, b) {
|
|
33041
|
-
return a < b || eq$
|
|
33178
|
+
return a < b || eq$2(a, b);
|
|
33042
33179
|
}
|
|
33043
33180
|
function boundAndSnapToGrid(point, {
|
|
33044
33181
|
snapStep,
|
|
@@ -33341,20 +33478,6 @@ function setAtIndex(args) {
|
|
|
33341
33478
|
return copy;
|
|
33342
33479
|
}
|
|
33343
33480
|
|
|
33344
|
-
// Rather than be tightly bound to how data was structured in
|
|
33345
|
-
// the legacy interactive graph, this lets us store state
|
|
33346
|
-
// however we want and we just transform it before handing it off
|
|
33347
|
-
// the the parent InteractiveGraph
|
|
33348
|
-
function mafsStateToInteractiveGraph(state) {
|
|
33349
|
-
if (state.graph.type === "circle") {
|
|
33350
|
-
return _extends({}, state, {
|
|
33351
|
-
graph: _extends({}, state.graph, {
|
|
33352
|
-
radius: getRadius(state.graph)
|
|
33353
|
-
})
|
|
33354
|
-
});
|
|
33355
|
-
}
|
|
33356
|
-
return _extends({}, state);
|
|
33357
|
-
}
|
|
33358
33481
|
const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
33359
33482
|
const {
|
|
33360
33483
|
onChange,
|
|
@@ -33367,12 +33490,12 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33367
33490
|
const prevState = useRef(state);
|
|
33368
33491
|
useEffect(() => {
|
|
33369
33492
|
if (prevState.current !== state) {
|
|
33370
|
-
onChange(
|
|
33371
|
-
graph: state
|
|
33372
|
-
})
|
|
33493
|
+
onChange({
|
|
33494
|
+
graph: mafsStateToInteractiveGraph(state, graph)
|
|
33495
|
+
});
|
|
33373
33496
|
}
|
|
33374
33497
|
prevState.current = state;
|
|
33375
|
-
}, [onChange, state]);
|
|
33498
|
+
}, [onChange, state, graph]);
|
|
33376
33499
|
|
|
33377
33500
|
// Destructuring first to keep useEffect from making excess calls
|
|
33378
33501
|
const [xSnap, ySnap] = props.snapStep;
|
|
@@ -33424,12 +33547,234 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
33424
33547
|
}));
|
|
33425
33548
|
});
|
|
33426
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
|
+
|
|
33427
33773
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
33428
33774
|
const defaultBackgroundImage = {
|
|
33429
33775
|
url: null
|
|
33430
33776
|
};
|
|
33431
33777
|
const eq = Util.eq;
|
|
33432
|
-
const deepEq$1 = Util.deepEq;
|
|
33433
33778
|
const UNLIMITED = "unlimited";
|
|
33434
33779
|
|
|
33435
33780
|
// Sample background image:
|
|
@@ -33470,6 +33815,39 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
33470
33815
|
|
|
33471
33816
|
// There's no transform function in exports
|
|
33472
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
|
+
|
|
33473
33851
|
// (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
|
|
33474
33852
|
// to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
|
|
33475
33853
|
// for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
|
|
@@ -34553,7 +34931,6 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
34553
34931
|
$(this.angle).on("move", () => {
|
|
34554
34932
|
var _this$angle;
|
|
34555
34933
|
this.onChange({
|
|
34556
|
-
// @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'.
|
|
34557
34934
|
graph: _extends({}, graph, {
|
|
34558
34935
|
coords: (_this$angle = this.angle) == null ? void 0 : _this$angle.getClockwiseCoords()
|
|
34559
34936
|
})
|
|
@@ -34627,7 +35004,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
34627
35004
|
return InteractiveGraph.getUserInputFromProps(this.props);
|
|
34628
35005
|
}
|
|
34629
35006
|
simpleValidate(rubric) {
|
|
34630
|
-
return
|
|
35007
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
34631
35008
|
}
|
|
34632
35009
|
render() {
|
|
34633
35010
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
@@ -34706,7 +35083,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34706
35083
|
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
34707
35084
|
}
|
|
34708
35085
|
simpleValidate(rubric) {
|
|
34709
|
-
return
|
|
35086
|
+
return interactiveGraphValidator(this.getUserInput(), rubric);
|
|
34710
35087
|
}
|
|
34711
35088
|
render() {
|
|
34712
35089
|
var _this$props$apiOption;
|
|
@@ -34731,33 +35108,6 @@ class InteractiveGraph extends React.Component {
|
|
|
34731
35108
|
ref: this.legacyGraphRef
|
|
34732
35109
|
}, this.props));
|
|
34733
35110
|
}
|
|
34734
|
-
static getQuadraticCoefficients(coords) {
|
|
34735
|
-
const p1 = coords[0];
|
|
34736
|
-
const p2 = coords[1];
|
|
34737
|
-
const p3 = coords[2];
|
|
34738
|
-
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
34739
|
-
if (denom === 0) {
|
|
34740
|
-
// Many of the callers assume that the return value is always defined.
|
|
34741
|
-
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
|
|
34742
|
-
return;
|
|
34743
|
-
}
|
|
34744
|
-
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
34745
|
-
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;
|
|
34746
|
-
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;
|
|
34747
|
-
return [a, b, c];
|
|
34748
|
-
}
|
|
34749
|
-
static getSinusoidCoefficients(coords) {
|
|
34750
|
-
// It's assumed that p1 is the root and p2 is the first peak
|
|
34751
|
-
const p1 = coords[0];
|
|
34752
|
-
const p2 = coords[1];
|
|
34753
|
-
|
|
34754
|
-
// Resulting coefficients are canonical for this sine curve
|
|
34755
|
-
const amplitude = p2[1] - p1[1];
|
|
34756
|
-
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
34757
|
-
const phase = p1[0] * angularFrequency;
|
|
34758
|
-
const verticalOffset = p1[1];
|
|
34759
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
34760
|
-
}
|
|
34761
35111
|
|
|
34762
35112
|
/**
|
|
34763
35113
|
* @param {object} graph Like props.graph or props.correct
|
|
@@ -34971,7 +35321,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34971
35321
|
const coords =
|
|
34972
35322
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
34973
35323
|
props.graph.coords || InteractiveGraph.defaultQuadraticCoords(props);
|
|
34974
|
-
return
|
|
35324
|
+
return getQuadraticCoefficients(coords);
|
|
34975
35325
|
}
|
|
34976
35326
|
static defaultQuadraticCoords(props) {
|
|
34977
35327
|
const coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
|
|
@@ -34986,7 +35336,7 @@ class InteractiveGraph extends React.Component {
|
|
|
34986
35336
|
const coords =
|
|
34987
35337
|
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
34988
35338
|
props.graph.coords || InteractiveGraph.defaultSinusoidCoords(props);
|
|
34989
|
-
return
|
|
35339
|
+
return getSinusoidCoefficients(coords);
|
|
34990
35340
|
}
|
|
34991
35341
|
static defaultSinusoidCoords(props) {
|
|
34992
35342
|
const coords = [[0.5, 0.5], [0.65, 0.6]];
|
|
@@ -35066,222 +35416,8 @@ class InteractiveGraph extends React.Component {
|
|
|
35066
35416
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
35067
35417
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
35068
35418
|
}
|
|
35069
|
-
static validate(userInput, rubric
|
|
35070
|
-
|
|
35071
|
-
if (userInput.type === "none" && rubric.correct.type === "none") {
|
|
35072
|
-
return {
|
|
35073
|
-
type: "points",
|
|
35074
|
-
earned: 0,
|
|
35075
|
-
total: 0,
|
|
35076
|
-
message: null
|
|
35077
|
-
};
|
|
35078
|
-
}
|
|
35079
|
-
|
|
35080
|
-
// When nothing has moved, there will neither be coords nor the
|
|
35081
|
-
// circle's center/radius fields. When those fields are absent, skip
|
|
35082
|
-
// all these checks; just go mark the answer as empty.
|
|
35083
|
-
const hasValue = Boolean(
|
|
35084
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35085
|
-
userInput.coords ||
|
|
35086
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
35087
|
-
userInput.center && userInput.radius);
|
|
35088
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
35089
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
35090
|
-
const guess = userInput.coords;
|
|
35091
|
-
const correct = rubric.correct.coords;
|
|
35092
|
-
|
|
35093
|
-
// If both of the guess points are on the correct line, it's
|
|
35094
|
-
// correct.
|
|
35095
|
-
if (
|
|
35096
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35097
|
-
collinear(correct[0], correct[1], guess[0]) &&
|
|
35098
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35099
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35100
|
-
return {
|
|
35101
|
-
type: "points",
|
|
35102
|
-
earned: 1,
|
|
35103
|
-
total: 1,
|
|
35104
|
-
message: null
|
|
35105
|
-
};
|
|
35106
|
-
}
|
|
35107
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
35108
|
-
const guess = userInput.coords;
|
|
35109
|
-
const correct = rubric.correct.coords;
|
|
35110
|
-
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])) {
|
|
35111
|
-
return {
|
|
35112
|
-
type: "points",
|
|
35113
|
-
earned: 1,
|
|
35114
|
-
total: 1,
|
|
35115
|
-
message: null
|
|
35116
|
-
};
|
|
35117
|
-
}
|
|
35118
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
35119
|
-
// If the parabola coefficients match, it's correct.
|
|
35120
|
-
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
35121
|
-
const correctCoeffs = this.getQuadraticCoefficients(
|
|
35122
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35123
|
-
rubric.correct.coords);
|
|
35124
|
-
if (deepEq$1(guessCoeffs, correctCoeffs)) {
|
|
35125
|
-
return {
|
|
35126
|
-
type: "points",
|
|
35127
|
-
earned: 1,
|
|
35128
|
-
total: 1,
|
|
35129
|
-
message: null
|
|
35130
|
-
};
|
|
35131
|
-
}
|
|
35132
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
35133
|
-
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
35134
|
-
const correctCoeffs = this.getSinusoidCoefficients(
|
|
35135
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35136
|
-
rubric.correct.coords);
|
|
35137
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
35138
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
35139
|
-
// If the canonical coefficients match, it's correct.
|
|
35140
|
-
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
35141
|
-
return {
|
|
35142
|
-
type: "points",
|
|
35143
|
-
earned: 1,
|
|
35144
|
-
total: 1,
|
|
35145
|
-
message: null
|
|
35146
|
-
};
|
|
35147
|
-
}
|
|
35148
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
35149
|
-
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
35150
|
-
return {
|
|
35151
|
-
type: "points",
|
|
35152
|
-
earned: 1,
|
|
35153
|
-
total: 1,
|
|
35154
|
-
message: null
|
|
35155
|
-
};
|
|
35156
|
-
}
|
|
35157
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
35158
|
-
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
35159
|
-
const guess = userInput.coords.slice();
|
|
35160
|
-
correct = correct.slice();
|
|
35161
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
35162
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
35163
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
35164
|
-
// it and -0 converted to a string is "0"
|
|
35165
|
-
guess == null || guess.sort();
|
|
35166
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
35167
|
-
correct.sort();
|
|
35168
|
-
if (deepEq$1(guess, correct)) {
|
|
35169
|
-
return {
|
|
35170
|
-
type: "points",
|
|
35171
|
-
earned: 1,
|
|
35172
|
-
total: 1,
|
|
35173
|
-
message: null
|
|
35174
|
-
};
|
|
35175
|
-
}
|
|
35176
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
35177
|
-
var _userInput$coords, _rubric$correct$coord;
|
|
35178
|
-
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
35179
|
-
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
35180
|
-
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
35181
|
-
let match;
|
|
35182
|
-
if (rubric.correct.match === "similar") {
|
|
35183
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
35184
|
-
} else if (rubric.correct.match === "congruent") {
|
|
35185
|
-
match = similar(guess, correct, number.DEFAULT_TOLERANCE);
|
|
35186
|
-
} else if (rubric.correct.match === "approx") {
|
|
35187
|
-
match = similar(guess, correct, 0.1);
|
|
35188
|
-
} else {
|
|
35189
|
-
/* exact */
|
|
35190
|
-
guess.sort();
|
|
35191
|
-
correct.sort();
|
|
35192
|
-
match = deepEq$1(guess, correct);
|
|
35193
|
-
}
|
|
35194
|
-
if (match) {
|
|
35195
|
-
return {
|
|
35196
|
-
type: "points",
|
|
35197
|
-
earned: 1,
|
|
35198
|
-
total: 1,
|
|
35199
|
-
message: null
|
|
35200
|
-
};
|
|
35201
|
-
}
|
|
35202
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
35203
|
-
var _rubric$correct;
|
|
35204
|
-
let guess = Util.deepClone(userInput.coords);
|
|
35205
|
-
let correct = Util.deepClone((_rubric$correct = rubric.correct) == null ? void 0 : _rubric$correct.coords);
|
|
35206
|
-
guess = _.invoke(guess, "sort").sort();
|
|
35207
|
-
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
35208
|
-
correct = _.invoke(correct, "sort").sort();
|
|
35209
|
-
if (deepEq$1(guess, correct)) {
|
|
35210
|
-
return {
|
|
35211
|
-
type: "points",
|
|
35212
|
-
earned: 1,
|
|
35213
|
-
total: 1,
|
|
35214
|
-
message: null
|
|
35215
|
-
};
|
|
35216
|
-
}
|
|
35217
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
35218
|
-
const guess = userInput.coords;
|
|
35219
|
-
const correct = rubric.correct.coords;
|
|
35220
|
-
if (
|
|
35221
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35222
|
-
deepEq$1(guess[0], correct[0]) &&
|
|
35223
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35224
|
-
collinear(correct[0], correct[1], guess[1])) {
|
|
35225
|
-
return {
|
|
35226
|
-
type: "points",
|
|
35227
|
-
earned: 1,
|
|
35228
|
-
total: 1,
|
|
35229
|
-
message: null
|
|
35230
|
-
};
|
|
35231
|
-
}
|
|
35232
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
35233
|
-
const guess = userInput.coords;
|
|
35234
|
-
const correct = rubric.correct.coords;
|
|
35235
|
-
let match;
|
|
35236
|
-
if (rubric.correct.match === "congruent") {
|
|
35237
|
-
const angles = _.map([guess, correct], function (coords) {
|
|
35238
|
-
const angle = GraphUtils.findAngle(
|
|
35239
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35240
|
-
coords[2],
|
|
35241
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35242
|
-
coords[0],
|
|
35243
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
35244
|
-
coords[1]);
|
|
35245
|
-
return (angle + 360) % 360;
|
|
35246
|
-
});
|
|
35247
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
35248
|
-
match = eq(...angles);
|
|
35249
|
-
} else {
|
|
35250
|
-
/* exact */
|
|
35251
|
-
match =
|
|
35252
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35253
|
-
deepEq$1(guess[1], correct[1]) &&
|
|
35254
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35255
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
35256
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
35257
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
35258
|
-
}
|
|
35259
|
-
if (match) {
|
|
35260
|
-
return {
|
|
35261
|
-
type: "points",
|
|
35262
|
-
earned: 1,
|
|
35263
|
-
total: 1,
|
|
35264
|
-
message: null
|
|
35265
|
-
};
|
|
35266
|
-
}
|
|
35267
|
-
}
|
|
35268
|
-
}
|
|
35269
|
-
|
|
35270
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
35271
|
-
// actually just wrong
|
|
35272
|
-
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
35273
|
-
// We're where we started.
|
|
35274
|
-
return {
|
|
35275
|
-
type: "invalid",
|
|
35276
|
-
message: null
|
|
35277
|
-
};
|
|
35278
|
-
}
|
|
35279
|
-
return {
|
|
35280
|
-
type: "points",
|
|
35281
|
-
earned: 0,
|
|
35282
|
-
total: 1,
|
|
35283
|
-
message: null
|
|
35284
|
-
};
|
|
35419
|
+
static validate(userInput, rubric) {
|
|
35420
|
+
return interactiveGraphValidator(userInput, rubric);
|
|
35285
35421
|
}
|
|
35286
35422
|
static getUserInputFromProps(props) {
|
|
35287
35423
|
return props.graph;
|
|
@@ -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
|
|
@@ -38040,9 +38186,11 @@ class Matcher extends React.Component {
|
|
|
38040
38186
|
this.refs.right.moveOptionToIndex(option, index);
|
|
38041
38187
|
};
|
|
38042
38188
|
}
|
|
38189
|
+
static validate(state, rubric) {
|
|
38190
|
+
return matcherValidator(state, rubric);
|
|
38191
|
+
}
|
|
38043
38192
|
simpleValidate(rubric) {
|
|
38044
|
-
|
|
38045
|
-
return Matcher.validate(this.getUserInput(), 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);
|
|
@@ -38408,57 +38597,7 @@ class Matrix extends React.Component {
|
|
|
38408
38597
|
};
|
|
38409
38598
|
}
|
|
38410
38599
|
static validate(state, rubric, strings) {
|
|
38411
|
-
|
|
38412
|
-
const supplied = state.answers;
|
|
38413
|
-
const solutionSize = getMatrixSize(solution);
|
|
38414
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
38415
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
38416
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
38417
|
-
let message = null;
|
|
38418
|
-
let hasEmptyCell = false;
|
|
38419
|
-
let incorrect = false;
|
|
38420
|
-
_(suppliedSize[0]).times(row => {
|
|
38421
|
-
_(suppliedSize[1]).times(col => {
|
|
38422
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
38423
|
-
hasEmptyCell = true;
|
|
38424
|
-
}
|
|
38425
|
-
if (!incorrectSize) {
|
|
38426
|
-
const validator = createValidator(
|
|
38427
|
-
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
38428
|
-
solution[row][col], {
|
|
38429
|
-
simplify: true
|
|
38430
|
-
}, strings);
|
|
38431
|
-
const result = validator(supplied[row][col]);
|
|
38432
|
-
if (result.message) {
|
|
38433
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
38434
|
-
message = result.message;
|
|
38435
|
-
}
|
|
38436
|
-
if (!result.correct) {
|
|
38437
|
-
incorrect = true;
|
|
38438
|
-
}
|
|
38439
|
-
}
|
|
38440
|
-
});
|
|
38441
|
-
});
|
|
38442
|
-
if (hasEmptyCell) {
|
|
38443
|
-
return {
|
|
38444
|
-
type: "invalid",
|
|
38445
|
-
message: strings.fillAllCells
|
|
38446
|
-
};
|
|
38447
|
-
}
|
|
38448
|
-
if (incorrectSize) {
|
|
38449
|
-
return {
|
|
38450
|
-
type: "points",
|
|
38451
|
-
earned: 0,
|
|
38452
|
-
total: 1,
|
|
38453
|
-
message: null
|
|
38454
|
-
};
|
|
38455
|
-
}
|
|
38456
|
-
return {
|
|
38457
|
-
type: "points",
|
|
38458
|
-
earned: incorrect ? 0 : 1,
|
|
38459
|
-
total: 1,
|
|
38460
|
-
message: message
|
|
38461
|
-
};
|
|
38600
|
+
return matrixValidator(state, rubric, strings);
|
|
38462
38601
|
}
|
|
38463
38602
|
componentDidMount() {
|
|
38464
38603
|
// Used in the `onBlur` and `onFocus` handlers
|
|
@@ -38470,7 +38609,7 @@ class Matrix extends React.Component {
|
|
|
38470
38609
|
};
|
|
38471
38610
|
}
|
|
38472
38611
|
simpleValidate(rubric) {
|
|
38473
|
-
return
|
|
38612
|
+
return matrixValidator(this.getUserInput(), rubric, this.context.strings);
|
|
38474
38613
|
}
|
|
38475
38614
|
render() {
|
|
38476
38615
|
// Set the input sizes through JS so we can control the size of the
|
|
@@ -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'.
|
|
@@ -40120,6 +40298,9 @@ class NumberLine extends React.Component {
|
|
|
40120
40298
|
});
|
|
40121
40299
|
};
|
|
40122
40300
|
}
|
|
40301
|
+
static validate(state, rubric) {
|
|
40302
|
+
return numberLineValidator(state, rubric);
|
|
40303
|
+
}
|
|
40123
40304
|
getUserInput() {
|
|
40124
40305
|
return {
|
|
40125
40306
|
numLinePosition: this.props.numLinePosition,
|
|
@@ -40129,8 +40310,7 @@ class NumberLine extends React.Component {
|
|
|
40129
40310
|
};
|
|
40130
40311
|
}
|
|
40131
40312
|
simpleValidate(rubric) {
|
|
40132
|
-
|
|
40133
|
-
return NumberLine.validate(this.getUserInput(), rubric);
|
|
40313
|
+
return numberLineValidator(this.getUserInput(), rubric);
|
|
40134
40314
|
}
|
|
40135
40315
|
render() {
|
|
40136
40316
|
const {
|
|
@@ -40197,46 +40377,6 @@ NumberLine.defaultProps = {
|
|
|
40197
40377
|
rel: "ge",
|
|
40198
40378
|
apiOptions: ApiOptions.defaults
|
|
40199
40379
|
};
|
|
40200
|
-
_.extend(NumberLine, {
|
|
40201
|
-
validate: function (state, rubric) {
|
|
40202
|
-
const range = rubric.range;
|
|
40203
|
-
const divisionRange = state.divisionRange;
|
|
40204
|
-
const start = rubric.initialX != null ? rubric.initialX : range[0];
|
|
40205
|
-
const startRel = rubric.isInequality ? "ge" : "eq";
|
|
40206
|
-
const correctRel = rubric.correctRel || "eq";
|
|
40207
|
-
const correctPos = number.equal(state.numLinePosition, rubric.correctX || 0);
|
|
40208
|
-
const outsideAllowedRange = state.numDivisions > divisionRange[1] || state.numDivisions < divisionRange[0];
|
|
40209
|
-
|
|
40210
|
-
// TODO: I don't think isTickCrtl is a thing anymore
|
|
40211
|
-
if (state.isTickCrtl && outsideAllowedRange) {
|
|
40212
|
-
return {
|
|
40213
|
-
type: "invalid",
|
|
40214
|
-
message: "Number of divisions is outside the allowed range."
|
|
40215
|
-
};
|
|
40216
|
-
}
|
|
40217
|
-
if (correctPos && correctRel === state.rel) {
|
|
40218
|
-
return {
|
|
40219
|
-
type: "points",
|
|
40220
|
-
earned: 1,
|
|
40221
|
-
total: 1,
|
|
40222
|
-
message: null
|
|
40223
|
-
};
|
|
40224
|
-
}
|
|
40225
|
-
if (state.numLinePosition === start && state.rel === startRel) {
|
|
40226
|
-
// We're where we started.
|
|
40227
|
-
return {
|
|
40228
|
-
type: "invalid",
|
|
40229
|
-
message: null
|
|
40230
|
-
};
|
|
40231
|
-
}
|
|
40232
|
-
return {
|
|
40233
|
-
type: "points",
|
|
40234
|
-
earned: 0,
|
|
40235
|
-
total: 1,
|
|
40236
|
-
message: null
|
|
40237
|
-
};
|
|
40238
|
-
}
|
|
40239
|
-
});
|
|
40240
40380
|
const numberLineTransform = editorProps => {
|
|
40241
40381
|
const props = _.pick(editorProps, ["range", "labelRange", "labelStyle", "labelTicks", "divisionRange", "snapDivisions", "isTickCtrl", "isInequality", "showTooltips"]);
|
|
40242
40382
|
const numLinePosition = editorProps.initialX != null ? editorProps.initialX : editorProps.range[0];
|
|
@@ -43302,10 +43442,25 @@ var PhetSimulation$1 = {
|
|
|
43302
43442
|
isLintable: true
|
|
43303
43443
|
};
|
|
43304
43444
|
|
|
43305
|
-
/* eslint-disable react/no-unsafe */
|
|
43306
43445
|
const {
|
|
43307
43446
|
deepEq
|
|
43308
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 */
|
|
43309
43464
|
class Plotter extends React.Component {
|
|
43310
43465
|
constructor(..._args) {
|
|
43311
43466
|
super(..._args);
|
|
@@ -44037,6 +44192,9 @@ class Plotter extends React.Component {
|
|
|
44037
44192
|
});
|
|
44038
44193
|
};
|
|
44039
44194
|
}
|
|
44195
|
+
static validate(userInput, rubric) {
|
|
44196
|
+
return plotterValidator(userInput, rubric);
|
|
44197
|
+
}
|
|
44040
44198
|
componentDidMount() {
|
|
44041
44199
|
this._isMounted = true;
|
|
44042
44200
|
this.setupGraphie(this.state);
|
|
@@ -44064,8 +44222,7 @@ class Plotter extends React.Component {
|
|
|
44064
44222
|
return this.state.values;
|
|
44065
44223
|
}
|
|
44066
44224
|
simpleValidate(rubric) {
|
|
44067
|
-
|
|
44068
|
-
return Plotter.validate(this.getUserInput(), rubric);
|
|
44225
|
+
return plotterValidator(this.getUserInput(), rubric);
|
|
44069
44226
|
}
|
|
44070
44227
|
render() {
|
|
44071
44228
|
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
@@ -44084,6 +44241,8 @@ class Plotter extends React.Component {
|
|
|
44084
44241
|
});
|
|
44085
44242
|
}
|
|
44086
44243
|
}
|
|
44244
|
+
|
|
44245
|
+
// We don't need to change any of the original props for static mode
|
|
44087
44246
|
Plotter.contextType = PerseusI18nContext;
|
|
44088
44247
|
Plotter.defaultProps = {
|
|
44089
44248
|
type: "bar",
|
|
@@ -44098,24 +44257,6 @@ Plotter.defaultProps = {
|
|
|
44098
44257
|
plotDimensions: [380, 300],
|
|
44099
44258
|
labelInterval: 1
|
|
44100
44259
|
};
|
|
44101
|
-
_.extend(Plotter, {
|
|
44102
|
-
validate: function (userInput, rubric) {
|
|
44103
|
-
if (deepEq(userInput, rubric.starting)) {
|
|
44104
|
-
return {
|
|
44105
|
-
type: "invalid",
|
|
44106
|
-
message: null
|
|
44107
|
-
};
|
|
44108
|
-
}
|
|
44109
|
-
return {
|
|
44110
|
-
type: "points",
|
|
44111
|
-
earned: deepEq(userInput, rubric.correct) ? 1 : 0,
|
|
44112
|
-
total: 1,
|
|
44113
|
-
message: null
|
|
44114
|
-
};
|
|
44115
|
-
}
|
|
44116
|
-
});
|
|
44117
|
-
|
|
44118
|
-
// We don't need to change any of the original props for static mode
|
|
44119
44260
|
const staticTransform = _.identity;
|
|
44120
44261
|
var Plotter$1 = {
|
|
44121
44262
|
name: "plotter",
|
|
@@ -44285,6 +44426,66 @@ var Sorter$1 = {
|
|
|
44285
44426
|
isLintable: true
|
|
44286
44427
|
};
|
|
44287
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
|
+
|
|
44288
44489
|
const {
|
|
44289
44490
|
assert
|
|
44290
44491
|
} = InteractiveUtil;
|
|
@@ -44406,12 +44607,14 @@ class Table extends React.Component {
|
|
|
44406
44607
|
}, cb);
|
|
44407
44608
|
};
|
|
44408
44609
|
}
|
|
44610
|
+
static validate(state, rubric, strings) {
|
|
44611
|
+
return tableValidator(state, rubric, strings);
|
|
44612
|
+
}
|
|
44409
44613
|
getUserInput() {
|
|
44410
44614
|
return _.map(this.props.answers, _.clone);
|
|
44411
44615
|
}
|
|
44412
44616
|
simpleValidate(rubric) {
|
|
44413
|
-
|
|
44414
|
-
return Table.validate(this.getUserInput(), rubric, this.context.strings);
|
|
44617
|
+
return tableValidator(this.getUserInput(), rubric, this.context.strings);
|
|
44415
44618
|
}
|
|
44416
44619
|
render() {
|
|
44417
44620
|
const rows = this._getRows();
|
|
@@ -44490,68 +44693,6 @@ Table.defaultProps = function () {
|
|
|
44490
44693
|
linterContext: linterContextDefault
|
|
44491
44694
|
};
|
|
44492
44695
|
}();
|
|
44493
|
-
_.extend(Table, {
|
|
44494
|
-
validate: function (state, rubric, strings) {
|
|
44495
|
-
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44496
|
-
return _.filter(table, function (row) {
|
|
44497
|
-
// Check if row has a cell that is nonempty
|
|
44498
|
-
return _.some(row, _.identity);
|
|
44499
|
-
});
|
|
44500
|
-
};
|
|
44501
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44502
|
-
const supplied = filterNonEmpty(state);
|
|
44503
|
-
const hasEmptyCell = _.some(supplied, function (row) {
|
|
44504
|
-
return _.some(row, function (cell) {
|
|
44505
|
-
return cell === "";
|
|
44506
|
-
});
|
|
44507
|
-
});
|
|
44508
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44509
|
-
return {
|
|
44510
|
-
type: "invalid",
|
|
44511
|
-
message: null
|
|
44512
|
-
};
|
|
44513
|
-
}
|
|
44514
|
-
if (supplied.length !== solution.length) {
|
|
44515
|
-
return {
|
|
44516
|
-
type: "points",
|
|
44517
|
-
earned: 0,
|
|
44518
|
-
total: 1,
|
|
44519
|
-
message: null
|
|
44520
|
-
};
|
|
44521
|
-
}
|
|
44522
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44523
|
-
let message = null;
|
|
44524
|
-
const allCorrect = _.every(solution, function (rowSolution) {
|
|
44525
|
-
let i;
|
|
44526
|
-
for (i = 0; i < supplied.length; i++) {
|
|
44527
|
-
const rowSupplied = supplied[i];
|
|
44528
|
-
const correct = _.every(rowSupplied, function (cellSupplied, i) {
|
|
44529
|
-
const cellSolution = rowSolution[i];
|
|
44530
|
-
const validator = createValidator(cellSolution, {
|
|
44531
|
-
simplify: true
|
|
44532
|
-
}, strings);
|
|
44533
|
-
const result = validator(cellSupplied);
|
|
44534
|
-
if (result.message) {
|
|
44535
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
44536
|
-
message = result.message;
|
|
44537
|
-
}
|
|
44538
|
-
return result.correct;
|
|
44539
|
-
});
|
|
44540
|
-
if (correct) {
|
|
44541
|
-
supplied.splice(i, 1);
|
|
44542
|
-
return true;
|
|
44543
|
-
}
|
|
44544
|
-
}
|
|
44545
|
-
return false;
|
|
44546
|
-
});
|
|
44547
|
-
return {
|
|
44548
|
-
type: "points",
|
|
44549
|
-
earned: allCorrect ? 1 : 0,
|
|
44550
|
-
total: 1,
|
|
44551
|
-
message: message
|
|
44552
|
-
};
|
|
44553
|
-
}
|
|
44554
|
-
});
|
|
44555
44696
|
const propTransform = editorProps => {
|
|
44556
44697
|
// Remove answers before passing to widget
|
|
44557
44698
|
const rows = editorProps.answers.length;
|
|
@@ -44781,7 +44922,7 @@ const init = function init(options) {
|
|
|
44781
44922
|
|
|
44782
44923
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44783
44924
|
const libName = "@khanacademy/perseus";
|
|
44784
|
-
const libVersion = "35.
|
|
44925
|
+
const libVersion = "35.1.1";
|
|
44785
44926
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44786
44927
|
|
|
44787
44928
|
const apiVersion = {
|