@khanacademy/perseus 52.0.1 → 53.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/number-input.d.ts +2 -0
- package/dist/components/range-input.d.ts +1 -0
- package/dist/components/svg-image.d.ts +2 -2
- package/dist/es/index.css +0 -30
- package/dist/es/index.js +893 -1042
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +42 -6
- package/dist/es/strings.js.map +1 -1
- package/dist/index.css +0 -30
- package/dist/index.d.ts +1 -2
- package/dist/index.js +913 -2263
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +0 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/server-item-renderer.d.ts +0 -3
- package/dist/strings.d.ts +36 -0
- package/dist/strings.js +60 -6
- package/dist/strings.js.map +1 -1
- package/dist/styles/constants.d.ts +0 -2
- package/dist/types.d.ts +3 -32
- package/dist/util/math-utils.d.ts +1 -0
- package/dist/util/scoring.d.ts +0 -15
- package/dist/util/test-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
- package/dist/widget-container.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +0 -3
- package/dist/widgets/cs-program/cs-program.d.ts +2 -3
- package/dist/widgets/definition/definition.d.ts +0 -2
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +0 -1
- package/dist/widgets/dropdown/dropdown.d.ts +2 -4
- package/dist/widgets/explanation/explanation.d.ts +0 -2
- package/dist/widgets/expression/expression.d.ts +1 -11
- package/dist/widgets/grapher/grapher.d.ts +2 -2
- package/dist/widgets/group/group.d.ts +0 -4
- package/dist/widgets/iframe/iframe.d.ts +2 -3
- package/dist/widgets/image/image.d.ts +0 -3
- package/dist/widgets/input-number/input-number.d.ts +0 -3
- package/dist/widgets/interaction/interaction.d.ts +0 -1
- package/dist/widgets/interactive-graph.d.ts +2 -3
- package/dist/widgets/interactive-graphs/backgrounds/axis-ticks.d.ts +1 -0
- package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +3 -2
- package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +16 -1
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +0 -2
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +0 -2
- package/dist/widgets/label-image/label-image.d.ts +2 -2
- package/dist/widgets/matcher/matcher.d.ts +0 -2
- package/dist/widgets/matrix/matrix.d.ts +2 -4
- package/dist/widgets/measurer/measurer.d.ts +0 -1
- package/dist/widgets/mock-widgets/mock-widget-types.d.ts +0 -6
- package/dist/widgets/mock-widgets/mock-widget.d.ts +2 -5
- package/dist/widgets/molecule/molecule.d.ts +0 -1
- package/dist/widgets/number-line/number-line.d.ts +2 -3
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -3
- package/dist/widgets/orderer/orderer.d.ts +0 -3
- package/dist/widgets/passage/passage.d.ts +0 -1
- package/dist/widgets/passage-ref/passage-ref.d.ts +0 -2
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -2
- package/dist/widgets/plotter/plotter.d.ts +2 -3
- package/dist/widgets/radio/radio.d.ts +2 -4
- package/dist/widgets/sorter/sorter.d.ts +2 -4
- package/dist/widgets/table/table.d.ts +2 -3
- package/dist/widgets/video/video.d.ts +0 -3
- package/dist/widgets.d.ts +1 -32
- package/package.json +46 -46
- package/dist/renderer-util.d.ts +0 -14
- package/dist/widgets/__shared__/score-noop.d.ts +0 -11
- package/dist/widgets/group/score-group.d.ts +0 -4
- package/dist/widgets/group/validate-group.d.ts +0 -4
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +0 -4
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +0 -5
- package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -6,7 +6,6 @@ var KAS = require('@khanacademy/kas');
|
|
|
6
6
|
var mathInput = require('@khanacademy/math-input');
|
|
7
7
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
8
8
|
var PerseusLinter = require('@khanacademy/perseus-linter');
|
|
9
|
-
var perseusScore = require('@khanacademy/perseus-score');
|
|
10
9
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
11
10
|
var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
|
|
12
11
|
var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
|
|
@@ -21,6 +20,7 @@ var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
|
21
20
|
var $ = require('jquery');
|
|
22
21
|
var uuid = require('uuid');
|
|
23
22
|
var PropTypes = require('prop-types');
|
|
23
|
+
var perseusScore = require('@khanacademy/perseus-score');
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
26
26
|
var kmath = require('@khanacademy/kmath');
|
|
@@ -33,7 +33,7 @@ var Banner = require('@khanacademy/wonder-blocks-banner');
|
|
|
33
33
|
var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
|
|
34
34
|
var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
|
|
35
35
|
var caretUp = require('@phosphor-icons/core/regular/caret-up.svg');
|
|
36
|
-
|
|
36
|
+
require('lodash.debounce');
|
|
37
37
|
var mafs = require('mafs');
|
|
38
38
|
var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
|
|
39
39
|
var questionIcon = require('@phosphor-icons/core/regular/question.svg');
|
|
@@ -82,7 +82,6 @@ var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
|
|
|
82
82
|
var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
|
|
83
83
|
var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
|
|
84
84
|
var caretUp__default = /*#__PURE__*/_interopDefaultLegacy(caretUp);
|
|
85
|
-
var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce$1);
|
|
86
85
|
var questionIcon__default = /*#__PURE__*/_interopDefaultLegacy(questionIcon);
|
|
87
86
|
var Switch__default = /*#__PURE__*/_interopDefaultLegacy(Switch);
|
|
88
87
|
var Pill__default = /*#__PURE__*/_interopDefaultLegacy(Pill);
|
|
@@ -379,7 +378,7 @@ const mockStrings = {
|
|
|
379
378
|
x,
|
|
380
379
|
y
|
|
381
380
|
} = _ref27;
|
|
382
|
-
return `Point ${num} at ${x} comma ${y}
|
|
381
|
+
return `Point ${num} at ${x} comma ${y}.`;
|
|
383
382
|
},
|
|
384
383
|
srInteractiveElements: _ref28 => {
|
|
385
384
|
let {
|
|
@@ -670,7 +669,50 @@ const mockStrings = {
|
|
|
670
669
|
point3Y
|
|
671
670
|
} = _ref61;
|
|
672
671
|
return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
|
|
673
|
-
}
|
|
672
|
+
},
|
|
673
|
+
srPolygonGraph: "A polygon.",
|
|
674
|
+
srPolygonGraphCoordinatePlane: "A polygon on a coordinate plane.",
|
|
675
|
+
srPolygonGraphPointsNum: _ref62 => {
|
|
676
|
+
let {
|
|
677
|
+
num
|
|
678
|
+
} = _ref62;
|
|
679
|
+
return `The polygon has ${num} points.`;
|
|
680
|
+
},
|
|
681
|
+
srPolygonGraphPointsOne: "The polygon has 1 point.",
|
|
682
|
+
srPolygonElementsNum: _ref63 => {
|
|
683
|
+
let {
|
|
684
|
+
num
|
|
685
|
+
} = _ref63;
|
|
686
|
+
return `A polygon with ${num} points.`;
|
|
687
|
+
},
|
|
688
|
+
srPolygonElementsOne: "A polygon with 1 point.",
|
|
689
|
+
srPolygonPointAngleApprox: _ref64 => {
|
|
690
|
+
let {
|
|
691
|
+
angle
|
|
692
|
+
} = _ref64;
|
|
693
|
+
return `Angle approximately equal to ${angle} degrees.`;
|
|
694
|
+
},
|
|
695
|
+
srPolygonPointAngle: _ref65 => {
|
|
696
|
+
let {
|
|
697
|
+
angle
|
|
698
|
+
} = _ref65;
|
|
699
|
+
return `Angle equal to ${angle} degrees.`;
|
|
700
|
+
},
|
|
701
|
+
srPolygonSideLength: _ref66 => {
|
|
702
|
+
let {
|
|
703
|
+
pointNum,
|
|
704
|
+
length
|
|
705
|
+
} = _ref66;
|
|
706
|
+
return `A line segment, length equal to ${length} units, connects to point ${pointNum}.`;
|
|
707
|
+
},
|
|
708
|
+
srPolygonSideLengthApprox: _ref67 => {
|
|
709
|
+
let {
|
|
710
|
+
pointNum,
|
|
711
|
+
length
|
|
712
|
+
} = _ref67;
|
|
713
|
+
return `A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`;
|
|
714
|
+
},
|
|
715
|
+
srUnlimitedPolygonEmpty: "An empty coordinate plane."
|
|
674
716
|
// The above strings are used for interactive graph SR descriptions.
|
|
675
717
|
};
|
|
676
718
|
|
|
@@ -1612,7 +1654,6 @@ const ClassNames = {
|
|
|
1612
1654
|
SELECTED: "perseus-radio-selected",
|
|
1613
1655
|
OPTION_CONTENT: "perseus-radio-option-content"
|
|
1614
1656
|
},
|
|
1615
|
-
INTERACTIVE: "perseus-interactive",
|
|
1616
1657
|
CORRECT: "perseus-correct",
|
|
1617
1658
|
INCORRECT: "perseus-incorrect",
|
|
1618
1659
|
UNANSWERED: "perseus-unanswered",
|
|
@@ -1868,7 +1909,6 @@ class Expression extends React__namespace.Component {
|
|
|
1868
1909
|
// eslint-disable-next-line react/no-string-refs
|
|
1869
1910
|
, {
|
|
1870
1911
|
ref: "input",
|
|
1871
|
-
className: ClassNames.INTERACTIVE,
|
|
1872
1912
|
value: this.props.value,
|
|
1873
1913
|
onChange: this.changeAndTrack,
|
|
1874
1914
|
convertDotToTimes: this.props.times,
|
|
@@ -1970,7 +2010,6 @@ var Expression$1 = {
|
|
|
1970
2010
|
name: "expression",
|
|
1971
2011
|
displayName: "Expression / Equation",
|
|
1972
2012
|
accessible: true,
|
|
1973
|
-
defaultAlignment: "inline-block",
|
|
1974
2013
|
widget: ExpressionWithDependencies,
|
|
1975
2014
|
transform: widgetOptions => {
|
|
1976
2015
|
const {
|
|
@@ -1995,12 +2034,6 @@ var Expression$1 = {
|
|
|
1995
2034
|
propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
|
|
1996
2035
|
// For use by the editor
|
|
1997
2036
|
isLintable: true,
|
|
1998
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
1999
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2000
|
-
scorer: perseusScore.scoreExpression,
|
|
2001
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
2002
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2003
|
-
validator: perseusScore.validateExpression,
|
|
2004
2037
|
getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
|
|
2005
2038
|
// TODO(LEMS-2656): remove TS suppression
|
|
2006
2039
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
@@ -3280,10 +3313,6 @@ const pureMdMax$1 = "1023px";
|
|
|
3280
3313
|
const pureLgMax$1 = "1279px";
|
|
3281
3314
|
// @tableBackgroundAccent: #f9f9f9; // for striping
|
|
3282
3315
|
const tableBackgroundAccent = "#F9F9F9";
|
|
3283
|
-
// @zIndexAboveScratchpad: @zIndexScratchPad + 1;
|
|
3284
|
-
const zIndexAboveScratchpad$1 = 2;
|
|
3285
|
-
// @zIndexInteractiveComponent: @zIndexAboveScratchpad + 1;
|
|
3286
|
-
const zIndexInteractiveComponent$1 = 3;
|
|
3287
3316
|
// @phoneMargin: 16px;
|
|
3288
3317
|
const phoneMargin = 16;
|
|
3289
3318
|
const negativePhoneMargin = -16;
|
|
@@ -3327,8 +3356,6 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
3327
3356
|
pureMdMax: pureMdMax$1,
|
|
3328
3357
|
pureLgMax: pureLgMax$1,
|
|
3329
3358
|
tableBackgroundAccent: tableBackgroundAccent,
|
|
3330
|
-
zIndexAboveScratchpad: zIndexAboveScratchpad$1,
|
|
3331
|
-
zIndexInteractiveComponent: zIndexInteractiveComponent$1,
|
|
3332
3359
|
phoneMargin: phoneMargin,
|
|
3333
3360
|
negativePhoneMargin: negativePhoneMargin,
|
|
3334
3361
|
hintBorderWidth: hintBorderWidth,
|
|
@@ -10074,7 +10101,7 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10074
10101
|
|
|
10075
10102
|
/* eslint-disable @babel/no-invalid-this */
|
|
10076
10103
|
const {
|
|
10077
|
-
clockwise: clockwise$
|
|
10104
|
+
clockwise: clockwise$2,
|
|
10078
10105
|
reverseVector: reverseVector$1
|
|
10079
10106
|
} = kmath.geometry;
|
|
10080
10107
|
function scaledDistanceFromAngle(angle) {
|
|
@@ -11312,7 +11339,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11312
11339
|
// Temporary objects
|
|
11313
11340
|
___default["default"].invoke(polygon.temp, "remove");
|
|
11314
11341
|
polygon.temp = [];
|
|
11315
|
-
const isClockwise = clockwise$
|
|
11342
|
+
const isClockwise = clockwise$2(polygon.coords);
|
|
11316
11343
|
|
|
11317
11344
|
// Update angle labels
|
|
11318
11345
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -14745,7 +14772,7 @@ function sum(array) {
|
|
|
14745
14772
|
return memo + arg;
|
|
14746
14773
|
}, 0);
|
|
14747
14774
|
}
|
|
14748
|
-
function clockwise$
|
|
14775
|
+
function clockwise$1(points) {
|
|
14749
14776
|
const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
14750
14777
|
const areas = ___default["default"].map(segments, function (segment) {
|
|
14751
14778
|
const p1 = segment[0];
|
|
@@ -14811,7 +14838,7 @@ const draw$1 = {
|
|
|
14811
14838
|
const graphie = this.graphie;
|
|
14812
14839
|
const self = this;
|
|
14813
14840
|
const coords = ___default["default"].invoke(state.points, "coord");
|
|
14814
|
-
const isClockwise = clockwise$
|
|
14841
|
+
const isClockwise = clockwise$1(coords);
|
|
14815
14842
|
const n = coords.length;
|
|
14816
14843
|
|
|
14817
14844
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -16557,9 +16584,28 @@ class Zoomable extends React__namespace.Component {
|
|
|
16557
16584
|
if (!this._isMounted) {
|
|
16558
16585
|
return;
|
|
16559
16586
|
}
|
|
16587
|
+
|
|
16588
|
+
// When a zoomable item is contained within a collapsed parent (such as an Explanation widget),
|
|
16589
|
+
// the parent node has zero width, which causes the scale to be miscalculated.
|
|
16590
|
+
// Therefore, we check to see if the node has a valid width, and if it doesn't,
|
|
16591
|
+
// then we traverse up the DOM tree and look for the closest "perseus-renderer"
|
|
16592
|
+
// (which should have a width).
|
|
16593
|
+
// To be cautious, we also account for the possibility that we can't find any parents with a valid width
|
|
16594
|
+
// (not that they don't exist, just that we can't find them).
|
|
16595
|
+
let parentNode = this._node;
|
|
16596
|
+
let currentNode = parentNode;
|
|
16597
|
+
while (currentNode && currentNode.offsetWidth === 0) {
|
|
16598
|
+
// When traversing up the DOM tree, we need to start at the "parentElement"
|
|
16599
|
+
// because "closest()" can match the current element,
|
|
16600
|
+
// and if it does so, then we are stuck in an infinite loop.
|
|
16601
|
+
currentNode = currentNode.parentElement?.closest(".perseus-renderer") ?? null;
|
|
16602
|
+
}
|
|
16603
|
+
if (currentNode) {
|
|
16604
|
+
parentNode = currentNode;
|
|
16605
|
+
}
|
|
16560
16606
|
const parentBounds = {
|
|
16561
|
-
width:
|
|
16562
|
-
height:
|
|
16607
|
+
width: parentNode.offsetWidth,
|
|
16608
|
+
height: parentNode.offsetHeight
|
|
16563
16609
|
};
|
|
16564
16610
|
const childBounds = this.props.computeChildBounds(this._node, parentBounds);
|
|
16565
16611
|
|
|
@@ -17623,130 +17669,162 @@ class QuestionParagraph extends React__namespace.Component {
|
|
|
17623
17669
|
}
|
|
17624
17670
|
}
|
|
17625
17671
|
|
|
17626
|
-
const noScore = {
|
|
17627
|
-
type: "points",
|
|
17628
|
-
earned: 0,
|
|
17629
|
-
total: 0,
|
|
17630
|
-
message: null
|
|
17631
|
-
};
|
|
17632
|
-
|
|
17633
17672
|
/**
|
|
17634
|
-
*
|
|
17635
|
-
* Trying to encapsulate references to the score format.
|
|
17636
|
-
*/
|
|
17637
|
-
function scoreIsEmpty(score) {
|
|
17638
|
-
// HACK(benkomalo): ugh. this isn't great; the Perseus score objects
|
|
17639
|
-
// overload the type "invalid" for what should probably be three
|
|
17640
|
-
// distinct cases:
|
|
17641
|
-
// - truly empty or not fully filled out
|
|
17642
|
-
// - invalid or malformed inputs
|
|
17643
|
-
// - "almost correct" like inputs where the widget wants to give
|
|
17644
|
-
// feedback (e.g. a fraction needs to be reduced, or `pi` should
|
|
17645
|
-
// be used instead of 3.14)
|
|
17646
|
-
//
|
|
17647
|
-
// Unfortunately the coercion happens all over the place, as these
|
|
17648
|
-
// Perseus style score objects are created *everywhere* (basically
|
|
17649
|
-
// in every widget), so it's hard to change now. We assume that
|
|
17650
|
-
// anything with a "message" is not truly empty, and one of the
|
|
17651
|
-
// latter two cases for now.
|
|
17652
|
-
return score.type === "invalid" && (!score.message || score.message.length === 0);
|
|
17653
|
-
}
|
|
17654
|
-
|
|
17655
|
-
/**
|
|
17656
|
-
* Combine two score objects.
|
|
17673
|
+
* TranslationLinter asynchronously calls Khan Academy's poentry linter.
|
|
17657
17674
|
*
|
|
17658
|
-
*
|
|
17659
|
-
*
|
|
17675
|
+
* The poentry linter checks for issues that prevent content from being
|
|
17676
|
+
* translated. This linter is KA-specific and so TranslationLinter does not do
|
|
17677
|
+
* anything in non-KA environments.
|
|
17660
17678
|
*/
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17679
|
+
|
|
17680
|
+
// A LintCB is called once the linter has been run.
|
|
17681
|
+
|
|
17682
|
+
// msec to wait before we actually call the linter after receiving the
|
|
17683
|
+
// last call. The timer is reset if the lint is called during the wait.
|
|
17684
|
+
const DEBOUNCE_TIMEOUT = 1000;
|
|
17685
|
+
class TranslationLinter {
|
|
17686
|
+
// To avoid linting when nothing has changed.
|
|
17687
|
+
previousContent = null;
|
|
17688
|
+
|
|
17689
|
+
/**
|
|
17690
|
+
* Runs the poentry linter on the given perseus string asynchronously.
|
|
17691
|
+
*
|
|
17692
|
+
* It doesn't do anything unless we're in a KA environment.
|
|
17693
|
+
*
|
|
17694
|
+
* onLintErrorsGenerated is called even if there are no errors. It is not
|
|
17695
|
+
* called if perseusStr is unchanged from the last call.
|
|
17696
|
+
*
|
|
17697
|
+
* Linting is relatively expensive -- so we debounce linting each instance
|
|
17698
|
+
* for some amount of time.
|
|
17699
|
+
*/
|
|
17700
|
+
runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
|
|
17701
|
+
// TODO(FEI-5003): Do we still need to check for 'KA'?
|
|
17702
|
+
if (typeof KA === "undefined") {
|
|
17703
|
+
return;
|
|
17669
17704
|
}
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
earned: scoreA.earned + scoreB.earned,
|
|
17673
|
-
total: scoreA.total + scoreB.total,
|
|
17674
|
-
message: message
|
|
17675
|
-
};
|
|
17676
|
-
}
|
|
17677
|
-
if (scoreA.type === "points" && scoreB.type === "invalid") {
|
|
17678
|
-
return scoreB;
|
|
17679
|
-
}
|
|
17680
|
-
if (scoreA.type === "invalid" && scoreB.type === "points") {
|
|
17681
|
-
return scoreA;
|
|
17682
|
-
}
|
|
17683
|
-
if (scoreA.type === "invalid" && scoreB.type === "invalid") {
|
|
17684
|
-
if (scoreA.message && scoreB.message && scoreA.message !== scoreB.message) {
|
|
17685
|
-
// TODO(alpert): Figure out how to combine messages usefully
|
|
17686
|
-
message = null;
|
|
17687
|
-
} else {
|
|
17688
|
-
message = scoreA.message || scoreB.message;
|
|
17705
|
+
if (perseusStr === this.previousContent) {
|
|
17706
|
+
return;
|
|
17689
17707
|
}
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17708
|
+
this.previousContent = perseusStr;
|
|
17709
|
+
if (perseusStr === "") {
|
|
17710
|
+
onLintErrorsGenerated([]);
|
|
17711
|
+
return;
|
|
17712
|
+
}
|
|
17713
|
+
fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
|
|
17714
|
+
headers: {
|
|
17715
|
+
"Content-Type": "application/json"
|
|
17716
|
+
},
|
|
17717
|
+
body: JSON.stringify({
|
|
17718
|
+
// The poentry linter verifies that the translation from
|
|
17719
|
+
// the original text to the translated text is sane. We
|
|
17720
|
+
// want to ensure that the translation from English to
|
|
17721
|
+
// English is sane, so msgid === mstr.
|
|
17722
|
+
msgid: perseusStr,
|
|
17723
|
+
// msgid is the original text
|
|
17724
|
+
msgstr: perseusStr,
|
|
17725
|
+
// msgstr is the translated text
|
|
17726
|
+
format: "perseus_text",
|
|
17727
|
+
filename: ""
|
|
17728
|
+
}),
|
|
17729
|
+
method: "POST"
|
|
17730
|
+
}).then(response => {
|
|
17731
|
+
if (response.status >= 400) {
|
|
17732
|
+
return {
|
|
17733
|
+
status: "error",
|
|
17734
|
+
message: "Could not run i18n linter."
|
|
17735
|
+
};
|
|
17736
|
+
}
|
|
17737
|
+
return response.json();
|
|
17738
|
+
}, rejection => {
|
|
17739
|
+
return {
|
|
17740
|
+
status: "error",
|
|
17741
|
+
message: "Could not run i18n linter."
|
|
17742
|
+
};
|
|
17743
|
+
}).then(json => {
|
|
17744
|
+
if (json.status === "error") {
|
|
17745
|
+
onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
|
|
17746
|
+
} else {
|
|
17747
|
+
onLintErrorsGenerated([]);
|
|
17748
|
+
}
|
|
17749
|
+
});
|
|
17750
|
+
}, DEBOUNCE_TIMEOUT);
|
|
17695
17751
|
|
|
17696
17752
|
/**
|
|
17697
|
-
*
|
|
17698
|
-
*
|
|
17753
|
+
* Applies an array of errors generated by linters without position
|
|
17754
|
+
* information (TranslationLinter and the legacy getSaveWarnings()) to the
|
|
17755
|
+
* top of a Perseus tree.
|
|
17699
17756
|
*/
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17757
|
+
applyLintErrors(parsedMarkdown, translationLintErrors) {
|
|
17758
|
+
// These lint errors do not have position data associated with
|
|
17759
|
+
// them, so we just plop them at the top.
|
|
17760
|
+
if (translationLintErrors.length) {
|
|
17761
|
+
const errorText = translationLintErrors.join("\n\n");
|
|
17762
|
+
parsedMarkdown.unshift({
|
|
17763
|
+
content: {
|
|
17764
|
+
type: "text",
|
|
17765
|
+
content: ""
|
|
17766
|
+
},
|
|
17767
|
+
insideTable: false,
|
|
17768
|
+
message: errorText,
|
|
17769
|
+
ruleName: "legacy-error",
|
|
17770
|
+
severity: PerseusLinter.Rule.Severity.ERROR,
|
|
17771
|
+
type: "lint"
|
|
17772
|
+
});
|
|
17704
17773
|
}
|
|
17705
|
-
}
|
|
17706
|
-
}
|
|
17707
|
-
function flattenScores(widgetScoreMap) {
|
|
17708
|
-
return Object.values(widgetScoreMap).reduce(combineScores, noScore);
|
|
17709
|
-
}
|
|
17710
|
-
function isCorrect(score) {
|
|
17711
|
-
return score.type === "points" && score.earned >= score.total;
|
|
17774
|
+
}
|
|
17712
17775
|
}
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17776
|
+
|
|
17777
|
+
/**
|
|
17778
|
+
* Preprocess TeX code to convert things that MathJax doesn't know how to handle
|
|
17779
|
+
* to things is does.
|
|
17780
|
+
*/
|
|
17781
|
+
var preprocessTex = (texCode => texCode
|
|
17782
|
+
// Replace uses of \begin{align}...\end{align} which KaTeX doesn't
|
|
17783
|
+
// support (yet) with \begin{aligned}...\end{aligned} which renders
|
|
17784
|
+
// the same is supported by KaTeX. It does the same for align*.
|
|
17785
|
+
// TODO(kevinb) update content to use aligned instead of align.
|
|
17786
|
+
// TODO(LEMS-1608) Remove this replacement as MathJax supports the
|
|
17787
|
+
// "align" macro correctly (and, in fact, it is not synonymous with
|
|
17788
|
+
// "aligned").
|
|
17789
|
+
.replace(/\{align[*]?\}/g, "{aligned}")
|
|
17790
|
+
// Replace non-breaking spaces with regular spaces.
|
|
17791
|
+
.replace(/[\u00a0]/g, " "));
|
|
17792
|
+
|
|
17793
|
+
const {
|
|
17794
|
+
interactiveSizes
|
|
17795
|
+
} = constants;
|
|
17796
|
+
|
|
17797
|
+
// Note: these size cutoffs represent content-width cutoffs as specified in
|
|
17798
|
+
// http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
|
|
17799
|
+
// TODO(benkomalo): these values aren't used in JS outside of this file, but
|
|
17800
|
+
// are coupled to the values in styles/styles.less - DRY it up at some point
|
|
17801
|
+
const smMax = articleMaxWidthTableInPx;
|
|
17802
|
+
const mdMax = articleMaxWidthInPx;
|
|
17803
|
+
const containerSizeClass = {
|
|
17804
|
+
SMALL: "small",
|
|
17805
|
+
MEDIUM: "medium",
|
|
17806
|
+
LARGE: "large",
|
|
17807
|
+
XLARGE: "xlarge"
|
|
17808
|
+
};
|
|
17809
|
+
const getClassFromWidth = width => {
|
|
17810
|
+
if (!width) {
|
|
17811
|
+
return containerSizeClass.MEDIUM;
|
|
17724
17812
|
}
|
|
17725
|
-
if (
|
|
17726
|
-
return
|
|
17727
|
-
empty: true,
|
|
17728
|
-
correct: false,
|
|
17729
|
-
message: score.message,
|
|
17730
|
-
suppressAlmostThere: score.suppressAlmostThere,
|
|
17731
|
-
guess: guess,
|
|
17732
|
-
state: state
|
|
17733
|
-
};
|
|
17813
|
+
if (width <= smMax) {
|
|
17814
|
+
return containerSizeClass.SMALL;
|
|
17734
17815
|
}
|
|
17735
|
-
|
|
17736
|
-
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
}
|
|
17744
|
-
|
|
17816
|
+
if (width <= mdMax) {
|
|
17817
|
+
return containerSizeClass.MEDIUM;
|
|
17818
|
+
}
|
|
17819
|
+
return containerSizeClass.LARGE;
|
|
17820
|
+
};
|
|
17821
|
+
const getInteractiveBoxFromSizeClass = sizeClass => {
|
|
17822
|
+
if (sizeClass === containerSizeClass.SMALL) {
|
|
17823
|
+
return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
|
|
17824
|
+
}
|
|
17825
|
+
return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
|
|
17826
|
+
};
|
|
17745
17827
|
|
|
17746
|
-
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17747
|
-
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17748
|
-
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
17749
|
-
const DEFAULT_STATIC = false;
|
|
17750
17828
|
const DEFAULT_TRACKING = "";
|
|
17751
17829
|
const DEFAULT_LINTABLE = false;
|
|
17752
17830
|
const widgets = {};
|
|
@@ -17763,7 +17841,6 @@ const registerWidgets = widgets => {
|
|
|
17763
17841
|
widgets.forEach(widget => {
|
|
17764
17842
|
registerWidget(widget.name, widget);
|
|
17765
17843
|
});
|
|
17766
|
-
validateAlignments();
|
|
17767
17844
|
};
|
|
17768
17845
|
|
|
17769
17846
|
/**
|
|
@@ -17844,12 +17921,6 @@ const getWidget = name => {
|
|
|
17844
17921
|
const getWidgetExport = name => {
|
|
17845
17922
|
return widgets[name] ?? null;
|
|
17846
17923
|
};
|
|
17847
|
-
const getWidgetValidator = name => {
|
|
17848
|
-
return widgets[name]?.validator ?? null;
|
|
17849
|
-
};
|
|
17850
|
-
const getWidgetScorer = name => {
|
|
17851
|
-
return widgets[name]?.scorer ?? null;
|
|
17852
|
-
};
|
|
17853
17924
|
const getPublicWidgetOptionsFunction = name => {
|
|
17854
17925
|
return widgets[name]?.getPublicWidgetOptions ?? (i => i);
|
|
17855
17926
|
};
|
|
@@ -17895,112 +17966,6 @@ const isAccessible = widgetInfo => {
|
|
|
17895
17966
|
const getAllWidgetTypes = () => {
|
|
17896
17967
|
return ___default["default"].keys(widgets);
|
|
17897
17968
|
};
|
|
17898
|
-
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
17899
|
-
const type = oldWidgetInfo.type;
|
|
17900
|
-
// NOTE(jeremy): This looks like it could be replaced by fixing types so
|
|
17901
|
-
// that `type` is non-optional. But we're seeing this in Sentry today so I
|
|
17902
|
-
// suspect we have legacy data (potentially unpublished) and we should
|
|
17903
|
-
// figure that out before depending solely on types.
|
|
17904
|
-
if (!___default["default"].isString(type)) {
|
|
17905
|
-
throw new perseusCore.PerseusError("widget type must be a string, but was: " + type, perseusCore.Errors.Internal);
|
|
17906
|
-
}
|
|
17907
|
-
const widgetExports = widgets[type];
|
|
17908
|
-
if (widgetExports == null) {
|
|
17909
|
-
// If we have a widget that isn't registered, we can't upgrade it
|
|
17910
|
-
// TODO(aria): Figure out what the best thing to do here would be
|
|
17911
|
-
return oldWidgetInfo;
|
|
17912
|
-
}
|
|
17913
|
-
|
|
17914
|
-
// Unversioned widgets (pre-July 2014) are all implicitly 0.0
|
|
17915
|
-
const initialVersion = oldWidgetInfo.version || {
|
|
17916
|
-
major: 0,
|
|
17917
|
-
minor: 0
|
|
17918
|
-
};
|
|
17919
|
-
const latestVersion = widgetExports.version || {
|
|
17920
|
-
major: 0,
|
|
17921
|
-
minor: 0
|
|
17922
|
-
};
|
|
17923
|
-
|
|
17924
|
-
// If the widget version is later than what we understand (major
|
|
17925
|
-
// version is higher than latest, or major versions are equal and minor
|
|
17926
|
-
// version is higher than latest), don't perform any upgrades.
|
|
17927
|
-
if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
|
|
17928
|
-
return oldWidgetInfo;
|
|
17929
|
-
}
|
|
17930
|
-
|
|
17931
|
-
// We do a clone here so that it's safe to mutate the input parameter
|
|
17932
|
-
// in propUpgrades functions (which I will probably accidentally do at
|
|
17933
|
-
// some point, and we would like to not break when that happens).
|
|
17934
|
-
let newEditorProps = ___default["default"].clone(oldWidgetInfo.options) || {};
|
|
17935
|
-
const upgradePropsMap = widgetExports.propUpgrades || {};
|
|
17936
|
-
|
|
17937
|
-
// Empty props usually mean a newly created widget by the editor,
|
|
17938
|
-
// and are always considerered up-to-date.
|
|
17939
|
-
// Mostly, we'd rather not run upgrade functions on props that are
|
|
17940
|
-
// not complete.
|
|
17941
|
-
if (___default["default"].keys(newEditorProps).length !== 0) {
|
|
17942
|
-
// We loop through all the versions after the current version of
|
|
17943
|
-
// the loaded widget, up to and including the latest version of the
|
|
17944
|
-
// loaded widget, and run the upgrade function to bring our loaded
|
|
17945
|
-
// widget's props up to that version.
|
|
17946
|
-
// There is a little subtlety here in that we call
|
|
17947
|
-
// upgradePropsMap[1] to upgrade *to* version 1,
|
|
17948
|
-
// (not from version 1).
|
|
17949
|
-
for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
|
|
17950
|
-
if (upgradePropsMap[String(nextVersion)]) {
|
|
17951
|
-
newEditorProps = upgradePropsMap[String(nextVersion)](newEditorProps);
|
|
17952
|
-
} else {
|
|
17953
|
-
// This is a Log.error because it is unlikely to be hit in
|
|
17954
|
-
// local testing, and a Log.error is slightly less scary in
|
|
17955
|
-
// prod than a `throw new Error`
|
|
17956
|
-
Log.error("No upgrade found for widget. Cannot render.", perseusCore.Errors.Internal, {
|
|
17957
|
-
loggedMetadata: {
|
|
17958
|
-
type,
|
|
17959
|
-
fromMajorVersion: nextVersion - 1,
|
|
17960
|
-
toMajorVersion: nextVersion
|
|
17961
|
-
}
|
|
17962
|
-
});
|
|
17963
|
-
// But try to keep going anyways (yolo!)
|
|
17964
|
-
// (Throwing an error here would just break the page
|
|
17965
|
-
// silently anyways, so that doesn't seem much better
|
|
17966
|
-
// than a halfhearted attempt to continue, however
|
|
17967
|
-
// shallow...)
|
|
17968
|
-
}
|
|
17969
|
-
}
|
|
17970
|
-
}
|
|
17971
|
-
|
|
17972
|
-
// Minor version upgrades (eg. new optional props) don't have
|
|
17973
|
-
// transform functions. Instead, we fill in the new props with their
|
|
17974
|
-
// defaults.
|
|
17975
|
-
const defaultProps = type in editors ? editors[type].defaultProps : {};
|
|
17976
|
-
newEditorProps = {
|
|
17977
|
-
...defaultProps,
|
|
17978
|
-
...newEditorProps
|
|
17979
|
-
};
|
|
17980
|
-
let alignment = oldWidgetInfo.alignment;
|
|
17981
|
-
|
|
17982
|
-
// Widgets that support multiple alignments will "lock in" the
|
|
17983
|
-
// alignment to the alignment that would be listed first in the
|
|
17984
|
-
// select box. If the widget only supports one alignment, the
|
|
17985
|
-
// alignment value will likely just end up as "default".
|
|
17986
|
-
if (alignment == null || alignment === "default") {
|
|
17987
|
-
alignment = getSupportedAlignments(type)[0];
|
|
17988
|
-
}
|
|
17989
|
-
let widgetStatic = oldWidgetInfo.static;
|
|
17990
|
-
if (widgetStatic == null) {
|
|
17991
|
-
widgetStatic = DEFAULT_STATIC;
|
|
17992
|
-
}
|
|
17993
|
-
return ___default["default"].extend({}, oldWidgetInfo, {
|
|
17994
|
-
// maintain other info, like type
|
|
17995
|
-
// After upgrading we guarantee that the version is up-to-date
|
|
17996
|
-
version: latestVersion,
|
|
17997
|
-
// Default graded to true (so null/undefined becomes true):
|
|
17998
|
-
graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
|
|
17999
|
-
alignment: alignment,
|
|
18000
|
-
static: widgetStatic,
|
|
18001
|
-
options: newEditorProps
|
|
18002
|
-
});
|
|
18003
|
-
};
|
|
18004
17969
|
const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
|
|
18005
17970
|
const type = widgetInfo.type;
|
|
18006
17971
|
const widgetExports = widgets[type];
|
|
@@ -18042,70 +18007,6 @@ const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
|
|
|
18042
18007
|
return widgetInfo;
|
|
18043
18008
|
};
|
|
18044
18009
|
|
|
18045
|
-
/**
|
|
18046
|
-
* Handling for the optional alignments for widgets
|
|
18047
|
-
* See widget-container.jsx for details on how alignments are implemented.
|
|
18048
|
-
*/
|
|
18049
|
-
|
|
18050
|
-
/**
|
|
18051
|
-
* Returns the list of supported alignments for the given (string) widget
|
|
18052
|
-
* type. This is used primarily at editing time to display the choices
|
|
18053
|
-
* for the user.
|
|
18054
|
-
*
|
|
18055
|
-
* Supported alignments are given as an array of strings in the exports of
|
|
18056
|
-
* a widget's module.
|
|
18057
|
-
*/
|
|
18058
|
-
const getSupportedAlignments = type => {
|
|
18059
|
-
const widgetExport = widgets[type];
|
|
18060
|
-
// @ts-expect-error - TS2322 - Type 'string[] | readonly Alignment[]' is not assignable to type 'readonly Alignment[]'.
|
|
18061
|
-
return widgetExport && widgetExport.supportedAlignments || DEFAULT_SUPPORTED_ALIGNMENTS;
|
|
18062
|
-
};
|
|
18063
|
-
|
|
18064
|
-
/**
|
|
18065
|
-
* For the given (string) widget type, determine the default alignment for
|
|
18066
|
-
* the widget. This is used at rendering time to go from "default" alignment
|
|
18067
|
-
* to the actual alignment displayed on the screen.
|
|
18068
|
-
*
|
|
18069
|
-
* The default alignment is given either as a string (called
|
|
18070
|
-
* `defaultAlignment`) or a function (called `getDefaultAlignment`) on
|
|
18071
|
-
* the exports of a widget's module.
|
|
18072
|
-
*/
|
|
18073
|
-
const getDefaultAlignment = type => {
|
|
18074
|
-
const widgetExports = widgets[type];
|
|
18075
|
-
let alignment;
|
|
18076
|
-
if (!widgetExports) {
|
|
18077
|
-
return DEFAULT_ALIGNMENT$1;
|
|
18078
|
-
}
|
|
18079
|
-
if (widgetExports.getDefaultAlignment) {
|
|
18080
|
-
alignment = widgetExports.getDefaultAlignment();
|
|
18081
|
-
} else {
|
|
18082
|
-
alignment = widgetExports.defaultAlignment;
|
|
18083
|
-
}
|
|
18084
|
-
return alignment || DEFAULT_ALIGNMENT$1;
|
|
18085
|
-
};
|
|
18086
|
-
const validAlignments = ["block", "inline-block", "inline", "float-left", "float-right", "full-width"];
|
|
18087
|
-
|
|
18088
|
-
/**
|
|
18089
|
-
* Used at startup to fail fast if an alignment given by a widget is
|
|
18090
|
-
* invalid.
|
|
18091
|
-
*/
|
|
18092
|
-
// TODO(alex): Change this to run as a testcase (vs. being run at runtime)
|
|
18093
|
-
// TODO(LP-10707): I think this can be completely removed because our TypeScript types
|
|
18094
|
-
// enforce this!
|
|
18095
|
-
const validateAlignments = () => {
|
|
18096
|
-
___default["default"].each(widgets, function (widgetInfo) {
|
|
18097
|
-
if (widgetInfo.defaultAlignment && !___default["default"].contains(validAlignments, widgetInfo.defaultAlignment)) {
|
|
18098
|
-
throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid defaultAlignment value: " + widgetInfo.defaultAlignment, perseusCore.Errors.InvalidInput);
|
|
18099
|
-
}
|
|
18100
|
-
if (widgetInfo.supportedAlignments) {
|
|
18101
|
-
const unknownAlignments = ___default["default"].difference(widgetInfo.supportedAlignments, validAlignments);
|
|
18102
|
-
if (unknownAlignments.length) {
|
|
18103
|
-
throw new perseusCore.PerseusError("Widget '" + widgetInfo.displayName + "' has an invalid value for supportedAlignments: " + unknownAlignments.join(" "), perseusCore.Errors.InvalidInput);
|
|
18104
|
-
}
|
|
18105
|
-
}
|
|
18106
|
-
});
|
|
18107
|
-
};
|
|
18108
|
-
|
|
18109
18010
|
/**
|
|
18110
18011
|
* Handling for static mode for widgets that support it.
|
|
18111
18012
|
*/
|
|
@@ -18159,8 +18060,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18159
18060
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
18160
18061
|
getWidget: getWidget,
|
|
18161
18062
|
getWidgetExport: getWidgetExport,
|
|
18162
|
-
getWidgetValidator: getWidgetValidator,
|
|
18163
|
-
getWidgetScorer: getWidgetScorer,
|
|
18164
18063
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18165
18064
|
getEditor: getEditor,
|
|
18166
18065
|
getTransform: getTransform,
|
|
@@ -18169,267 +18068,14 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18169
18068
|
getPublicWidgets: getPublicWidgets,
|
|
18170
18069
|
isAccessible: isAccessible,
|
|
18171
18070
|
getAllWidgetTypes: getAllWidgetTypes,
|
|
18172
|
-
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
18173
18071
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
18174
18072
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
18175
|
-
getSupportedAlignments: getSupportedAlignments,
|
|
18176
|
-
getDefaultAlignment: getDefaultAlignment,
|
|
18177
|
-
validateAlignments: validateAlignments,
|
|
18178
18073
|
supportsStaticMode: supportsStaticMode,
|
|
18179
18074
|
getStaticTransform: getStaticTransform,
|
|
18180
18075
|
getTracking: getTracking,
|
|
18181
18076
|
isLintable: isLintable
|
|
18182
18077
|
});
|
|
18183
18078
|
|
|
18184
|
-
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
18185
|
-
return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18186
|
-
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
18187
|
-
const newValues = {};
|
|
18188
|
-
if (!widgetInfo.type) {
|
|
18189
|
-
// TODO: why does widget have no type?
|
|
18190
|
-
// We don't want to derive type from widget ID
|
|
18191
|
-
// see: LEMS-1845
|
|
18192
|
-
newValues.type = widgetId.split(" ")[0];
|
|
18193
|
-
}
|
|
18194
|
-
if (!widgetInfo.alignment) {
|
|
18195
|
-
newValues.alignment = "default";
|
|
18196
|
-
}
|
|
18197
|
-
widgetInfo = {
|
|
18198
|
-
...widgetInfo,
|
|
18199
|
-
...newValues
|
|
18200
|
-
};
|
|
18201
|
-
}
|
|
18202
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
18203
|
-
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
18204
|
-
});
|
|
18205
|
-
}
|
|
18206
|
-
|
|
18207
|
-
/**
|
|
18208
|
-
* Checks the given user input to see if any answerable widgets have not been
|
|
18209
|
-
* "filled in" (ie. if they're empty). Another way to think about this
|
|
18210
|
-
* function is that its a check to see if we can score the provided input.
|
|
18211
|
-
*/
|
|
18212
|
-
function emptyWidgetsFunctional(widgets,
|
|
18213
|
-
// This is a port of old code, I'm not sure why
|
|
18214
|
-
// we need widgetIds vs the keys of the widgets object
|
|
18215
|
-
widgetIds, userInputMap, strings, locale) {
|
|
18216
|
-
return widgetIds.filter(id => {
|
|
18217
|
-
const widget = widgets[id];
|
|
18218
|
-
if (!widget || widget.static === true) {
|
|
18219
|
-
// Static widgets shouldn't count as empty
|
|
18220
|
-
return false;
|
|
18221
|
-
}
|
|
18222
|
-
const validator = getWidgetValidator(widget.type);
|
|
18223
|
-
const userInput = userInputMap[id];
|
|
18224
|
-
const validationData = widget.options;
|
|
18225
|
-
const score = validator?.(userInput, validationData, strings, locale);
|
|
18226
|
-
if (score) {
|
|
18227
|
-
return scoreIsEmpty(score);
|
|
18228
|
-
}
|
|
18229
|
-
});
|
|
18230
|
-
}
|
|
18231
|
-
|
|
18232
|
-
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
18233
|
-
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
18234
|
-
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
18235
|
-
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
18236
|
-
// need to be removed before we move this to the server
|
|
18237
|
-
strings, locale) {
|
|
18238
|
-
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
18239
|
-
// widget data for widgets that are not in PerseusRenderer.content,
|
|
18240
|
-
// so this checks that the widgets are being used before scoring them
|
|
18241
|
-
const usedWidgetIds = perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
|
|
18242
|
-
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
18243
|
-
return flattenScores(scores);
|
|
18244
|
-
}
|
|
18245
|
-
function scoreWidgetsFunctional(widgets,
|
|
18246
|
-
// This is a port of old code, I'm not sure why
|
|
18247
|
-
// we need widgetIds vs the keys of the widgets object
|
|
18248
|
-
widgetIds, userInputMap, strings, locale) {
|
|
18249
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
18250
|
-
const gradedWidgetIds = widgetIds.filter(id => {
|
|
18251
|
-
const props = upgradedWidgets[id];
|
|
18252
|
-
const widgetIsGraded = props?.graded == null || props.graded;
|
|
18253
|
-
const widgetIsStatic = !!props?.static;
|
|
18254
|
-
// Ungraded widgets or widgets set to static shouldn't be graded.
|
|
18255
|
-
return widgetIsGraded && !widgetIsStatic;
|
|
18256
|
-
});
|
|
18257
|
-
const widgetScores = {};
|
|
18258
|
-
gradedWidgetIds.forEach(id => {
|
|
18259
|
-
const widget = upgradedWidgets[id];
|
|
18260
|
-
if (!widget) {
|
|
18261
|
-
return;
|
|
18262
|
-
}
|
|
18263
|
-
const userInput = userInputMap[id];
|
|
18264
|
-
const validator = getWidgetValidator(widget.type);
|
|
18265
|
-
const scorer = getWidgetScorer(widget.type);
|
|
18266
|
-
|
|
18267
|
-
// We do validation (empty checks) first and then scoring. If
|
|
18268
|
-
// validation fails, it's result is itself a PerseusScore.
|
|
18269
|
-
const score = validator?.(userInput, widget.options, strings, locale) ?? scorer?.(userInput, widget.options, strings, locale);
|
|
18270
|
-
if (score != null) {
|
|
18271
|
-
widgetScores[id] = score;
|
|
18272
|
-
}
|
|
18273
|
-
});
|
|
18274
|
-
return widgetScores;
|
|
18275
|
-
}
|
|
18276
|
-
|
|
18277
|
-
/**
|
|
18278
|
-
* TranslationLinter asynchronously calls Khan Academy's poentry linter.
|
|
18279
|
-
*
|
|
18280
|
-
* The poentry linter checks for issues that prevent content from being
|
|
18281
|
-
* translated. This linter is KA-specific and so TranslationLinter does not do
|
|
18282
|
-
* anything in non-KA environments.
|
|
18283
|
-
*/
|
|
18284
|
-
|
|
18285
|
-
// A LintCB is called once the linter has been run.
|
|
18286
|
-
|
|
18287
|
-
// msec to wait before we actually call the linter after receiving the
|
|
18288
|
-
// last call. The timer is reset if the lint is called during the wait.
|
|
18289
|
-
const DEBOUNCE_TIMEOUT = 1000;
|
|
18290
|
-
class TranslationLinter {
|
|
18291
|
-
// To avoid linting when nothing has changed.
|
|
18292
|
-
previousContent = null;
|
|
18293
|
-
|
|
18294
|
-
/**
|
|
18295
|
-
* Runs the poentry linter on the given perseus string asynchronously.
|
|
18296
|
-
*
|
|
18297
|
-
* It doesn't do anything unless we're in a KA environment.
|
|
18298
|
-
*
|
|
18299
|
-
* onLintErrorsGenerated is called even if there are no errors. It is not
|
|
18300
|
-
* called if perseusStr is unchanged from the last call.
|
|
18301
|
-
*
|
|
18302
|
-
* Linting is relatively expensive -- so we debounce linting each instance
|
|
18303
|
-
* for some amount of time.
|
|
18304
|
-
*/
|
|
18305
|
-
runLinter = _.debounce((perseusStr, onLintErrorsGenerated) => {
|
|
18306
|
-
// TODO(FEI-5003): Do we still need to check for 'KA'?
|
|
18307
|
-
if (typeof KA === "undefined") {
|
|
18308
|
-
return;
|
|
18309
|
-
}
|
|
18310
|
-
if (perseusStr === this.previousContent) {
|
|
18311
|
-
return;
|
|
18312
|
-
}
|
|
18313
|
-
this.previousContent = perseusStr;
|
|
18314
|
-
if (perseusStr === "") {
|
|
18315
|
-
onLintErrorsGenerated([]);
|
|
18316
|
-
return;
|
|
18317
|
-
}
|
|
18318
|
-
fetch("/api/internal/translations/lint_poentry?preview=1&lang=en", {
|
|
18319
|
-
headers: {
|
|
18320
|
-
"Content-Type": "application/json"
|
|
18321
|
-
},
|
|
18322
|
-
body: JSON.stringify({
|
|
18323
|
-
// The poentry linter verifies that the translation from
|
|
18324
|
-
// the original text to the translated text is sane. We
|
|
18325
|
-
// want to ensure that the translation from English to
|
|
18326
|
-
// English is sane, so msgid === mstr.
|
|
18327
|
-
msgid: perseusStr,
|
|
18328
|
-
// msgid is the original text
|
|
18329
|
-
msgstr: perseusStr,
|
|
18330
|
-
// msgstr is the translated text
|
|
18331
|
-
format: "perseus_text",
|
|
18332
|
-
filename: ""
|
|
18333
|
-
}),
|
|
18334
|
-
method: "POST"
|
|
18335
|
-
}).then(response => {
|
|
18336
|
-
if (response.status >= 400) {
|
|
18337
|
-
return {
|
|
18338
|
-
status: "error",
|
|
18339
|
-
message: "Could not run i18n linter."
|
|
18340
|
-
};
|
|
18341
|
-
}
|
|
18342
|
-
return response.json();
|
|
18343
|
-
}, rejection => {
|
|
18344
|
-
return {
|
|
18345
|
-
status: "error",
|
|
18346
|
-
message: "Could not run i18n linter."
|
|
18347
|
-
};
|
|
18348
|
-
}).then(json => {
|
|
18349
|
-
if (json.status === "error") {
|
|
18350
|
-
onLintErrorsGenerated(["Some part of this text makes it untranslatable. " + "The specific message from the i18n linter was: " + json.message.replace(/\n/g, " ")]);
|
|
18351
|
-
} else {
|
|
18352
|
-
onLintErrorsGenerated([]);
|
|
18353
|
-
}
|
|
18354
|
-
});
|
|
18355
|
-
}, DEBOUNCE_TIMEOUT);
|
|
18356
|
-
|
|
18357
|
-
/**
|
|
18358
|
-
* Applies an array of errors generated by linters without position
|
|
18359
|
-
* information (TranslationLinter and the legacy getSaveWarnings()) to the
|
|
18360
|
-
* top of a Perseus tree.
|
|
18361
|
-
*/
|
|
18362
|
-
applyLintErrors(parsedMarkdown, translationLintErrors) {
|
|
18363
|
-
// These lint errors do not have position data associated with
|
|
18364
|
-
// them, so we just plop them at the top.
|
|
18365
|
-
if (translationLintErrors.length) {
|
|
18366
|
-
const errorText = translationLintErrors.join("\n\n");
|
|
18367
|
-
parsedMarkdown.unshift({
|
|
18368
|
-
content: {
|
|
18369
|
-
type: "text",
|
|
18370
|
-
content: ""
|
|
18371
|
-
},
|
|
18372
|
-
insideTable: false,
|
|
18373
|
-
message: errorText,
|
|
18374
|
-
ruleName: "legacy-error",
|
|
18375
|
-
severity: PerseusLinter.Rule.Severity.ERROR,
|
|
18376
|
-
type: "lint"
|
|
18377
|
-
});
|
|
18378
|
-
}
|
|
18379
|
-
}
|
|
18380
|
-
}
|
|
18381
|
-
|
|
18382
|
-
/**
|
|
18383
|
-
* Preprocess TeX code to convert things that MathJax doesn't know how to handle
|
|
18384
|
-
* to things is does.
|
|
18385
|
-
*/
|
|
18386
|
-
var preprocessTex = (texCode => texCode
|
|
18387
|
-
// Replace uses of \begin{align}...\end{align} which KaTeX doesn't
|
|
18388
|
-
// support (yet) with \begin{aligned}...\end{aligned} which renders
|
|
18389
|
-
// the same is supported by KaTeX. It does the same for align*.
|
|
18390
|
-
// TODO(kevinb) update content to use aligned instead of align.
|
|
18391
|
-
// TODO(LEMS-1608) Remove this replacement as MathJax supports the
|
|
18392
|
-
// "align" macro correctly (and, in fact, it is not synonymous with
|
|
18393
|
-
// "aligned").
|
|
18394
|
-
.replace(/\{align[*]?\}/g, "{aligned}")
|
|
18395
|
-
// Replace non-breaking spaces with regular spaces.
|
|
18396
|
-
.replace(/[\u00a0]/g, " "));
|
|
18397
|
-
|
|
18398
|
-
const {
|
|
18399
|
-
interactiveSizes
|
|
18400
|
-
} = constants;
|
|
18401
|
-
|
|
18402
|
-
// Note: these size cutoffs represent content-width cutoffs as specified in
|
|
18403
|
-
// http://zpl.io/1mVmvU (broken link - we no longer use Zeplin)
|
|
18404
|
-
// TODO(benkomalo): these values aren't used in JS outside of this file, but
|
|
18405
|
-
// are coupled to the values in styles/styles.less - DRY it up at some point
|
|
18406
|
-
const smMax = articleMaxWidthTableInPx;
|
|
18407
|
-
const mdMax = articleMaxWidthInPx;
|
|
18408
|
-
const containerSizeClass = {
|
|
18409
|
-
SMALL: "small",
|
|
18410
|
-
MEDIUM: "medium",
|
|
18411
|
-
LARGE: "large",
|
|
18412
|
-
XLARGE: "xlarge"
|
|
18413
|
-
};
|
|
18414
|
-
const getClassFromWidth = width => {
|
|
18415
|
-
if (!width) {
|
|
18416
|
-
return containerSizeClass.MEDIUM;
|
|
18417
|
-
}
|
|
18418
|
-
if (width <= smMax) {
|
|
18419
|
-
return containerSizeClass.SMALL;
|
|
18420
|
-
}
|
|
18421
|
-
if (width <= mdMax) {
|
|
18422
|
-
return containerSizeClass.MEDIUM;
|
|
18423
|
-
}
|
|
18424
|
-
return containerSizeClass.LARGE;
|
|
18425
|
-
};
|
|
18426
|
-
const getInteractiveBoxFromSizeClass = sizeClass => {
|
|
18427
|
-
if (sizeClass === containerSizeClass.SMALL) {
|
|
18428
|
-
return [interactiveSizes.defaultBoxSizeSmall, interactiveSizes.defaultBoxSizeSmall];
|
|
18429
|
-
}
|
|
18430
|
-
return [interactiveSizes.defaultBoxSize, interactiveSizes.defaultBoxSize];
|
|
18431
|
-
};
|
|
18432
|
-
|
|
18433
18079
|
class WidgetContainer extends React__namespace.Component {
|
|
18434
18080
|
widgetRef = /*#__PURE__*/React__namespace.createRef();
|
|
18435
18081
|
static defaultProps = {
|
|
@@ -18502,7 +18148,7 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
18502
18148
|
}
|
|
18503
18149
|
let alignment = this.state.widgetProps.alignment;
|
|
18504
18150
|
if (alignment === "default") {
|
|
18505
|
-
alignment = getDefaultAlignment(type);
|
|
18151
|
+
alignment = perseusCore.CoreWidgetRegistry.getDefaultAlignment(type);
|
|
18506
18152
|
}
|
|
18507
18153
|
className += " widget-" + alignment;
|
|
18508
18154
|
const apiOptions = this.state.widgetProps.apiOptions;
|
|
@@ -18510,6 +18156,7 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
18510
18156
|
// Hack to prevent interaction with static widgets: we overlay a big
|
|
18511
18157
|
// div on top of the widget and overflow: hidden the container.
|
|
18512
18158
|
// Ideally widgets themselves should know how to prevent interaction.
|
|
18159
|
+
// UPDATE HTML5: `inert` on the underlying div would be better
|
|
18513
18160
|
const isStatic = this.state.widgetProps.static || apiOptions.readOnly;
|
|
18514
18161
|
const staticContainerStyles = {
|
|
18515
18162
|
position: "relative",
|
|
@@ -18521,7 +18168,7 @@ class WidgetContainer extends React__namespace.Component {
|
|
|
18521
18168
|
position: "absolute",
|
|
18522
18169
|
top: 0,
|
|
18523
18170
|
left: 0,
|
|
18524
|
-
zIndex:
|
|
18171
|
+
zIndex: 3
|
|
18525
18172
|
};
|
|
18526
18173
|
|
|
18527
18174
|
// Some widgets may include strings of markdown that we may
|
|
@@ -18787,7 +18434,7 @@ class Renderer extends React__namespace.Component {
|
|
|
18787
18434
|
};
|
|
18788
18435
|
};
|
|
18789
18436
|
_getInitialWidgetState = props => {
|
|
18790
|
-
const allWidgetInfo = getUpgradedWidgetOptions(props.widgets);
|
|
18437
|
+
const allWidgetInfo = perseusCore.getUpgradedWidgetOptions(props.widgets);
|
|
18791
18438
|
return {
|
|
18792
18439
|
widgetInfo: allWidgetInfo,
|
|
18793
18440
|
widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props)
|
|
@@ -19699,7 +19346,7 @@ class Renderer extends React__namespace.Component {
|
|
|
19699
19346
|
* empty if the graph is in the starting state.
|
|
19700
19347
|
*/
|
|
19701
19348
|
emptyWidgets() {
|
|
19702
|
-
return emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.
|
|
19349
|
+
return perseusScore.emptyWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
|
|
19703
19350
|
}
|
|
19704
19351
|
_setWidgetProps = (id, newProps, cb, silent) => {
|
|
19705
19352
|
this.setState(prevState => {
|
|
@@ -19829,8 +19476,8 @@ class Renderer extends React__namespace.Component {
|
|
|
19829
19476
|
* @deprecated use scorePerseusItem
|
|
19830
19477
|
*/
|
|
19831
19478
|
score() {
|
|
19832
|
-
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.
|
|
19833
|
-
const combinedScore = flattenScores(scores);
|
|
19479
|
+
const scores = perseusScore.scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.context.locale);
|
|
19480
|
+
const combinedScore = perseusScore.flattenScores(scores);
|
|
19834
19481
|
return combinedScore;
|
|
19835
19482
|
}
|
|
19836
19483
|
|
|
@@ -20351,7 +19998,7 @@ class InputWithExamples extends React__namespace.Component {
|
|
|
20351
19998
|
};
|
|
20352
19999
|
_getInputClassName = () => {
|
|
20353
20000
|
// Otherwise, we need to add these INPUT and FOCUSED tags here.
|
|
20354
|
-
let className = ClassNames.INPUT
|
|
20001
|
+
let className = ClassNames.INPUT;
|
|
20355
20002
|
if (this.state.focused) {
|
|
20356
20003
|
className += " " + ClassNames.FOCUSED;
|
|
20357
20004
|
}
|
|
@@ -20744,14 +20391,10 @@ const propTransform$3 = widgetOptions => {
|
|
|
20744
20391
|
var InputNumber$1 = {
|
|
20745
20392
|
name: "input-number",
|
|
20746
20393
|
displayName: "Input number (deprecated - use numeric input instead)",
|
|
20747
|
-
defaultAlignment: "inline-block",
|
|
20748
20394
|
hidden: true,
|
|
20749
20395
|
widget: InputNumber,
|
|
20750
20396
|
transform: propTransform$3,
|
|
20751
20397
|
isLintable: true,
|
|
20752
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
20753
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
20754
|
-
scorer: perseusScore.scoreInputNumber,
|
|
20755
20398
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
20756
20399
|
if (rubric.value == null) {
|
|
20757
20400
|
return;
|
|
@@ -21003,14 +20646,11 @@ const propsTransform = function (widgetOptions) {
|
|
|
21003
20646
|
var NumericInput$1 = {
|
|
21004
20647
|
name: "numeric-input",
|
|
21005
20648
|
displayName: "Numeric input",
|
|
21006
|
-
defaultAlignment: "inline-block",
|
|
21007
20649
|
accessible: true,
|
|
21008
20650
|
widget: NumericInput,
|
|
21009
20651
|
transform: propsTransform,
|
|
21010
20652
|
isLintable: true,
|
|
21011
|
-
|
|
21012
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
21013
|
-
scorer: perseusScore.scoreNumericInput,
|
|
20653
|
+
getPublicWidgetOptions: perseusCore.getNumericInputPublicWidgetOptions,
|
|
21014
20654
|
// TODO(LEMS-2656): remove TS suppression
|
|
21015
20655
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
21016
20656
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
@@ -21071,24 +20711,6 @@ const getPromptJSON$p = renderProps => {
|
|
|
21071
20711
|
};
|
|
21072
20712
|
};
|
|
21073
20713
|
|
|
21074
|
-
/**
|
|
21075
|
-
* Several widgets don't have "right"/"wrong" scoring logic,
|
|
21076
|
-
* so this just says to move on past those widgets
|
|
21077
|
-
*
|
|
21078
|
-
* TODO(LEMS-2543) widgets that use this probably shouldn't have any
|
|
21079
|
-
* scoring logic and the thing scoring an exercise
|
|
21080
|
-
* should just know to skip these
|
|
21081
|
-
*/
|
|
21082
|
-
function scoreNoop() {
|
|
21083
|
-
let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
21084
|
-
return {
|
|
21085
|
-
type: "points",
|
|
21086
|
-
earned: points,
|
|
21087
|
-
total: points,
|
|
21088
|
-
message: null
|
|
21089
|
-
};
|
|
21090
|
-
}
|
|
21091
|
-
|
|
21092
20714
|
/**
|
|
21093
20715
|
* Utility functions for manipulating ranges of highlightable content.
|
|
21094
20716
|
*/
|
|
@@ -23266,8 +22888,7 @@ var Passage$1 = {
|
|
|
23266
22888
|
transform: editorProps => {
|
|
23267
22889
|
return ___default["default"].pick(editorProps, "passageTitle", "passageText", "footnotes", "showLineNumbers");
|
|
23268
22890
|
},
|
|
23269
|
-
isLintable: true
|
|
23270
|
-
scorer: () => scoreNoop()
|
|
22891
|
+
isLintable: true
|
|
23271
22892
|
};
|
|
23272
22893
|
|
|
23273
22894
|
/**
|
|
@@ -23393,16 +23014,13 @@ var PassageRef$1 = {
|
|
|
23393
23014
|
name: "passage-ref",
|
|
23394
23015
|
displayName: "PassageRef (SAT only)",
|
|
23395
23016
|
hidden: true,
|
|
23396
|
-
defaultAlignment: "inline",
|
|
23397
23017
|
widget: PassageRef,
|
|
23398
23018
|
transform: widgetOptions => ({
|
|
23399
23019
|
passageNumber: widgetOptions.passageNumber,
|
|
23400
23020
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23401
23021
|
summaryText: widgetOptions.summaryText
|
|
23402
23022
|
}),
|
|
23403
|
-
version: perseusCore.passageRefLogic.version
|
|
23404
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23405
|
-
scorer: () => scoreNoop()
|
|
23023
|
+
version: perseusCore.passageRefLogic.version
|
|
23406
23024
|
};
|
|
23407
23025
|
|
|
23408
23026
|
/**
|
|
@@ -23444,109 +23062,6 @@ var mediaQueries = {
|
|
|
23444
23062
|
xlOrLarger: `@media screen and (min-width: ${pureXlMin})`
|
|
23445
23063
|
};
|
|
23446
23064
|
|
|
23447
|
-
const {
|
|
23448
|
-
zIndexAboveScratchpad,
|
|
23449
|
-
zIndexInteractiveComponent,
|
|
23450
|
-
radioBorderColor,
|
|
23451
|
-
checkedColor,
|
|
23452
|
-
circleSize,
|
|
23453
|
-
radioMarginWidth
|
|
23454
|
-
} = constants;
|
|
23455
|
-
var sharedStyles = aphrodite.StyleSheet.create({
|
|
23456
|
-
perseusInteractive: {
|
|
23457
|
-
zIndex: zIndexInteractiveComponent,
|
|
23458
|
-
position: "relative"
|
|
23459
|
-
},
|
|
23460
|
-
aboveScratchpad: {
|
|
23461
|
-
position: "relative",
|
|
23462
|
-
zIndex: zIndexAboveScratchpad
|
|
23463
|
-
},
|
|
23464
|
-
blankBackground: {
|
|
23465
|
-
// TODO(emily): Use WB colors?
|
|
23466
|
-
backgroundColor: "#FDFDFD"
|
|
23467
|
-
},
|
|
23468
|
-
perseusSrOnly: {
|
|
23469
|
-
border: 0,
|
|
23470
|
-
clip: "rect(0,0,0,0)",
|
|
23471
|
-
height: 1,
|
|
23472
|
-
margin: -1,
|
|
23473
|
-
overflow: "hidden",
|
|
23474
|
-
padding: 0,
|
|
23475
|
-
position: "absolute",
|
|
23476
|
-
width: 1
|
|
23477
|
-
},
|
|
23478
|
-
responsiveLabel: {
|
|
23479
|
-
// NOTE(charlie): The values used here should be kept in sync with the
|
|
23480
|
-
// caption text sizing in Perseus' styles.less.
|
|
23481
|
-
// TODO(charlie): Migrate the captions over to using this style.
|
|
23482
|
-
[mediaQueries.smOrSmaller]: {
|
|
23483
|
-
fontSize: 14,
|
|
23484
|
-
lineHeight: 1.3
|
|
23485
|
-
},
|
|
23486
|
-
[mediaQueries.md]: {
|
|
23487
|
-
fontSize: 17,
|
|
23488
|
-
lineHeight: 1.4
|
|
23489
|
-
},
|
|
23490
|
-
[mediaQueries.lgOrLarger]: {
|
|
23491
|
-
fontSize: 20,
|
|
23492
|
-
lineHeight: 1.4
|
|
23493
|
-
}
|
|
23494
|
-
},
|
|
23495
|
-
responsiveInput: {
|
|
23496
|
-
display: "inline-block",
|
|
23497
|
-
WebkitAppearance: "none",
|
|
23498
|
-
appearance: "none",
|
|
23499
|
-
"::-ms-check": {
|
|
23500
|
-
display: "none"
|
|
23501
|
-
},
|
|
23502
|
-
backgroundColor: "#fff",
|
|
23503
|
-
border: "2px solid #fff",
|
|
23504
|
-
boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
|
|
23505
|
-
outline: "none",
|
|
23506
|
-
boxSizing: "border-box",
|
|
23507
|
-
flexShrink: 0,
|
|
23508
|
-
marginBottom: 1,
|
|
23509
|
-
marginLeft: 1,
|
|
23510
|
-
marginRight: 1,
|
|
23511
|
-
marginTop: 1,
|
|
23512
|
-
height: circleSize - 2,
|
|
23513
|
-
width: circleSize - 2
|
|
23514
|
-
},
|
|
23515
|
-
responsiveRadioInput: {
|
|
23516
|
-
borderRadius: "50%",
|
|
23517
|
-
":checked": {
|
|
23518
|
-
backgroundColor: checkedColor,
|
|
23519
|
-
border: "none",
|
|
23520
|
-
borderRadius: "50%",
|
|
23521
|
-
boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
|
|
23522
|
-
marginTop: radioMarginWidth,
|
|
23523
|
-
marginBottom: radioMarginWidth,
|
|
23524
|
-
marginLeft: radioMarginWidth,
|
|
23525
|
-
marginRight: radioMarginWidth,
|
|
23526
|
-
height: circleSize - 2 * radioMarginWidth,
|
|
23527
|
-
width: circleSize - 2 * radioMarginWidth
|
|
23528
|
-
}
|
|
23529
|
-
},
|
|
23530
|
-
responsiveRadioInputActive: {
|
|
23531
|
-
backgroundColor: "#fff",
|
|
23532
|
-
border: "2px solid #fff",
|
|
23533
|
-
borderRadius: "50%",
|
|
23534
|
-
boxShadow: `0 0px 0px 2px ${checkedColor}`,
|
|
23535
|
-
marginTop: radioMarginWidth,
|
|
23536
|
-
marginBottom: radioMarginWidth,
|
|
23537
|
-
marginLeft: radioMarginWidth,
|
|
23538
|
-
marginRight: radioMarginWidth,
|
|
23539
|
-
height: circleSize - 2 * radioMarginWidth,
|
|
23540
|
-
width: circleSize - 2 * radioMarginWidth,
|
|
23541
|
-
":checked": {
|
|
23542
|
-
backgroundColor: "#fff"
|
|
23543
|
-
}
|
|
23544
|
-
},
|
|
23545
|
-
disableTextSelection: {
|
|
23546
|
-
userSelect: "none"
|
|
23547
|
-
}
|
|
23548
|
-
});
|
|
23549
|
-
|
|
23550
23065
|
/**
|
|
23551
23066
|
* A small collection of useful scrolling utility functions.
|
|
23552
23067
|
*/
|
|
@@ -23923,8 +23438,8 @@ const styles$s = aphrodite.StyleSheet.create({
|
|
|
23923
23438
|
crossOutLine: {
|
|
23924
23439
|
// Center the icon within the container.
|
|
23925
23440
|
position: "absolute",
|
|
23926
|
-
top: `
|
|
23927
|
-
left: `
|
|
23441
|
+
top: `2px`,
|
|
23442
|
+
left: `2px`
|
|
23928
23443
|
}
|
|
23929
23444
|
});
|
|
23930
23445
|
|
|
@@ -23951,43 +23466,6 @@ function ChoiceInner(props) {
|
|
|
23951
23466
|
}
|
|
23952
23467
|
return /*#__PURE__*/React__namespace.createElement(InlineIcon, iconMinus);
|
|
23953
23468
|
}
|
|
23954
|
-
|
|
23955
|
-
// Handle dynamic styling of the multiple choice icon. Most
|
|
23956
|
-
// MC icon styles are constant, but we do allow the caller
|
|
23957
|
-
// to specify the selected color, and thus must control styles
|
|
23958
|
-
// related to the selected state dynamically.
|
|
23959
|
-
function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
|
|
23960
|
-
let backgroundColor;
|
|
23961
|
-
let borderColor;
|
|
23962
|
-
let color;
|
|
23963
|
-
if (!showCorrectness && pressed) {
|
|
23964
|
-
borderColor = wonderBlocksTokens.color.blue;
|
|
23965
|
-
color = wonderBlocksTokens.color.blue;
|
|
23966
|
-
backgroundColor = "transparent";
|
|
23967
|
-
} else if (checked) {
|
|
23968
|
-
const bg = showCorrectness && correct ? wonderBlocksTokens.color.green : wonderBlocksTokens.color.blue;
|
|
23969
|
-
color = white;
|
|
23970
|
-
backgroundColor = bg;
|
|
23971
|
-
borderColor = bg;
|
|
23972
|
-
} else {
|
|
23973
|
-
borderColor = wonderBlocksTokens.color.offBlack64;
|
|
23974
|
-
color = wonderBlocksTokens.color.offBlack64;
|
|
23975
|
-
}
|
|
23976
|
-
|
|
23977
|
-
// define shape
|
|
23978
|
-
let borderRadius;
|
|
23979
|
-
if (multipleSelect) {
|
|
23980
|
-
borderRadius = 3;
|
|
23981
|
-
} else {
|
|
23982
|
-
borderRadius = CHOICE_ICON_SIZE;
|
|
23983
|
-
}
|
|
23984
|
-
return {
|
|
23985
|
-
backgroundColor,
|
|
23986
|
-
borderColor,
|
|
23987
|
-
color,
|
|
23988
|
-
borderRadius
|
|
23989
|
-
};
|
|
23990
|
-
}
|
|
23991
23469
|
const ChoiceIcon = function (props) {
|
|
23992
23470
|
const {
|
|
23993
23471
|
checked,
|
|
@@ -23999,9 +23477,41 @@ const ChoiceIcon = function (props) {
|
|
|
23999
23477
|
multipleSelect,
|
|
24000
23478
|
pos,
|
|
24001
23479
|
previouslyAnswered,
|
|
23480
|
+
// Used in "review mode"/"show rationale" to show that option was previously chosen
|
|
24002
23481
|
pressed
|
|
24003
23482
|
} = props;
|
|
24004
|
-
|
|
23483
|
+
|
|
23484
|
+
// Accounts for incorrect choice still displaying as learner tries again
|
|
23485
|
+
const choiceIsChecked = checked || showCorrectness && !correct && previouslyAnswered;
|
|
23486
|
+
|
|
23487
|
+
// Core styling
|
|
23488
|
+
const choiceStyling = [styles$r.choiceBase, multipleSelect ? styles$r.multiSelectShape : styles$r.singleSelectShape, showCorrectness ? styles$r.choiceHasIcon : styles$r.choiceHasLetter, choiceIsChecked ? styles$r.choiceIsChecked : styles$r.choiceIsUnchecked];
|
|
23489
|
+
|
|
23490
|
+
// Color styling
|
|
23491
|
+
// Handle dynamic styling of the multiple choice icon. Most
|
|
23492
|
+
// MC icon styles are constant, but we do allow the caller
|
|
23493
|
+
// to specify the selected color, and thus must control styles
|
|
23494
|
+
// related to the selected state dynamically.
|
|
23495
|
+
let crossOutColor;
|
|
23496
|
+
if (showCorrectness && correct && checked) {
|
|
23497
|
+
choiceStyling.push(styles$r.choiceCorrect);
|
|
23498
|
+
crossOutColor = wonderBlocksTokens.color.green;
|
|
23499
|
+
} else if (showCorrectness && !correct && (checked || previouslyAnswered)) {
|
|
23500
|
+
choiceStyling.push(styles$r.choiceIncorrect);
|
|
23501
|
+
crossOutColor = wonderBlocksTokens.color.red;
|
|
23502
|
+
} else if (checked) {
|
|
23503
|
+
// Show filled neutral blue color (showCorrectness is false)
|
|
23504
|
+
choiceStyling.push(styles$r.choiceNeutral);
|
|
23505
|
+
crossOutColor = wonderBlocksTokens.color.blue;
|
|
23506
|
+
} else if (pressed) {
|
|
23507
|
+
// Show outlined neutral blue color (showCorrectness is false)
|
|
23508
|
+
choiceStyling.push(styles$r.activeNeutral);
|
|
23509
|
+
crossOutColor = wonderBlocksTokens.color.blue;
|
|
23510
|
+
} else {
|
|
23511
|
+
// choice is not checked
|
|
23512
|
+
choiceStyling.push(styles$r.uncheckedColors);
|
|
23513
|
+
crossOutColor = wonderBlocksTokens.color.offBlack64;
|
|
23514
|
+
}
|
|
24005
23515
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
24006
23516
|
className: aphrodite.css(styles$t.iconWrapper)
|
|
24007
23517
|
}, /*#__PURE__*/React__namespace.createElement(FocusRing, {
|
|
@@ -24009,9 +23519,8 @@ const ChoiceIcon = function (props) {
|
|
|
24009
23519
|
visible: focused || hovered,
|
|
24010
23520
|
multipleSelect: multipleSelect
|
|
24011
23521
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
24012
|
-
style: dynamicStyles,
|
|
24013
23522
|
"data-testid": "choice-icon__library-choice-icon",
|
|
24014
|
-
className: aphrodite.css(
|
|
23523
|
+
className: aphrodite.css(...choiceStyling)
|
|
24015
23524
|
// used in BaseRadio editMode to check
|
|
24016
23525
|
// if we actually clicked on the radio icon
|
|
24017
23526
|
,
|
|
@@ -24023,7 +23532,7 @@ const ChoiceIcon = function (props) {
|
|
|
24023
23532
|
showCorrectness: showCorrectness,
|
|
24024
23533
|
correct: correct
|
|
24025
23534
|
})))), crossedOut && /*#__PURE__*/React__namespace.createElement(CrossOutLine, {
|
|
24026
|
-
color:
|
|
23535
|
+
color: crossOutColor
|
|
24027
23536
|
}));
|
|
24028
23537
|
};
|
|
24029
23538
|
const styles$r = aphrodite.StyleSheet.create({
|
|
@@ -24035,8 +23544,7 @@ const styles$r = aphrodite.StyleSheet.create({
|
|
|
24035
23544
|
alignItems: "center",
|
|
24036
23545
|
justifyContent: "center"
|
|
24037
23546
|
},
|
|
24038
|
-
|
|
24039
|
-
// Make the circle
|
|
23547
|
+
choiceBase: {
|
|
24040
23548
|
width: CHOICE_ICON_SIZE,
|
|
24041
23549
|
height: CHOICE_ICON_SIZE,
|
|
24042
23550
|
boxSizing: "border-box",
|
|
@@ -24048,30 +23556,48 @@ const styles$r = aphrodite.StyleSheet.create({
|
|
|
24048
23556
|
// "bold font family" so that characters which fall back to the default
|
|
24049
23557
|
// font get bolded too.
|
|
24050
23558
|
fontWeight: "bold",
|
|
24051
|
-
fontSize: 12,
|
|
24052
23559
|
// Center the icon wrapper.
|
|
24053
23560
|
display: "flex",
|
|
24054
23561
|
alignItems: "center",
|
|
24055
|
-
justifyContent: "center"
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
lineHeight: "1px"
|
|
24062
|
-
},
|
|
24063
|
-
circleCorrect: {
|
|
23562
|
+
justifyContent: "center"
|
|
23563
|
+
},
|
|
23564
|
+
choiceHasLetter: {
|
|
23565
|
+
fontSize: 12
|
|
23566
|
+
},
|
|
23567
|
+
choiceHasIcon: {
|
|
24064
23568
|
fontSize: CHOICE_ICON_SIZE
|
|
24065
23569
|
},
|
|
24066
|
-
|
|
24067
|
-
|
|
24068
|
-
|
|
24069
|
-
|
|
23570
|
+
choiceIsChecked: {
|
|
23571
|
+
color: wonderBlocksTokens.color.white
|
|
23572
|
+
},
|
|
23573
|
+
choiceIsUnchecked: {
|
|
23574
|
+
color: wonderBlocksTokens.color.offBlack64
|
|
23575
|
+
},
|
|
23576
|
+
choiceCorrect: {
|
|
23577
|
+
backgroundColor: wonderBlocksTokens.color.green,
|
|
23578
|
+
borderColor: wonderBlocksTokens.color.green
|
|
24070
23579
|
},
|
|
24071
|
-
|
|
23580
|
+
choiceIncorrect: {
|
|
24072
23581
|
backgroundColor: wonderBlocksTokens.color.red,
|
|
24073
|
-
borderColor: wonderBlocksTokens.color.red
|
|
24074
|
-
|
|
23582
|
+
borderColor: wonderBlocksTokens.color.red
|
|
23583
|
+
},
|
|
23584
|
+
choiceNeutral: {
|
|
23585
|
+
backgroundColor: wonderBlocksTokens.color.blue,
|
|
23586
|
+
borderColor: wonderBlocksTokens.color.blue
|
|
23587
|
+
},
|
|
23588
|
+
activeNeutral: {
|
|
23589
|
+
color: wonderBlocksTokens.color.blue,
|
|
23590
|
+
borderColor: wonderBlocksTokens.color.blue,
|
|
23591
|
+
backgroundColor: "transparent"
|
|
23592
|
+
},
|
|
23593
|
+
multiSelectShape: {
|
|
23594
|
+
borderRadius: 3
|
|
23595
|
+
},
|
|
23596
|
+
singleSelectShape: {
|
|
23597
|
+
borderRadius: CHOICE_ICON_SIZE
|
|
23598
|
+
},
|
|
23599
|
+
uncheckedColors: {
|
|
23600
|
+
borderColor: wonderBlocksTokens.color.offBlack64
|
|
24075
23601
|
}
|
|
24076
23602
|
});
|
|
24077
23603
|
|
|
@@ -24615,7 +24141,7 @@ const BaseRadio = function (_ref) {
|
|
|
24615
24141
|
const aphroditeClassName = checked => {
|
|
24616
24142
|
// Whether or not to show correctness borders
|
|
24617
24143
|
// for this choice and the next choice.
|
|
24618
|
-
return aphrodite.css(
|
|
24144
|
+
return aphrodite.css(styles$o.item, styles$o.responsiveItem, checked && styles$o.selectedItem, checked && choice.highlighted && styles$o.aboveBackdrop, checked && choice.highlighted && apiOptions.isMobile && styles$o.aboveBackdropMobile, nextChoiceHighlighted && apiOptions.isMobile && styles$o.nextHighlighted);
|
|
24619
24145
|
};
|
|
24620
24146
|
|
|
24621
24147
|
// HACK(abdulrahman): Preloads the selection-state
|
|
@@ -25220,17 +24746,105 @@ var Radio = {
|
|
|
25220
24746
|
version: perseusCore.radioLogic.version,
|
|
25221
24747
|
propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
|
|
25222
24748
|
isLintable: true,
|
|
25223
|
-
|
|
25224
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25225
|
-
scorer: perseusScore.scoreRadio,
|
|
25226
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25227
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25228
|
-
validator: perseusScore.validateRadio
|
|
24749
|
+
getPublicWidgetOptions: perseusCore.getRadioPublicWidgetOptions
|
|
25229
24750
|
};
|
|
25230
24751
|
|
|
25231
24752
|
/* eslint-disable import/no-named-as-default */
|
|
25232
24753
|
var basicWidgets = [Radio, InputNumber$1, NumericInput$1, Expression$1];
|
|
25233
24754
|
|
|
24755
|
+
const {
|
|
24756
|
+
radioBorderColor,
|
|
24757
|
+
checkedColor,
|
|
24758
|
+
circleSize,
|
|
24759
|
+
radioMarginWidth
|
|
24760
|
+
} = constants;
|
|
24761
|
+
var sharedStyles = aphrodite.StyleSheet.create({
|
|
24762
|
+
blankBackground: {
|
|
24763
|
+
// TODO(emily): Use WB colors?
|
|
24764
|
+
backgroundColor: "#FDFDFD"
|
|
24765
|
+
},
|
|
24766
|
+
perseusSrOnly: {
|
|
24767
|
+
border: 0,
|
|
24768
|
+
clip: "rect(0,0,0,0)",
|
|
24769
|
+
height: 1,
|
|
24770
|
+
margin: -1,
|
|
24771
|
+
overflow: "hidden",
|
|
24772
|
+
padding: 0,
|
|
24773
|
+
position: "absolute",
|
|
24774
|
+
width: 1
|
|
24775
|
+
},
|
|
24776
|
+
responsiveLabel: {
|
|
24777
|
+
// NOTE(charlie): The values used here should be kept in sync with the
|
|
24778
|
+
// caption text sizing in Perseus' styles.less.
|
|
24779
|
+
// TODO(charlie): Migrate the captions over to using this style.
|
|
24780
|
+
[mediaQueries.smOrSmaller]: {
|
|
24781
|
+
fontSize: 14,
|
|
24782
|
+
lineHeight: 1.3
|
|
24783
|
+
},
|
|
24784
|
+
[mediaQueries.md]: {
|
|
24785
|
+
fontSize: 17,
|
|
24786
|
+
lineHeight: 1.4
|
|
24787
|
+
},
|
|
24788
|
+
[mediaQueries.lgOrLarger]: {
|
|
24789
|
+
fontSize: 20,
|
|
24790
|
+
lineHeight: 1.4
|
|
24791
|
+
}
|
|
24792
|
+
},
|
|
24793
|
+
responsiveInput: {
|
|
24794
|
+
display: "inline-block",
|
|
24795
|
+
WebkitAppearance: "none",
|
|
24796
|
+
appearance: "none",
|
|
24797
|
+
"::-ms-check": {
|
|
24798
|
+
display: "none"
|
|
24799
|
+
},
|
|
24800
|
+
backgroundColor: "#fff",
|
|
24801
|
+
border: "2px solid #fff",
|
|
24802
|
+
boxShadow: `0 0px 0px 1px ${radioBorderColor}`,
|
|
24803
|
+
outline: "none",
|
|
24804
|
+
boxSizing: "border-box",
|
|
24805
|
+
flexShrink: 0,
|
|
24806
|
+
marginBottom: 1,
|
|
24807
|
+
marginLeft: 1,
|
|
24808
|
+
marginRight: 1,
|
|
24809
|
+
marginTop: 1,
|
|
24810
|
+
height: circleSize - 2,
|
|
24811
|
+
width: circleSize - 2
|
|
24812
|
+
},
|
|
24813
|
+
responsiveRadioInput: {
|
|
24814
|
+
borderRadius: "50%",
|
|
24815
|
+
":checked": {
|
|
24816
|
+
backgroundColor: checkedColor,
|
|
24817
|
+
border: "none",
|
|
24818
|
+
borderRadius: "50%",
|
|
24819
|
+
boxShadow: `inset 0px 0px 0px 2px white, ` + `0 0px 0px 2px ${checkedColor}`,
|
|
24820
|
+
marginTop: radioMarginWidth,
|
|
24821
|
+
marginBottom: radioMarginWidth,
|
|
24822
|
+
marginLeft: radioMarginWidth,
|
|
24823
|
+
marginRight: radioMarginWidth,
|
|
24824
|
+
height: circleSize - 2 * radioMarginWidth,
|
|
24825
|
+
width: circleSize - 2 * radioMarginWidth
|
|
24826
|
+
}
|
|
24827
|
+
},
|
|
24828
|
+
responsiveRadioInputActive: {
|
|
24829
|
+
backgroundColor: "#fff",
|
|
24830
|
+
border: "2px solid #fff",
|
|
24831
|
+
borderRadius: "50%",
|
|
24832
|
+
boxShadow: `0 0px 0px 2px ${checkedColor}`,
|
|
24833
|
+
marginTop: radioMarginWidth,
|
|
24834
|
+
marginBottom: radioMarginWidth,
|
|
24835
|
+
marginLeft: radioMarginWidth,
|
|
24836
|
+
marginRight: radioMarginWidth,
|
|
24837
|
+
height: circleSize - 2 * radioMarginWidth,
|
|
24838
|
+
width: circleSize - 2 * radioMarginWidth,
|
|
24839
|
+
":checked": {
|
|
24840
|
+
backgroundColor: "#fff"
|
|
24841
|
+
}
|
|
24842
|
+
},
|
|
24843
|
+
disableTextSelection: {
|
|
24844
|
+
userSelect: "none"
|
|
24845
|
+
}
|
|
24846
|
+
});
|
|
24847
|
+
|
|
25234
24848
|
const getPromptJSON$n = (renderProps, userInput) => {
|
|
25235
24849
|
return {
|
|
25236
24850
|
type: "categorizer",
|
|
@@ -25331,7 +24945,6 @@ class Categorizer extends React__namespace.Component {
|
|
|
25331
24945
|
className: "category " + aphrodite.css(styles$n.cell),
|
|
25332
24946
|
key: catNum
|
|
25333
24947
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
25334
|
-
className: ClassNames.INTERACTIVE,
|
|
25335
24948
|
role: "button",
|
|
25336
24949
|
"aria-label": catName,
|
|
25337
24950
|
onClick: () => this.onChange(itemNum, catNum)
|
|
@@ -25423,12 +25036,6 @@ var Categorizer$1 = {
|
|
|
25423
25036
|
return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
25424
25037
|
},
|
|
25425
25038
|
isLintable: true,
|
|
25426
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25427
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25428
|
-
scorer: perseusScore.scoreCategorizer,
|
|
25429
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25430
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25431
|
-
validator: perseusScore.validateCategorizer,
|
|
25432
25039
|
getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
|
|
25433
25040
|
};
|
|
25434
25041
|
|
|
@@ -25638,12 +25245,9 @@ const styles$m = aphrodite.StyleSheet.create({
|
|
|
25638
25245
|
var CSProgram$1 = {
|
|
25639
25246
|
name: "cs-program",
|
|
25640
25247
|
displayName: "CS Program",
|
|
25641
|
-
supportedAlignments: ["block", "full-width"],
|
|
25642
25248
|
widget: CSProgram,
|
|
25643
25249
|
hidden: true,
|
|
25644
|
-
|
|
25645
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25646
|
-
scorer: perseusScore.scoreCSProgram
|
|
25250
|
+
getPublicWidgetOptions: perseusCore.getCSProgramPublicWidgetOptions
|
|
25647
25251
|
};
|
|
25648
25252
|
|
|
25649
25253
|
const getPromptJSON$l = renderProps => {
|
|
@@ -25720,11 +25324,8 @@ var Definition$1 = {
|
|
|
25720
25324
|
name: "definition",
|
|
25721
25325
|
displayName: "Definition",
|
|
25722
25326
|
accessible: true,
|
|
25723
|
-
defaultAlignment: "inline",
|
|
25724
25327
|
widget: Definition,
|
|
25725
|
-
transform: x => x
|
|
25726
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
25727
|
-
scorer: () => scoreNoop()
|
|
25328
|
+
transform: x => x
|
|
25728
25329
|
};
|
|
25729
25330
|
|
|
25730
25331
|
// There are no required props, but this component can receive props from any
|
|
@@ -25751,9 +25352,7 @@ var DeprecatedStandin$1 = {
|
|
|
25751
25352
|
name: "deprecated-standin",
|
|
25752
25353
|
displayName: "Deprecated Standin",
|
|
25753
25354
|
widget: DeprecatedStandin,
|
|
25754
|
-
hidden: true
|
|
25755
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
25756
|
-
scorer: () => scoreNoop(1)
|
|
25355
|
+
hidden: true
|
|
25757
25356
|
};
|
|
25758
25357
|
|
|
25759
25358
|
const getPromptJSON$k = (renderProps, userInput) => {
|
|
@@ -25862,16 +25461,10 @@ const optionsTransform = widgetOptions => {
|
|
|
25862
25461
|
var Dropdown$1 = {
|
|
25863
25462
|
name: "dropdown",
|
|
25864
25463
|
displayName: "Drop down",
|
|
25865
|
-
defaultAlignment: "inline-block",
|
|
25866
25464
|
accessible: true,
|
|
25867
25465
|
widget: Dropdown,
|
|
25868
25466
|
transform: optionsTransform,
|
|
25869
|
-
|
|
25870
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25871
|
-
scorer: perseusScore.scoreDropdown,
|
|
25872
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
25873
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25874
|
-
validator: perseusScore.validateDropdown
|
|
25467
|
+
getPublicWidgetOptions: perseusCore.getDropdownPublicWidgetOptions
|
|
25875
25468
|
};
|
|
25876
25469
|
|
|
25877
25470
|
const getPromptJSON$j = renderProps => {
|
|
@@ -26030,12 +25623,9 @@ var Explanation$1 = {
|
|
|
26030
25623
|
name: "explanation",
|
|
26031
25624
|
displayName: "Explanation",
|
|
26032
25625
|
accessible: true,
|
|
26033
|
-
defaultAlignment: "inline",
|
|
26034
25626
|
widget: Explanation,
|
|
26035
25627
|
transform: ___default["default"].identity,
|
|
26036
|
-
isLintable: true
|
|
26037
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
26038
|
-
scorer: () => scoreNoop()
|
|
25628
|
+
isLintable: true
|
|
26039
25629
|
};
|
|
26040
25630
|
|
|
26041
25631
|
const getPromptJSON$i = (title, rendererJSON, hintRendererJSON) => {
|
|
@@ -27156,7 +26746,7 @@ class FunctionGrapher extends React__namespace.Component {
|
|
|
27156
26746
|
boxSizing: "initial"
|
|
27157
26747
|
}
|
|
27158
26748
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
27159
|
-
className: "graphie-container
|
|
26749
|
+
className: "graphie-container blank-background",
|
|
27160
26750
|
style: {
|
|
27161
26751
|
width: box[0],
|
|
27162
26752
|
height: box[1]
|
|
@@ -27284,8 +26874,7 @@ class Grapher extends React__namespace.Component {
|
|
|
27284
26874
|
const coords = this.props.plot.coords;
|
|
27285
26875
|
const asymptote = this.props.plot.asymptote;
|
|
27286
26876
|
const typeSelector = /*#__PURE__*/React__namespace.createElement("div", {
|
|
27287
|
-
style: typeSelectorStyle
|
|
27288
|
-
className: "above-scratchpad"
|
|
26877
|
+
style: typeSelectorStyle
|
|
27289
26878
|
}, /*#__PURE__*/React__namespace.createElement(ButtonGroup, {
|
|
27290
26879
|
value: type,
|
|
27291
26880
|
allowEmpty: true,
|
|
@@ -27368,9 +26957,7 @@ var Grapher$1 = {
|
|
|
27368
26957
|
widget: Grapher,
|
|
27369
26958
|
transform: propTransform$2,
|
|
27370
26959
|
staticTransform: staticTransform$4,
|
|
27371
|
-
|
|
27372
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
27373
|
-
scorer: perseusScore.scoreGrapher
|
|
26960
|
+
getPublicWidgetOptions: perseusCore.getGrapherPublicWidgetOptions
|
|
27374
26961
|
};
|
|
27375
26962
|
|
|
27376
26963
|
const getPromptJSON$f = rendererJSON => {
|
|
@@ -27387,24 +26974,6 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27387
26974
|
};
|
|
27388
26975
|
};
|
|
27389
26976
|
|
|
27390
|
-
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27391
|
-
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27392
|
-
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27393
|
-
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27394
|
-
return flattenScores(scores);
|
|
27395
|
-
}
|
|
27396
|
-
|
|
27397
|
-
function validateGroup(userInput, validationData, strings, locale) {
|
|
27398
|
-
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27399
|
-
if (emptyWidgets.length === 0) {
|
|
27400
|
-
return null;
|
|
27401
|
-
}
|
|
27402
|
-
return {
|
|
27403
|
-
type: "invalid",
|
|
27404
|
-
message: null
|
|
27405
|
-
};
|
|
27406
|
-
}
|
|
27407
|
-
|
|
27408
26977
|
// exports has no 'transform'
|
|
27409
26978
|
|
|
27410
26979
|
class Group extends React__namespace.Component {
|
|
@@ -27556,12 +27125,6 @@ var Group$1 = {
|
|
|
27556
27125
|
displayName: "Group (SAT only)",
|
|
27557
27126
|
widget: Group,
|
|
27558
27127
|
traverseChildWidgets: traverseChildWidgets,
|
|
27559
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
27560
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27561
|
-
scorer: scoreGroup,
|
|
27562
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
27563
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27564
|
-
validator: validateGroup,
|
|
27565
27128
|
hidden: true,
|
|
27566
27129
|
isLintable: true
|
|
27567
27130
|
};
|
|
@@ -27702,9 +27265,7 @@ var Iframe$1 = {
|
|
|
27702
27265
|
widget: Iframe,
|
|
27703
27266
|
// Let's not expose it to all content creators yet
|
|
27704
27267
|
hidden: true,
|
|
27705
|
-
|
|
27706
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
27707
|
-
scorer: perseusScore.scoreIframe
|
|
27268
|
+
getPublicWidgetOptions: perseusCore.getIFramePublicWidgetOptions
|
|
27708
27269
|
};
|
|
27709
27270
|
|
|
27710
27271
|
const getPromptJSON$d = renderProps => {
|
|
@@ -27726,15 +27287,13 @@ const defaultBackgroundImage$2 = {
|
|
|
27726
27287
|
width: 0,
|
|
27727
27288
|
height: 0
|
|
27728
27289
|
};
|
|
27729
|
-
const editorAlignments = ["block", "full-width"];
|
|
27730
|
-
const DEFAULT_ALIGNMENT = "block";
|
|
27731
27290
|
|
|
27732
27291
|
// there is no transform as part of exports
|
|
27733
27292
|
|
|
27734
27293
|
class ImageWidget extends React__namespace.Component {
|
|
27735
27294
|
static contextType = PerseusI18nContext;
|
|
27736
27295
|
static defaultProps = {
|
|
27737
|
-
alignment:
|
|
27296
|
+
alignment: "block",
|
|
27738
27297
|
title: "",
|
|
27739
27298
|
range: [defaultRange, defaultRange],
|
|
27740
27299
|
box: [defaultBoxSize, defaultBoxSize],
|
|
@@ -27901,13 +27460,9 @@ var Image$1 = {
|
|
|
27901
27460
|
const bgImage = widgetOptions.backgroundImage;
|
|
27902
27461
|
return !(bgImage && bgImage.url && !widgetOptions.alt);
|
|
27903
27462
|
},
|
|
27904
|
-
defaultAlignment: DEFAULT_ALIGNMENT,
|
|
27905
|
-
supportedAlignments: editorAlignments,
|
|
27906
27463
|
displayName: "Image",
|
|
27907
27464
|
widget: ImageWidget,
|
|
27908
|
-
isLintable: true
|
|
27909
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
27910
|
-
scorer: () => scoreNoop()
|
|
27465
|
+
isLintable: true
|
|
27911
27466
|
};
|
|
27912
27467
|
|
|
27913
27468
|
const getPromptJSON$c = () => {
|
|
@@ -28510,9 +28065,7 @@ var Interactive = {
|
|
|
28510
28065
|
displayName: "Interaction",
|
|
28511
28066
|
widget: Interaction,
|
|
28512
28067
|
transform: ___default["default"].identity,
|
|
28513
|
-
hidden: true
|
|
28514
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
28515
|
-
scorer: () => scoreNoop()
|
|
28068
|
+
hidden: true
|
|
28516
28069
|
};
|
|
28517
28070
|
|
|
28518
28071
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -28794,7 +28347,7 @@ class Graph extends React__namespace.Component {
|
|
|
28794
28347
|
image = null;
|
|
28795
28348
|
}
|
|
28796
28349
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
28797
|
-
className: "graphie-container
|
|
28350
|
+
className: "graphie-container blank-background",
|
|
28798
28351
|
style: {
|
|
28799
28352
|
width: this.props.box[0],
|
|
28800
28353
|
height: this.props.box[1]
|
|
@@ -29428,7 +28981,8 @@ const YGridTick = _ref => {
|
|
|
29428
28981
|
let {
|
|
29429
28982
|
y,
|
|
29430
28983
|
range,
|
|
29431
|
-
tickStep
|
|
28984
|
+
tickStep,
|
|
28985
|
+
showPi
|
|
29432
28986
|
} = _ref;
|
|
29433
28987
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
29434
28988
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -29464,6 +29018,7 @@ const YGridTick = _ref => {
|
|
|
29464
29018
|
// If the graph displays both the y and x axis lines within the graph, we want
|
|
29465
29019
|
// to hide the label at -1 on the y-axis to prevent overlap with the x-axis label
|
|
29466
29020
|
const showLabel = shouldShowLabel(y, range, tickStep);
|
|
29021
|
+
const yLabel = showPi ? divideByAndShowPi(y) : y.toString();
|
|
29467
29022
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
29468
29023
|
className: "tick",
|
|
29469
29024
|
"aria-hidden": true
|
|
@@ -29481,12 +29036,13 @@ const YGridTick = _ref => {
|
|
|
29481
29036
|
textAnchor: "end",
|
|
29482
29037
|
x: xPositionText,
|
|
29483
29038
|
y: yPositionText
|
|
29484
|
-
},
|
|
29039
|
+
}, yLabel));
|
|
29485
29040
|
};
|
|
29486
29041
|
const XGridTick = _ref2 => {
|
|
29487
29042
|
let {
|
|
29488
29043
|
x,
|
|
29489
|
-
range
|
|
29044
|
+
range,
|
|
29045
|
+
showPi
|
|
29490
29046
|
} = _ref2;
|
|
29491
29047
|
// If the graph requires out-of-bounds labels, we want to make sure to set the
|
|
29492
29048
|
// coordinates to the edge of the visible range of the graph. Otherwise,
|
|
@@ -29525,6 +29081,7 @@ const XGridTick = _ref2 => {
|
|
|
29525
29081
|
// Apply the adjustments to the x and y positions for the text
|
|
29526
29082
|
const xPositionText = xPosition + xAdjustment;
|
|
29527
29083
|
const yPositionText = yPosition + yAdjustment;
|
|
29084
|
+
const xLabel = showPi ? divideByAndShowPi(x) : x.toString();
|
|
29528
29085
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
29529
29086
|
className: "tick",
|
|
29530
29087
|
"aria-hidden": true
|
|
@@ -29542,7 +29099,7 @@ const XGridTick = _ref2 => {
|
|
|
29542
29099
|
textAnchor: "middle",
|
|
29543
29100
|
x: xPositionText,
|
|
29544
29101
|
y: yPositionText
|
|
29545
|
-
},
|
|
29102
|
+
}, xLabel));
|
|
29546
29103
|
};
|
|
29547
29104
|
|
|
29548
29105
|
// Determines whether to show the label for the given tick
|
|
@@ -29589,6 +29146,22 @@ const countSignificantDecimals = number => {
|
|
|
29589
29146
|
}
|
|
29590
29147
|
return numStr.split(".")[1].length;
|
|
29591
29148
|
};
|
|
29149
|
+
|
|
29150
|
+
// Show the given value as a multiple of pi (already assumed to be
|
|
29151
|
+
// a multiple of pi). Exported for testing
|
|
29152
|
+
function divideByAndShowPi(value) {
|
|
29153
|
+
const dividedValue = value / Math.PI;
|
|
29154
|
+
switch (dividedValue) {
|
|
29155
|
+
case 1:
|
|
29156
|
+
return "π";
|
|
29157
|
+
case -1:
|
|
29158
|
+
return "-π";
|
|
29159
|
+
case 0:
|
|
29160
|
+
return "0";
|
|
29161
|
+
default:
|
|
29162
|
+
return dividedValue + "π";
|
|
29163
|
+
}
|
|
29164
|
+
}
|
|
29592
29165
|
const AxisTicks = () => {
|
|
29593
29166
|
const {
|
|
29594
29167
|
tickStep,
|
|
@@ -29611,6 +29184,10 @@ const AxisTicks = () => {
|
|
|
29611
29184
|
key: `y-grid-tick-${y}`,
|
|
29612
29185
|
range: range,
|
|
29613
29186
|
tickStep: tickStep[Y]
|
|
29187
|
+
// Show the tick labels as multiples of pi
|
|
29188
|
+
// if the tick step is a multiple of pi.
|
|
29189
|
+
,
|
|
29190
|
+
showPi: tickStep[Y] % Math.PI === 0
|
|
29614
29191
|
});
|
|
29615
29192
|
})), /*#__PURE__*/React__namespace.createElement("g", {
|
|
29616
29193
|
className: "x-axis-ticks"
|
|
@@ -29619,6 +29196,10 @@ const AxisTicks = () => {
|
|
|
29619
29196
|
x: x,
|
|
29620
29197
|
key: `x-grid-tick-${x}`,
|
|
29621
29198
|
range: range
|
|
29199
|
+
// Show the tick labels as multiples of pi
|
|
29200
|
+
// if the tick step is a multiple of pi.
|
|
29201
|
+
,
|
|
29202
|
+
showPi: tickStep[X] % Math.PI === 0
|
|
29622
29203
|
});
|
|
29623
29204
|
})));
|
|
29624
29205
|
};
|
|
@@ -29920,6 +29501,15 @@ const styles$d = aphrodite.StyleSheet.create({
|
|
|
29920
29501
|
}
|
|
29921
29502
|
});
|
|
29922
29503
|
|
|
29504
|
+
function isPiMultiple(value) {
|
|
29505
|
+
// Early return for integers that clearly aren't multiples
|
|
29506
|
+
// of pi to save some computation.
|
|
29507
|
+
if (Number.isInteger(value)) {
|
|
29508
|
+
return false;
|
|
29509
|
+
}
|
|
29510
|
+
return value % Math.PI === 0 || value % (Math.PI / 2) === 0 || value % (Math.PI / 3) === 0 || value % (Math.PI / 4) === 0 || value % (Math.PI / 6) === 0;
|
|
29511
|
+
}
|
|
29512
|
+
|
|
29923
29513
|
const {
|
|
29924
29514
|
firstNumericalParse,
|
|
29925
29515
|
captureScratchpadTouchStart
|
|
@@ -29957,7 +29547,8 @@ class NumberInput extends React__namespace.Component {
|
|
|
29957
29547
|
onFormatChange: PropTypes__default["default"].func,
|
|
29958
29548
|
checkValidity: PropTypes__default["default"].func,
|
|
29959
29549
|
size: PropTypes__default["default"].oneOf(["mini", "small", "normal"]),
|
|
29960
|
-
label: PropTypes__default["default"].oneOf(["put your labels outside your inputs!"])
|
|
29550
|
+
label: PropTypes__default["default"].oneOf(["put your labels outside your inputs!"]),
|
|
29551
|
+
allowPiTruncation: PropTypes__default["default"].bool
|
|
29961
29552
|
};
|
|
29962
29553
|
static defaultProps = {
|
|
29963
29554
|
value: null,
|
|
@@ -29970,6 +29561,19 @@ class NumberInput extends React__namespace.Component {
|
|
|
29970
29561
|
state = {
|
|
29971
29562
|
format: this.props.format
|
|
29972
29563
|
};
|
|
29564
|
+
componentDidMount() {
|
|
29565
|
+
// If the value is a multiple of pi, but it is not in the pi format,
|
|
29566
|
+
// then convert it to the pi format and show it as a multiple of pi.
|
|
29567
|
+
const value = this.getValue();
|
|
29568
|
+
if (this.props.allowPiTruncation && value !== null && value !== 0) {
|
|
29569
|
+
if (this.state.format !== "pi" && isPiMultiple(value)) {
|
|
29570
|
+
this._setValue(value / Math.PI, "pi");
|
|
29571
|
+
this.setState({
|
|
29572
|
+
format: "pi"
|
|
29573
|
+
});
|
|
29574
|
+
}
|
|
29575
|
+
}
|
|
29576
|
+
}
|
|
29973
29577
|
componentDidUpdate(prevProps) {
|
|
29974
29578
|
if (!kmath.number.equal(this.getValue(), this.props.value)) {
|
|
29975
29579
|
this._setValue(this.props.value, this.state.format);
|
|
@@ -30126,7 +29730,8 @@ class RangeInput extends React__namespace.Component {
|
|
|
30126
29730
|
value: PropTypes__default["default"].array.isRequired,
|
|
30127
29731
|
onChange: PropTypes__default["default"].func.isRequired,
|
|
30128
29732
|
placeholder: PropTypes__default["default"].array,
|
|
30129
|
-
checkValidity: PropTypes__default["default"].func
|
|
29733
|
+
checkValidity: PropTypes__default["default"].func,
|
|
29734
|
+
allowPiTruncation: PropTypes__default["default"].bool
|
|
30130
29735
|
};
|
|
30131
29736
|
static defaultProps = {
|
|
30132
29737
|
placeholder: [null, null]
|
|
@@ -30150,14 +29755,16 @@ class RangeInput extends React__namespace.Component {
|
|
|
30150
29755
|
// eslint-disable-next-line react/jsx-no-bind
|
|
30151
29756
|
,
|
|
30152
29757
|
onChange: this.onChange.bind(this, 0),
|
|
30153
|
-
placeholder: this.props.placeholder[0]
|
|
29758
|
+
placeholder: this.props.placeholder[0],
|
|
29759
|
+
allowPiTruncation: this.props.allowPiTruncation
|
|
30154
29760
|
})), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
|
|
30155
29761
|
value: value[1],
|
|
30156
29762
|
checkValidity: val => checkValidity([value[0], val])
|
|
30157
29763
|
// eslint-disable-next-line react/jsx-no-bind
|
|
30158
29764
|
,
|
|
30159
29765
|
onChange: this.onChange.bind(this, 1),
|
|
30160
|
-
placeholder: this.props.placeholder[1]
|
|
29766
|
+
placeholder: this.props.placeholder[1],
|
|
29767
|
+
allowPiTruncation: this.props.allowPiTruncation
|
|
30161
29768
|
})));
|
|
30162
29769
|
}
|
|
30163
29770
|
}
|
|
@@ -30390,10 +29997,10 @@ function Vector(props) {
|
|
|
30390
29997
|
}));
|
|
30391
29998
|
}
|
|
30392
29999
|
|
|
30393
|
-
function srFormatNumber(a, locale) {
|
|
30000
|
+
function srFormatNumber(a, locale, maximumFractionDigits) {
|
|
30394
30001
|
// adding zero here converts negative zero to positive zero.
|
|
30395
30002
|
return (0 + a).toLocaleString(locale, {
|
|
30396
|
-
maximumFractionDigits: 3,
|
|
30003
|
+
maximumFractionDigits: maximumFractionDigits ?? 3,
|
|
30397
30004
|
useGrouping: false // no thousands separators
|
|
30398
30005
|
});
|
|
30399
30006
|
}
|
|
@@ -30572,6 +30179,95 @@ a, b, c) {
|
|
|
30572
30179
|
return [x1, x2];
|
|
30573
30180
|
}
|
|
30574
30181
|
|
|
30182
|
+
/**
|
|
30183
|
+
* Given a list of points, return the angle made by the point at index i
|
|
30184
|
+
* with the points at indices i - 1 and i + 1.
|
|
30185
|
+
*/
|
|
30186
|
+
function getAngleFromPoints(points, i) {
|
|
30187
|
+
// Make sure the index is valid.
|
|
30188
|
+
if (i < 0 || i >= points.length || !Number.isInteger(i)) {
|
|
30189
|
+
return null;
|
|
30190
|
+
}
|
|
30191
|
+
|
|
30192
|
+
// If there are fewer than 3 points, there is no angle.
|
|
30193
|
+
if (points.length < 3) {
|
|
30194
|
+
return null;
|
|
30195
|
+
}
|
|
30196
|
+
|
|
30197
|
+
// Get the points at the current index and the two adjacent points.
|
|
30198
|
+
const point = points.at(i);
|
|
30199
|
+
const pt1 = points.at(i - 1);
|
|
30200
|
+
const pt2 = points[(i + 1) % points.length];
|
|
30201
|
+
if (!point || !pt1 || !pt2) {
|
|
30202
|
+
return null;
|
|
30203
|
+
}
|
|
30204
|
+
|
|
30205
|
+
// Get the lengths of adjacent sides, making a triangle
|
|
30206
|
+
// we can use to calculate the angle.
|
|
30207
|
+
const a = mafs.vec.dist(point, pt1);
|
|
30208
|
+
const b = mafs.vec.dist(point, pt2);
|
|
30209
|
+
const c = mafs.vec.dist(pt1, pt2);
|
|
30210
|
+
|
|
30211
|
+
// Law of cosines
|
|
30212
|
+
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
30213
|
+
return angle;
|
|
30214
|
+
}
|
|
30215
|
+
function getSideLengthsFromPoints(points, i, isPolygonOpen) {
|
|
30216
|
+
if (i < 0 || i >= points.length || !Number.isInteger(i)) {
|
|
30217
|
+
return [];
|
|
30218
|
+
}
|
|
30219
|
+
if (points.length < 2) {
|
|
30220
|
+
return [];
|
|
30221
|
+
}
|
|
30222
|
+
const returnArray = [];
|
|
30223
|
+
const point = points[i];
|
|
30224
|
+
// If this point is the first point, then side 1 is the
|
|
30225
|
+
// last point in the list.
|
|
30226
|
+
const point1Index = i === 0 ? points.length - 1 : i - 1;
|
|
30227
|
+
const point1 = points[point1Index];
|
|
30228
|
+
// Make sure the previous point is not the same
|
|
30229
|
+
// as the current point.
|
|
30230
|
+
const side1 = i !== point1Index ? mafs.vec.dist(point, point1) : null;
|
|
30231
|
+
// The first side of the first point does not exist if
|
|
30232
|
+
// the polygon is open.
|
|
30233
|
+
if (side1 && !(isPolygonOpen && i === 0)) {
|
|
30234
|
+
returnArray.push({
|
|
30235
|
+
pointIndex: point1Index,
|
|
30236
|
+
sideLength: side1
|
|
30237
|
+
});
|
|
30238
|
+
}
|
|
30239
|
+
|
|
30240
|
+
// See if there is a side 2.
|
|
30241
|
+
const point2Index = (i + 1) % points.length;
|
|
30242
|
+
const point2 = points[point2Index];
|
|
30243
|
+
// Make sure that the next point is not the same as the
|
|
30244
|
+
// current point, and don't repeat the first point.
|
|
30245
|
+
const side2 = i !== point2Index && point2Index !== point1Index ? mafs.vec.dist(point, point2) : null;
|
|
30246
|
+
// The second side of the last point does not exist if
|
|
30247
|
+
// the polygon is open.
|
|
30248
|
+
if (side2 && !(isPolygonOpen && i === points.length - 1)) {
|
|
30249
|
+
returnArray.push({
|
|
30250
|
+
pointIndex: point2Index,
|
|
30251
|
+
sideLength: side2
|
|
30252
|
+
});
|
|
30253
|
+
}
|
|
30254
|
+
return returnArray;
|
|
30255
|
+
}
|
|
30256
|
+
|
|
30257
|
+
/**
|
|
30258
|
+
* Determine whether to determine the string with the
|
|
30259
|
+
* exact side length or the approximate side length.
|
|
30260
|
+
*/
|
|
30261
|
+
function getPolygonSideString(sideLength, pointIndex, strings, locale) {
|
|
30262
|
+
return Number.isInteger(sideLength) ? strings.srPolygonSideLength({
|
|
30263
|
+
pointNum: pointIndex + 1,
|
|
30264
|
+
length: `${sideLength}`
|
|
30265
|
+
}) : strings.srPolygonSideLengthApprox({
|
|
30266
|
+
pointNum: pointIndex + 1,
|
|
30267
|
+
length: srFormatNumber(sideLength, locale, 1)
|
|
30268
|
+
});
|
|
30269
|
+
}
|
|
30270
|
+
|
|
30575
30271
|
const {
|
|
30576
30272
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
30577
30273
|
} = kmath.angles;
|
|
@@ -31019,7 +30715,7 @@ const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs
|
|
|
31019
30715
|
})));
|
|
31020
30716
|
|
|
31021
30717
|
const {
|
|
31022
|
-
clockwise
|
|
30718
|
+
clockwise
|
|
31023
30719
|
} = kmath.geometry;
|
|
31024
30720
|
const {
|
|
31025
30721
|
getAngleFromVertex: getAngleFromVertex$1
|
|
@@ -31034,7 +30730,7 @@ const PolygonAngle = _ref => {
|
|
|
31034
30730
|
snapTo
|
|
31035
30731
|
} = _ref;
|
|
31036
30732
|
const [centerX, centerY] = centerPoint;
|
|
31037
|
-
const areClockwise = clockwise
|
|
30733
|
+
const areClockwise = clockwise([centerPoint, ...endPoints]);
|
|
31038
30734
|
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
31039
30735
|
const radius = 0.3;
|
|
31040
30736
|
const a = mafs.vec.dist(centerPoint, endPoints[0]);
|
|
@@ -31107,7 +30803,7 @@ const Angle = _ref2 => {
|
|
|
31107
30803
|
range
|
|
31108
30804
|
} = _ref2;
|
|
31109
30805
|
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
31110
|
-
const areClockwise = clockwise
|
|
30806
|
+
const areClockwise = clockwise([...coords, vertex]);
|
|
31111
30807
|
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
31112
30808
|
|
|
31113
30809
|
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
@@ -32054,9 +31750,6 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
32054
31750
|
};
|
|
32055
31751
|
}
|
|
32056
31752
|
|
|
32057
|
-
const {
|
|
32058
|
-
clockwise
|
|
32059
|
-
} = kmath.geometry;
|
|
32060
31753
|
function getGradableGraph(state, initialGraph) {
|
|
32061
31754
|
if (!state.hasBeenInteractedWith) {
|
|
32062
31755
|
return {
|
|
@@ -32133,17 +31826,9 @@ function getGradableGraph(state, initialGraph) {
|
|
|
32133
31826
|
};
|
|
32134
31827
|
}
|
|
32135
31828
|
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
32136
|
-
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
32137
|
-
// we don't allow reflex angles. This is because the angle is largely defined
|
|
32138
|
-
// by the order of the points in the coords array, and we want to maintain
|
|
32139
|
-
// the same angle scoring with the legacy graph (which had a bug).
|
|
32140
|
-
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
32141
|
-
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
32142
|
-
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
32143
|
-
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
32144
31829
|
return {
|
|
32145
31830
|
...initialGraph,
|
|
32146
|
-
coords,
|
|
31831
|
+
coords: state.coords,
|
|
32147
31832
|
allowReflexAngles: state.allowReflexAngles
|
|
32148
31833
|
};
|
|
32149
31834
|
}
|
|
@@ -32795,17 +32480,20 @@ function describePointGraph(state, i18n) {
|
|
|
32795
32480
|
});
|
|
32796
32481
|
});
|
|
32797
32482
|
return strings.srInteractiveElements({
|
|
32798
|
-
elements: pointDescriptions.join("
|
|
32483
|
+
elements: pointDescriptions.join(" ")
|
|
32799
32484
|
});
|
|
32800
32485
|
}
|
|
32801
32486
|
|
|
32802
|
-
|
|
32487
|
+
const {
|
|
32488
|
+
convertRadiansToDegrees
|
|
32489
|
+
} = kmath.angles;
|
|
32490
|
+
function renderPolygonGraph(state, dispatch, i18n, markings) {
|
|
32803
32491
|
return {
|
|
32804
32492
|
graph: /*#__PURE__*/React__namespace.createElement(PolygonGraph, {
|
|
32805
32493
|
graphState: state,
|
|
32806
32494
|
dispatch: dispatch
|
|
32807
32495
|
}),
|
|
32808
|
-
interactiveElementsDescription:
|
|
32496
|
+
interactiveElementsDescription: getPolygonGraphDescription(state, i18n, markings)
|
|
32809
32497
|
};
|
|
32810
32498
|
}
|
|
32811
32499
|
const PolygonGraph = props => {
|
|
@@ -32890,7 +32578,7 @@ const PolygonGraph = props => {
|
|
|
32890
32578
|
focusVisible,
|
|
32891
32579
|
setFocusVisible
|
|
32892
32580
|
};
|
|
32893
|
-
return numSides === "unlimited" ? UnlimitedPolygonGraph
|
|
32581
|
+
return numSides === "unlimited" ? /*#__PURE__*/React__namespace.createElement(UnlimitedPolygonGraph, statefulProps) : /*#__PURE__*/React__namespace.createElement(LimitedPolygonGraph, statefulProps);
|
|
32894
32582
|
};
|
|
32895
32583
|
const LimitedPolygonGraph = statefulProps => {
|
|
32896
32584
|
const {
|
|
@@ -32915,8 +32603,39 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32915
32603
|
const {
|
|
32916
32604
|
disableKeyboardInteraction
|
|
32917
32605
|
} = graphConfig;
|
|
32606
|
+
const {
|
|
32607
|
+
strings,
|
|
32608
|
+
locale
|
|
32609
|
+
} = usePerseusI18n();
|
|
32610
|
+
const id = React__namespace.useId();
|
|
32611
|
+
const pointsOffArray = Array(points.length).fill("off");
|
|
32612
|
+
// When moving an element, set its aria-live to "polite" and the others
|
|
32613
|
+
// to "off". Otherwise, other connected elements that move at the same
|
|
32614
|
+
// time might override the currently focused element's aria live.
|
|
32615
|
+
const [ariaLives, setAriaLives] = React__namespace.useState([
|
|
32616
|
+
// First one represents the aria-live value for the polygon itself.
|
|
32617
|
+
"off",
|
|
32618
|
+
// The rest represent the points.
|
|
32619
|
+
...pointsOffArray]);
|
|
32918
32620
|
const lines = getLines(points);
|
|
32919
|
-
|
|
32621
|
+
const polygonPointsNumId = id + "-points-num";
|
|
32622
|
+
const polygonPointsId = id + "-points";
|
|
32623
|
+
|
|
32624
|
+
// Aria label srings
|
|
32625
|
+
const {
|
|
32626
|
+
srPolygonGraph,
|
|
32627
|
+
srPolygonGraphPointsNum,
|
|
32628
|
+
srPolygonGraphPoints,
|
|
32629
|
+
srPolygonElementsNum
|
|
32630
|
+
} = describePolygonGraph(statefulProps.graphState, {
|
|
32631
|
+
strings,
|
|
32632
|
+
locale
|
|
32633
|
+
}, statefulProps.graphConfig.markings);
|
|
32634
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32635
|
+
// Outer graph minimal description
|
|
32636
|
+
"aria-label": srPolygonGraph,
|
|
32637
|
+
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
32638
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
32920
32639
|
points: [...points],
|
|
32921
32640
|
color: "var(--movable-line-stroke-color)",
|
|
32922
32641
|
svgPolygonProps: {
|
|
@@ -32942,13 +32661,17 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32942
32661
|
});
|
|
32943
32662
|
}), showSides && lines.map((_ref, i) => {
|
|
32944
32663
|
let [start, end] = _ref;
|
|
32664
|
+
// Use x and y to find the position of the label
|
|
32945
32665
|
const [x, y] = mafs.vec.midpoint(start, end);
|
|
32946
|
-
const length =
|
|
32666
|
+
const length = mafs.vec.dist(start, end);
|
|
32667
|
+
// Check if the length needs to indicate
|
|
32668
|
+
// that it's an approximation.
|
|
32669
|
+
const isApprox = !Number.isInteger(length);
|
|
32947
32670
|
return /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
32948
32671
|
key: "side-" + i,
|
|
32949
32672
|
x: x,
|
|
32950
32673
|
y: y
|
|
32951
|
-
},
|
|
32674
|
+
}, isApprox ? `≈ ${length.toFixed(snapTo === "sides" ? 0 : 1)}` : length);
|
|
32952
32675
|
}), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
32953
32676
|
points: [...points],
|
|
32954
32677
|
color: "transparent",
|
|
@@ -32968,27 +32691,80 @@ const LimitedPolygonGraph = statefulProps => {
|
|
|
32968
32691
|
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32969
32692
|
},
|
|
32970
32693
|
// Required for lines to darken on focus
|
|
32971
|
-
onFocus: () =>
|
|
32694
|
+
onFocus: () => {
|
|
32695
|
+
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
32696
|
+
setAriaLives(() => ["polite", ...pointsOffArray]);
|
|
32697
|
+
},
|
|
32972
32698
|
// Required for line weighting to update on blur. Without this,
|
|
32973
32699
|
// the user has to hover over the shape for it to update
|
|
32974
32700
|
onBlur: () => setFocusVisible(hasFocusVisible(polygonRef.current)),
|
|
32975
|
-
className: "movable-polygon"
|
|
32976
|
-
|
|
32977
|
-
|
|
32978
|
-
|
|
32979
|
-
|
|
32980
|
-
point: point,
|
|
32981
|
-
sequenceNumber: i + 1,
|
|
32982
|
-
onMove: destination => {
|
|
32983
|
-
const now = Date.now();
|
|
32984
|
-
const targetFPS = 40;
|
|
32985
|
-
const moveThresholdTime = 1000 / targetFPS;
|
|
32986
|
-
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
32987
|
-
dispatch(actions.polygon.movePoint(i, destination));
|
|
32988
|
-
lastMoveTimeRef.current = now;
|
|
32989
|
-
}
|
|
32701
|
+
className: "movable-polygon",
|
|
32702
|
+
// Accessibility-related fields
|
|
32703
|
+
role: "button",
|
|
32704
|
+
"aria-label": srPolygonGraphPoints ? `${srPolygonElementsNum} ${srPolygonGraphPoints}` : srPolygonElementsNum,
|
|
32705
|
+
"aria-live": ariaLives[0]
|
|
32990
32706
|
}
|
|
32991
|
-
}))
|
|
32707
|
+
}), points.map((point, i) => {
|
|
32708
|
+
const angleId = `${id}-angle-${i}`;
|
|
32709
|
+
const side1Id = `${id}-point-${i}-side-1`;
|
|
32710
|
+
const side2Id = `${id}-point-${i}-side-2`;
|
|
32711
|
+
|
|
32712
|
+
// Limited polygons always have an angle at each vertex.
|
|
32713
|
+
const angle = getAngleFromPoints(points, i);
|
|
32714
|
+
const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
|
|
32715
|
+
|
|
32716
|
+
// Limited polygons always two sides attached to each vertex.
|
|
32717
|
+
const sidesArray = getSideLengthsFromPoints(points, i);
|
|
32718
|
+
const {
|
|
32719
|
+
pointIndex: point1Index,
|
|
32720
|
+
sideLength: side1Length
|
|
32721
|
+
} = sidesArray[0];
|
|
32722
|
+
const {
|
|
32723
|
+
pointIndex: point2Index,
|
|
32724
|
+
sideLength: side2Length
|
|
32725
|
+
} = sidesArray[1];
|
|
32726
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32727
|
+
key: "point-" + i
|
|
32728
|
+
}, /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
32729
|
+
ariaDescribedBy: `${angleId} ${side1Id} ${side2Id}`,
|
|
32730
|
+
ariaLive: ariaLives[i + 1],
|
|
32731
|
+
constrain: constrain,
|
|
32732
|
+
point: point,
|
|
32733
|
+
sequenceNumber: i + 1,
|
|
32734
|
+
onMove: destination => {
|
|
32735
|
+
const now = Date.now();
|
|
32736
|
+
const targetFPS = 40;
|
|
32737
|
+
const moveThresholdTime = 1000 / targetFPS;
|
|
32738
|
+
if (now - lastMoveTimeRef.current > moveThresholdTime) {
|
|
32739
|
+
dispatch(actions.polygon.movePoint(i, destination));
|
|
32740
|
+
lastMoveTimeRef.current = now;
|
|
32741
|
+
}
|
|
32742
|
+
},
|
|
32743
|
+
onFocus: () => {
|
|
32744
|
+
const newPointAriaLives = [...pointsOffArray];
|
|
32745
|
+
newPointAriaLives[i] = "polite";
|
|
32746
|
+
// Whole polygon is "off", and the current
|
|
32747
|
+
// point is "polite".
|
|
32748
|
+
setAriaLives(["off", ...newPointAriaLives]);
|
|
32749
|
+
}
|
|
32750
|
+
}), angleDegree && /*#__PURE__*/React__namespace.createElement("g", {
|
|
32751
|
+
id: angleId
|
|
32752
|
+
}, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
|
|
32753
|
+
angle: angleDegree
|
|
32754
|
+
}) : strings.srPolygonPointAngleApprox({
|
|
32755
|
+
angle: srFormatNumber(angleDegree, locale, 1)
|
|
32756
|
+
})), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32757
|
+
id: side1Id
|
|
32758
|
+
}, getPolygonSideString(side1Length, point1Index, strings, locale)), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32759
|
+
id: side2Id
|
|
32760
|
+
}, getPolygonSideString(side2Length, point2Index, strings, locale)));
|
|
32761
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32762
|
+
id: polygonPointsNumId,
|
|
32763
|
+
style: a11y.srOnly
|
|
32764
|
+
}, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React__namespace.createElement("g", {
|
|
32765
|
+
id: polygonPointsId,
|
|
32766
|
+
style: a11y.srOnly
|
|
32767
|
+
}, srPolygonGraphPoints));
|
|
32992
32768
|
};
|
|
32993
32769
|
const UnlimitedPolygonGraph = statefulProps => {
|
|
32994
32770
|
const {
|
|
@@ -33003,6 +32779,19 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33003
32779
|
coords,
|
|
33004
32780
|
closedPolygon
|
|
33005
32781
|
} = statefulProps.graphState;
|
|
32782
|
+
const {
|
|
32783
|
+
strings,
|
|
32784
|
+
locale
|
|
32785
|
+
} = usePerseusI18n();
|
|
32786
|
+
const id = React__namespace.useId();
|
|
32787
|
+
const polygonPointsNumId = id + "-points-num";
|
|
32788
|
+
const polygonPointsId = id + "-points";
|
|
32789
|
+
|
|
32790
|
+
// When moving an element, set its aria-live to "polite" and the others
|
|
32791
|
+
// to "off". Otherwise, other connected elements that move at the same
|
|
32792
|
+
// time might override the currently focused element's aria live.
|
|
32793
|
+
const pointsOffArray = Array(points.length).fill("off");
|
|
32794
|
+
const [ariaLives, setAriaLives] = React__namespace.useState(pointsOffArray);
|
|
33006
32795
|
|
|
33007
32796
|
// If the polygon is closed, return a LimitedPolygon component.
|
|
33008
32797
|
if (closedPolygon) {
|
|
@@ -33017,7 +32806,22 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33017
32806
|
} = graphConfig;
|
|
33018
32807
|
const widthPx = graphDimensionsInPixels[0];
|
|
33019
32808
|
const heightPx = graphDimensionsInPixels[1];
|
|
33020
|
-
|
|
32809
|
+
|
|
32810
|
+
// Aria label srings
|
|
32811
|
+
const emptyGraph = coords.length === 0;
|
|
32812
|
+
const {
|
|
32813
|
+
srPolygonGraph,
|
|
32814
|
+
srPolygonGraphPointsNum,
|
|
32815
|
+
srPolygonGraphPoints
|
|
32816
|
+
} = describePolygonGraph(statefulProps.graphState, {
|
|
32817
|
+
strings,
|
|
32818
|
+
locale
|
|
32819
|
+
}, statefulProps.graphConfig.markings);
|
|
32820
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32821
|
+
// Outer graph minimal description
|
|
32822
|
+
"aria-label": emptyGraph ? strings.srUnlimitedPolygonEmpty : srPolygonGraph,
|
|
32823
|
+
"aria-describedby": `${polygonPointsNumId} ${polygonPointsId}`
|
|
32824
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
33021
32825
|
points: [...points],
|
|
33022
32826
|
color: "var(--movable-line-stroke-color)",
|
|
33023
32827
|
svgPolylineProps: {
|
|
@@ -33042,28 +32846,71 @@ const UnlimitedPolygonGraph = statefulProps => {
|
|
|
33042
32846
|
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
33043
32847
|
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
33044
32848
|
}
|
|
33045
|
-
}), coords.map((point, i) =>
|
|
33046
|
-
|
|
33047
|
-
|
|
33048
|
-
|
|
33049
|
-
|
|
33050
|
-
|
|
33051
|
-
|
|
33052
|
-
|
|
33053
|
-
|
|
33054
|
-
|
|
33055
|
-
|
|
33056
|
-
|
|
33057
|
-
// If the point being clicked is the first point and
|
|
33058
|
-
// there's enough non-duplicated points to form
|
|
33059
|
-
// a polygon (3 or more), close the shape before
|
|
33060
|
-
// setting focus.
|
|
33061
|
-
if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
|
|
33062
|
-
dispatch(actions.polygon.closePolygon());
|
|
33063
|
-
}
|
|
33064
|
-
dispatch(actions.polygon.clickPoint(i));
|
|
32849
|
+
}), coords.map((point, i) => {
|
|
32850
|
+
const angleId = `${id}-angle-${i}`;
|
|
32851
|
+
let sideIds = "";
|
|
32852
|
+
|
|
32853
|
+
// This point only has an angle if it's not the first
|
|
32854
|
+
// or last point in this open polygon.
|
|
32855
|
+
const hasAngle = i > 0 && i < coords.length - 1;
|
|
32856
|
+
const angle = hasAngle ? getAngleFromPoints(points, i) : null;
|
|
32857
|
+
const angleDegree = angle ? convertRadiansToDegrees(angle) : null;
|
|
32858
|
+
const sidesArray = getSideLengthsFromPoints(points, i, true);
|
|
32859
|
+
for (let sideIndex = 0; sideIndex < sidesArray.length; sideIndex++) {
|
|
32860
|
+
sideIds += `${id}-point-${i}-side-${sideIndex} `;
|
|
33065
32861
|
}
|
|
33066
|
-
|
|
32862
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32863
|
+
key: "point-" + i
|
|
32864
|
+
}, /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
32865
|
+
ariaDescribedBy: `${angleId} ${sideIds}`,
|
|
32866
|
+
ariaLive: ariaLives[i],
|
|
32867
|
+
point: point,
|
|
32868
|
+
sequenceNumber: i + 1,
|
|
32869
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
32870
|
+
ref: ref => {
|
|
32871
|
+
pointsRef.current[i] = ref;
|
|
32872
|
+
},
|
|
32873
|
+
onFocus: () => {
|
|
32874
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
32875
|
+
const newPointAriaLives = [...pointsOffArray];
|
|
32876
|
+
newPointAriaLives[i] = "polite";
|
|
32877
|
+
// Whole polygon is "off", and the current
|
|
32878
|
+
// point is "polite".
|
|
32879
|
+
setAriaLives([...newPointAriaLives]);
|
|
32880
|
+
},
|
|
32881
|
+
onClick: () => {
|
|
32882
|
+
// If the point being clicked is the first point and
|
|
32883
|
+
// there's enough non-duplicated points to form
|
|
32884
|
+
// a polygon (3 or more), close the shape before
|
|
32885
|
+
// setting focus.
|
|
32886
|
+
if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
|
|
32887
|
+
dispatch(actions.polygon.closePolygon());
|
|
32888
|
+
}
|
|
32889
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
32890
|
+
}
|
|
32891
|
+
}), angleDegree && /*#__PURE__*/React__namespace.createElement("g", {
|
|
32892
|
+
id: angleId
|
|
32893
|
+
}, Number.isInteger(angleDegree) ? strings.srPolygonPointAngle({
|
|
32894
|
+
angle: angleDegree
|
|
32895
|
+
}) : strings.srPolygonPointAngleApprox({
|
|
32896
|
+
angle: srFormatNumber(angleDegree, locale, 1)
|
|
32897
|
+
})), sidesArray.map((_ref2, j) => {
|
|
32898
|
+
let {
|
|
32899
|
+
pointIndex,
|
|
32900
|
+
sideLength
|
|
32901
|
+
} = _ref2;
|
|
32902
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32903
|
+
key: `${id}-point-${i}-side-${j}`,
|
|
32904
|
+
id: `${id}-point-${i}-side-${j}`
|
|
32905
|
+
}, getPolygonSideString(sideLength, pointIndex, strings, locale));
|
|
32906
|
+
}));
|
|
32907
|
+
}), coords.length > 0 && /*#__PURE__*/React__namespace.createElement("g", {
|
|
32908
|
+
id: polygonPointsNumId,
|
|
32909
|
+
style: a11y.srOnly
|
|
32910
|
+
}, srPolygonGraphPointsNum), srPolygonGraphPoints && /*#__PURE__*/React__namespace.createElement("g", {
|
|
32911
|
+
id: polygonPointsId,
|
|
32912
|
+
style: a11y.srOnly
|
|
32913
|
+
}, srPolygonGraphPoints));
|
|
33067
32914
|
};
|
|
33068
32915
|
function getLines(points) {
|
|
33069
32916
|
return points.map((point, i) => {
|
|
@@ -33082,6 +32929,54 @@ const hasFocusVisible = element => {
|
|
|
33082
32929
|
return matches(":focus");
|
|
33083
32930
|
}
|
|
33084
32931
|
};
|
|
32932
|
+
function getPolygonGraphDescription(state, i18n, markings) {
|
|
32933
|
+
const strings = describePolygonGraph(state, i18n, markings);
|
|
32934
|
+
return strings.srPolygonInteractiveElements;
|
|
32935
|
+
}
|
|
32936
|
+
function describePolygonGraph(state, i18n, markings) {
|
|
32937
|
+
const {
|
|
32938
|
+
strings,
|
|
32939
|
+
locale
|
|
32940
|
+
} = i18n;
|
|
32941
|
+
const {
|
|
32942
|
+
coords
|
|
32943
|
+
} = state;
|
|
32944
|
+
const isCoordinatePlane = markings === "axes" || markings === "graph";
|
|
32945
|
+
const hasOnePoint = coords.length === 1;
|
|
32946
|
+
|
|
32947
|
+
// Figure out graph aria label based on markings.
|
|
32948
|
+
const srPolygonGraph = isCoordinatePlane ? strings.srPolygonGraphCoordinatePlane : strings.srPolygonGraph;
|
|
32949
|
+
const srPolygonGraphPointsNum = hasOnePoint ? strings.srPolygonGraphPointsOne : strings.srPolygonGraphPointsNum({
|
|
32950
|
+
num: coords.length
|
|
32951
|
+
});
|
|
32952
|
+
let srPolygonGraphPoints;
|
|
32953
|
+
// Figure out graph description based on markings.
|
|
32954
|
+
// If the graph is not on a coordinate plane, we should not include
|
|
32955
|
+
// the points' coordinates in the description.
|
|
32956
|
+
if (isCoordinatePlane) {
|
|
32957
|
+
const pointsString = coords.map((coord, i) => {
|
|
32958
|
+
return strings.srPointAtCoordinates({
|
|
32959
|
+
num: i + 1,
|
|
32960
|
+
x: srFormatNumber(coord[0], locale),
|
|
32961
|
+
y: srFormatNumber(coord[1], locale)
|
|
32962
|
+
});
|
|
32963
|
+
});
|
|
32964
|
+
srPolygonGraphPoints = pointsString.join(" ");
|
|
32965
|
+
}
|
|
32966
|
+
const srPolygonElementsNum = hasOnePoint ? strings.srPolygonElementsOne : strings.srPolygonElementsNum({
|
|
32967
|
+
num: coords.length
|
|
32968
|
+
});
|
|
32969
|
+
const srPolygonInteractiveElements = coords.length > 0 ? strings.srInteractiveElements({
|
|
32970
|
+
elements: [srPolygonElementsNum, srPolygonGraphPoints].join(" ")
|
|
32971
|
+
}) : null;
|
|
32972
|
+
return {
|
|
32973
|
+
srPolygonGraph,
|
|
32974
|
+
srPolygonGraphPointsNum,
|
|
32975
|
+
srPolygonGraphPoints,
|
|
32976
|
+
srPolygonElementsNum,
|
|
32977
|
+
srPolygonInteractiveElements
|
|
32978
|
+
};
|
|
32979
|
+
}
|
|
33085
32980
|
|
|
33086
32981
|
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
33087
32982
|
return {
|
|
@@ -33788,7 +33683,8 @@ const MafsGraph = props => {
|
|
|
33788
33683
|
} = renderGraphElements({
|
|
33789
33684
|
state,
|
|
33790
33685
|
dispatch,
|
|
33791
|
-
i18n
|
|
33686
|
+
i18n,
|
|
33687
|
+
markings: props.markings
|
|
33792
33688
|
});
|
|
33793
33689
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
33794
33690
|
value: {
|
|
@@ -33823,7 +33719,7 @@ const MafsGraph = props => {
|
|
|
33823
33719
|
handleKeyboardEvent(event, state, dispatch);
|
|
33824
33720
|
},
|
|
33825
33721
|
"aria-label": fullGraphAriaLabel,
|
|
33826
|
-
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) &&
|
|
33722
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && unlimitedGraphKeyboardPromptId),
|
|
33827
33723
|
ref: graphRef,
|
|
33828
33724
|
tabIndex: 0,
|
|
33829
33725
|
onFocus: event => {
|
|
@@ -33849,7 +33745,19 @@ const MafsGraph = props => {
|
|
|
33849
33745
|
bottom: 0,
|
|
33850
33746
|
left: 0
|
|
33851
33747
|
}
|
|
33852
|
-
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(
|
|
33748
|
+
}, (props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null)), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
|
|
33749
|
+
// If we have locked figures, we set aria-hidden
|
|
33750
|
+
// to false so that screen readers can read the
|
|
33751
|
+
// locked figures. If we don't have any locked
|
|
33752
|
+
// figures, we need to set aria-hidden to true so
|
|
33753
|
+
// that screen readers don't read this Mafs element
|
|
33754
|
+
// as an empty image.
|
|
33755
|
+
// Note: Adding this in a wrapping View element
|
|
33756
|
+
// so that `aria-hidden` is a valid property.
|
|
33757
|
+
// It does not work on `Mafs` or svg elements.
|
|
33758
|
+
, {
|
|
33759
|
+
"aria-hidden": props.lockedFigures && props.lockedFigures.length > 0 ? false : true
|
|
33760
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
|
|
33853
33761
|
preserveAspectRatio: false,
|
|
33854
33762
|
viewBox: {
|
|
33855
33763
|
x: state.range[X],
|
|
@@ -33869,10 +33777,10 @@ const MafsGraph = props => {
|
|
|
33869
33777
|
height: height
|
|
33870
33778
|
})),
|
|
33871
33779
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
33872
|
-
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes,
|
|
33780
|
+
(props.markings === "graph" || props.markings === "axes") && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)), props.lockedFigures && props.lockedFigures.length > 0 && /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
33873
33781
|
lockedFigures: props.lockedFigures,
|
|
33874
33782
|
range: state.range
|
|
33875
|
-
}))), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
33783
|
+
})))), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLabelsLayer, {
|
|
33876
33784
|
lockedFigures: props.lockedFigures
|
|
33877
33785
|
}), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
33878
33786
|
style: {
|
|
@@ -34166,7 +34074,8 @@ const renderGraphElements = props => {
|
|
|
34166
34074
|
const {
|
|
34167
34075
|
state,
|
|
34168
34076
|
dispatch,
|
|
34169
|
-
i18n
|
|
34077
|
+
i18n,
|
|
34078
|
+
markings
|
|
34170
34079
|
} = props;
|
|
34171
34080
|
const {
|
|
34172
34081
|
type
|
|
@@ -34183,7 +34092,7 @@ const renderGraphElements = props => {
|
|
|
34183
34092
|
case "ray":
|
|
34184
34093
|
return renderRayGraph(state, dispatch);
|
|
34185
34094
|
case "polygon":
|
|
34186
|
-
return renderPolygonGraph(state, dispatch);
|
|
34095
|
+
return renderPolygonGraph(state, dispatch, i18n, markings);
|
|
34187
34096
|
case "point":
|
|
34188
34097
|
return renderPointGraph(state, dispatch);
|
|
34189
34098
|
case "circle":
|
|
@@ -35168,7 +35077,7 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
35168
35077
|
return [dx, dy];
|
|
35169
35078
|
};
|
|
35170
35079
|
// Less than or approximately equal
|
|
35171
|
-
function leq
|
|
35080
|
+
function leq(a, b) {
|
|
35172
35081
|
return a < b || perseusCore.approximateEqual(a, b);
|
|
35173
35082
|
}
|
|
35174
35083
|
function boundAndSnapToGrid(point, _ref) {
|
|
@@ -35339,7 +35248,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
35339
35248
|
|
|
35340
35249
|
// Avoid degenerate triangles
|
|
35341
35250
|
if (innerAngles.some(function (angle) {
|
|
35342
|
-
return leq
|
|
35251
|
+
return leq(angle, 1);
|
|
35343
35252
|
})) {
|
|
35344
35253
|
return startingPoint;
|
|
35345
35254
|
}
|
|
@@ -35401,7 +35310,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
|
35401
35310
|
});
|
|
35402
35311
|
|
|
35403
35312
|
// Avoid degenerate triangles
|
|
35404
|
-
if (leq
|
|
35313
|
+
if (leq(sides[1] + sides[2], sides[0]) || leq(sides[0] + sides[2], sides[1]) || leq(sides[0] + sides[1], sides[2])) {
|
|
35405
35314
|
return startingPoint;
|
|
35406
35315
|
}
|
|
35407
35316
|
|
|
@@ -35568,24 +35477,10 @@ const {
|
|
|
35568
35477
|
sign,
|
|
35569
35478
|
vector
|
|
35570
35479
|
} = kmath.geometry;
|
|
35571
|
-
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
35572
35480
|
const defaultBackgroundImage = {
|
|
35573
35481
|
url: null
|
|
35574
35482
|
};
|
|
35575
35483
|
const UNLIMITED = "unlimited";
|
|
35576
|
-
|
|
35577
|
-
// Sample background image:
|
|
35578
|
-
// https://ka-perseus-graphie.s3.amazonaws.com/29c1b0fcd17fe63df0f148fe357044d5d5c7d0bb.png
|
|
35579
|
-
|
|
35580
|
-
// default to defaultValue if actual is null or undefined
|
|
35581
|
-
function defaultVal(actual, defaultValue) {
|
|
35582
|
-
return actual == null ? defaultValue : actual;
|
|
35583
|
-
}
|
|
35584
|
-
|
|
35585
|
-
// Less than or approximately equal
|
|
35586
|
-
function leq(a, b) {
|
|
35587
|
-
return a < b || perseusCore.approximateEqual(a, b);
|
|
35588
|
-
}
|
|
35589
35484
|
function capitalize(str) {
|
|
35590
35485
|
return str.replace(/(?:^|-)(.)/g, function (match, letter) {
|
|
35591
35486
|
return letter.toUpperCase();
|
|
@@ -35594,14 +35489,6 @@ function capitalize(str) {
|
|
|
35594
35489
|
function numSteps(range, step) {
|
|
35595
35490
|
return Math.floor((range[1] - range[0]) / step);
|
|
35596
35491
|
}
|
|
35597
|
-
const _getShouldShowInstructions = props => {
|
|
35598
|
-
return _isClickToAddPoints(props) && (
|
|
35599
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35600
|
-
props.graph.coords == null || props.graph.coords.length === 0);
|
|
35601
|
-
};
|
|
35602
|
-
const _isClickToAddPoints = props => {
|
|
35603
|
-
return props.graph.type === "point" && props.graph.numPoints === UNLIMITED || props.graph.type === "polygon" && props.graph.numSides === UNLIMITED;
|
|
35604
|
-
};
|
|
35605
35492
|
const makeInvalidTypeError = (functionName, graphType) => {
|
|
35606
35493
|
return new perseusCore.PerseusError(`${functionName} called but current graph type is not a '${graphType}'`, perseusCore.Errors.NotAllowed, {
|
|
35607
35494
|
metadata: {
|
|
@@ -35642,1200 +35529,6 @@ function getQuadraticCoefficients(coords) {
|
|
|
35642
35529
|
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;
|
|
35643
35530
|
return [a, b, c];
|
|
35644
35531
|
}
|
|
35645
|
-
|
|
35646
|
-
// (LEMS-2190): Move the Mafs Angle Graph coordinate reversal logic in interactive-graph-state.ts
|
|
35647
|
-
// to this file when we remove the legacy graph. This logic allows us to support bi-directional angles
|
|
35648
|
-
// for the new (non-reflexive) Mafs graphs, while maintaining the same scoring behaviour as the legacy graph.
|
|
35649
|
-
// Once the legacy graph is removed, we should move this logic directly into the validate function below.
|
|
35650
|
-
class LegacyInteractiveGraph extends React__namespace.Component {
|
|
35651
|
-
static contextType = PerseusI18nContext;
|
|
35652
|
-
angle;
|
|
35653
|
-
circle;
|
|
35654
|
-
graphie;
|
|
35655
|
-
horizHairline;
|
|
35656
|
-
line;
|
|
35657
|
-
lines;
|
|
35658
|
-
parabola;
|
|
35659
|
-
pointA;
|
|
35660
|
-
pointB;
|
|
35661
|
-
pointC;
|
|
35662
|
-
// @ts-expect-error - TS2564 - Property 'points' has no initializer and is not definitely assigned in the constructor.
|
|
35663
|
-
points;
|
|
35664
|
-
polygon;
|
|
35665
|
-
// @ts-expect-error - TS2564 - Property 'shouldResetGraphie' has no initializer and is not definitely assigned in the constructor.
|
|
35666
|
-
shouldResetGraphie;
|
|
35667
|
-
sinusoid;
|
|
35668
|
-
trashCan;
|
|
35669
|
-
vertHairline;
|
|
35670
|
-
static defaultProps = {
|
|
35671
|
-
labels: ["x", "y"],
|
|
35672
|
-
range: [[-10, 10], [-10, 10]],
|
|
35673
|
-
step: [1, 1],
|
|
35674
|
-
backgroundImage: defaultBackgroundImage,
|
|
35675
|
-
markings: "graph",
|
|
35676
|
-
showTooltips: false,
|
|
35677
|
-
showProtractor: false,
|
|
35678
|
-
graph: {
|
|
35679
|
-
type: "linear"
|
|
35680
|
-
}
|
|
35681
|
-
};
|
|
35682
|
-
state = {
|
|
35683
|
-
shouldShowInstructions: _getShouldShowInstructions(this.props)
|
|
35684
|
-
};
|
|
35685
|
-
componentDidMount() {
|
|
35686
|
-
if (this.refs.graph) {
|
|
35687
|
-
// eslint-disable-next-line react/no-string-refs
|
|
35688
|
-
// @ts-expect-error - TS2339 - Property 'graphie' does not exist on type 'ReactInstance'.
|
|
35689
|
-
this.setGraphie(this.refs.graph.graphie());
|
|
35690
|
-
}
|
|
35691
|
-
}
|
|
35692
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
35693
|
-
if (this.isClickToAddPoints() !== this.isClickToAddPoints(nextProps)) {
|
|
35694
|
-
this.shouldResetGraphie = true;
|
|
35695
|
-
this.setState({
|
|
35696
|
-
shouldShowInstructions: _getShouldShowInstructions(nextProps)
|
|
35697
|
-
});
|
|
35698
|
-
}
|
|
35699
|
-
if (this.props.backgroundImage?.url !== nextProps.backgroundImage?.url || this.props.backgroundImage !== nextProps.backgroundImage || this.props.containerSizeClass !== nextProps.containerSizeClass) {
|
|
35700
|
-
this.shouldResetGraphie = true;
|
|
35701
|
-
}
|
|
35702
|
-
}
|
|
35703
|
-
componentDidUpdate(prevProps, prevState) {
|
|
35704
|
-
// TODO(jeremy): This feels underspecified. For example, if the props
|
|
35705
|
-
// change the coords of a segment graph, but not the numSegments, we'd
|
|
35706
|
-
// not count that as an update.
|
|
35707
|
-
const oldType = prevProps.graph.type;
|
|
35708
|
-
const newType = this.props.graph.type;
|
|
35709
|
-
if (oldType !== newType || prevProps.graph.type === "angle" && this.props.graph.type === "angle" && this.areAngleGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "point" && this.props.graph.type === "point" && this.arePointGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "polygon" && this.props.graph.type === "polygon" && this.arePolygonGraphsEqual(prevProps.graph, this.props.graph) || prevProps.graph.type === "segment" && this.props.graph.type === "segment" && this.areSegmentGraphsEqual(prevProps.graph, this.props.graph)) {
|
|
35710
|
-
this["remove" + capitalize(oldType) + "Controls"]();
|
|
35711
|
-
this["add" + capitalize(newType) + "Controls"]();
|
|
35712
|
-
}
|
|
35713
|
-
if (this.shouldResetGraphie) {
|
|
35714
|
-
this.resetGraphie();
|
|
35715
|
-
}
|
|
35716
|
-
}
|
|
35717
|
-
_getShouldShowInstructions = props => {
|
|
35718
|
-
props = props || this.props;
|
|
35719
|
-
return this.isClickToAddPoints(props) && (
|
|
35720
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
35721
|
-
props.graph.coords == null || props.graph.coords.length === 0);
|
|
35722
|
-
};
|
|
35723
|
-
setGraphie = newGraphie => {
|
|
35724
|
-
this.graphie = newGraphie;
|
|
35725
|
-
this.setupGraphie();
|
|
35726
|
-
};
|
|
35727
|
-
handleAddPointsMouseDown = coord => {
|
|
35728
|
-
// This function should only be called when this.isClickToAddPoints()
|
|
35729
|
-
// is true
|
|
35730
|
-
if (!this.isClickToAddPoints()) {
|
|
35731
|
-
throw new perseusCore.PerseusError("handleAddPointsClick should not be registered" + "when isClickToAddPoints() is false", perseusCore.Errors.NotAllowed);
|
|
35732
|
-
}
|
|
35733
|
-
if (!this.isCoordInTrash(coord)) {
|
|
35734
|
-
let point;
|
|
35735
|
-
if (this.props.graph.type === "point") {
|
|
35736
|
-
point = this.createPointForPointsType(coord, this.points.length);
|
|
35737
|
-
if (!point.constrain()) {
|
|
35738
|
-
point.remove();
|
|
35739
|
-
return;
|
|
35740
|
-
}
|
|
35741
|
-
this.points.push(point);
|
|
35742
|
-
|
|
35743
|
-
// interactive2 allows us to grab the point
|
|
35744
|
-
const idx = this.points.length - 1;
|
|
35745
|
-
this.points[idx].grab(coord);
|
|
35746
|
-
this.updateCoordsFromPoints();
|
|
35747
|
-
} else if (this.props.graph.type === "polygon") {
|
|
35748
|
-
if (this.polygon?.closed()) {
|
|
35749
|
-
return;
|
|
35750
|
-
}
|
|
35751
|
-
point = this.createPointForPolygonType(coord, this.points.length);
|
|
35752
|
-
this.points.push(point);
|
|
35753
|
-
const idx = this.points.length - 1;
|
|
35754
|
-
this.points[idx].grab(coord);
|
|
35755
|
-
|
|
35756
|
-
// We don't call updateCoordsFromPoints for
|
|
35757
|
-
// polygons, since the polygon won't be
|
|
35758
|
-
// closed yet.
|
|
35759
|
-
this.updatePolygon();
|
|
35760
|
-
}
|
|
35761
|
-
this.setState({
|
|
35762
|
-
shouldShowInstructions: false
|
|
35763
|
-
});
|
|
35764
|
-
}
|
|
35765
|
-
};
|
|
35766
|
-
resetGraphie = () => {
|
|
35767
|
-
this.shouldResetGraphie = false;
|
|
35768
|
-
this.parabola = null;
|
|
35769
|
-
this.sinusoid = null;
|
|
35770
|
-
// eslint-disable-next-line react/no-string-refs
|
|
35771
|
-
// @ts-expect-error - TS2339 - Property 'reset' does not exist on type 'ReactInstance'.
|
|
35772
|
-
this.refs.graph?.reset();
|
|
35773
|
-
};
|
|
35774
|
-
setupGraphie = () => {
|
|
35775
|
-
this.setTrashCanVisibility(0);
|
|
35776
|
-
if (this.isClickToAddPoints()) {
|
|
35777
|
-
this.setTrashCanVisibility(0.5);
|
|
35778
|
-
}
|
|
35779
|
-
if (this.props.apiOptions.isMobile) {
|
|
35780
|
-
this.horizHairline = new WrappedLine(this.graphie, [0, 0], [0, 0], {
|
|
35781
|
-
normalStyle: {
|
|
35782
|
-
strokeWidth: 1
|
|
35783
|
-
}
|
|
35784
|
-
});
|
|
35785
|
-
this.horizHairline.attr({
|
|
35786
|
-
stroke: KhanColors.INTERACTIVE
|
|
35787
|
-
});
|
|
35788
|
-
this.horizHairline?.hide();
|
|
35789
|
-
this.vertHairline = new WrappedLine(this.graphie, [0, 0], [0, 0], {
|
|
35790
|
-
normalStyle: {
|
|
35791
|
-
strokeWidth: 1
|
|
35792
|
-
}
|
|
35793
|
-
});
|
|
35794
|
-
this.vertHairline.attr({
|
|
35795
|
-
stroke: KhanColors.INTERACTIVE
|
|
35796
|
-
});
|
|
35797
|
-
this.vertHairline?.hide();
|
|
35798
|
-
}
|
|
35799
|
-
const type = this.props.graph.type;
|
|
35800
|
-
this["add" + capitalize(type) + "Controls"]();
|
|
35801
|
-
};
|
|
35802
|
-
showHairlines = point => {
|
|
35803
|
-
if (this.props.apiOptions.isMobile && this.props.markings !== "none") {
|
|
35804
|
-
// Hairlines are already initialized when the graph is loaded, so
|
|
35805
|
-
// here we just move them to the updated location and make them
|
|
35806
|
-
// visible.
|
|
35807
|
-
this.horizHairline?.moveTo([this.props.range[0][0], point[1]], [this.props.range[0][1], point[1]]);
|
|
35808
|
-
this.horizHairline?.show();
|
|
35809
|
-
this.vertHairline?.moveTo([point[0], this.props.range[1][0]], [point[0], this.props.range[1][1]]);
|
|
35810
|
-
this.vertHairline?.show();
|
|
35811
|
-
}
|
|
35812
|
-
};
|
|
35813
|
-
hideHairlines = () => {
|
|
35814
|
-
if (this.props.apiOptions.isMobile) {
|
|
35815
|
-
this.horizHairline?.hide();
|
|
35816
|
-
this.vertHairline?.hide();
|
|
35817
|
-
}
|
|
35818
|
-
};
|
|
35819
|
-
setTrashCanVisibility = opacity => {
|
|
35820
|
-
const graphie = this.graphie;
|
|
35821
|
-
if (kmath.number.equal(opacity, 0)) {
|
|
35822
|
-
if (this.trashCan) {
|
|
35823
|
-
this.trashCan.remove();
|
|
35824
|
-
this.trashCan = null;
|
|
35825
|
-
}
|
|
35826
|
-
} else if (!this.props.apiOptions.isMobile) {
|
|
35827
|
-
// Only if trash tooltips are not being used, we initialize the old
|
|
35828
|
-
// trash can area.
|
|
35829
|
-
if (!this.trashCan) {
|
|
35830
|
-
this.trashCan = graphie?.raphael?.image(TRASH_ICON_URI, graphie.xpixels - 40, graphie.ypixels - 40, 40, 40);
|
|
35831
|
-
}
|
|
35832
|
-
this.trashCan?.attr({
|
|
35833
|
-
opacity: opacity
|
|
35834
|
-
});
|
|
35835
|
-
}
|
|
35836
|
-
};
|
|
35837
|
-
isClickToAddPoints = props => {
|
|
35838
|
-
props = props || this.props;
|
|
35839
|
-
return _isClickToAddPoints(props);
|
|
35840
|
-
};
|
|
35841
|
-
areAngleGraphsEqual(prevGraph, currentGraph) {
|
|
35842
|
-
return prevGraph.allowReflexAngles !== currentGraph.allowReflexAngles || prevGraph.angleOffsetDeg !== currentGraph.angleOffsetDeg || prevGraph.snapDegrees !== currentGraph.snapDegrees;
|
|
35843
|
-
}
|
|
35844
|
-
arePointGraphsEqual(prevGraph, currentGraph) {
|
|
35845
|
-
return prevGraph.numPoints !== currentGraph.numPoints;
|
|
35846
|
-
}
|
|
35847
|
-
arePolygonGraphsEqual(prevGraph, currentGraph) {
|
|
35848
|
-
return prevGraph.numSides !== currentGraph.numSides || prevGraph.showAngles !== currentGraph.showAngles || prevGraph.showSides !== currentGraph.showSides || prevGraph.snapTo !== currentGraph.snapTo;
|
|
35849
|
-
}
|
|
35850
|
-
areSegmentGraphsEqual(prevGraph, currentGraph) {
|
|
35851
|
-
return prevGraph.numSegments !== currentGraph.numSegments;
|
|
35852
|
-
}
|
|
35853
|
-
_lineStroke = () => {
|
|
35854
|
-
// This should probably use: this.props.apiOptions.isMobile
|
|
35855
|
-
// @ts-expect-error - TS2339 - Property 'isMobile' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
|
|
35856
|
-
return this.props.isMobile ? {
|
|
35857
|
-
"stroke-width": 3
|
|
35858
|
-
} : {};
|
|
35859
|
-
};
|
|
35860
|
-
addLine = type => {
|
|
35861
|
-
const self = this;
|
|
35862
|
-
const graphie = self.graphie;
|
|
35863
|
-
const coords = InteractiveGraph.getLineCoords(self.props.graph, self.props);
|
|
35864
|
-
const points = self.points = ___default["default"].map(coords, coord => {
|
|
35865
|
-
return Interactive2.addMaybeMobileMovablePoint(this, {
|
|
35866
|
-
coord: coord,
|
|
35867
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap()],
|
|
35868
|
-
onMove: () => {
|
|
35869
|
-
const graph = ___default["default"].extend({}, self.props.graph, {
|
|
35870
|
-
coords: ___default["default"].invoke(points, "coord")
|
|
35871
|
-
});
|
|
35872
|
-
self.onChange({
|
|
35873
|
-
graph: graph
|
|
35874
|
-
});
|
|
35875
|
-
}
|
|
35876
|
-
});
|
|
35877
|
-
});
|
|
35878
|
-
const lineConfig = {
|
|
35879
|
-
points: points,
|
|
35880
|
-
static: true,
|
|
35881
|
-
normalStyle: {
|
|
35882
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE,
|
|
35883
|
-
...this._lineStroke()
|
|
35884
|
-
}
|
|
35885
|
-
};
|
|
35886
|
-
if (type === "line") {
|
|
35887
|
-
// @ts-expect-error - TS2339 - Property 'extendLine' does not exist on type '{ readonly points: any[]; readonly static: true; readonly normalStyle: { readonly "stroke-width"?: number | undefined; readonly stroke: "#71B307" | "#63D9EA"; }; }'.
|
|
35888
|
-
lineConfig.extendLine = true;
|
|
35889
|
-
} else if (type === "ray") {
|
|
35890
|
-
// @ts-expect-error - TS2339 - Property 'extendRay' does not exist on type '{ readonly points: any[]; readonly static: true; readonly normalStyle: { readonly "stroke-width"?: number | undefined; readonly stroke: "#71B307" | "#63D9EA"; }; }'.
|
|
35891
|
-
lineConfig.extendRay = true;
|
|
35892
|
-
}
|
|
35893
|
-
self.line = Interactive2.addMovableLine(graphie, lineConfig);
|
|
35894
|
-
|
|
35895
|
-
// A and B can't be in the same place
|
|
35896
|
-
points[0].listen("constraints", "isLine", coord => {
|
|
35897
|
-
return !kmath.point.equal(coord, points[1].coord());
|
|
35898
|
-
});
|
|
35899
|
-
points[1].listen("constraints", "isLine", coord => {
|
|
35900
|
-
return !kmath.point.equal(coord, points[0].coord());
|
|
35901
|
-
});
|
|
35902
|
-
};
|
|
35903
|
-
removeLine = () => {
|
|
35904
|
-
___default["default"].invoke(this.points, "remove");
|
|
35905
|
-
this.line?.remove();
|
|
35906
|
-
};
|
|
35907
|
-
addNoneControls = () => {};
|
|
35908
|
-
removeNoneControls = () => {};
|
|
35909
|
-
addLinearControls = () => {
|
|
35910
|
-
this.addLine("line");
|
|
35911
|
-
};
|
|
35912
|
-
removeLinearControls = () => {
|
|
35913
|
-
this.removeLine();
|
|
35914
|
-
};
|
|
35915
|
-
addQuadraticControls = () => {
|
|
35916
|
-
if (this.props.graph.type !== "quadratic") {
|
|
35917
|
-
throw makeInvalidTypeError("addQuadraticControls", "quadratic");
|
|
35918
|
-
}
|
|
35919
|
-
let coords = this.props.graph.coords;
|
|
35920
|
-
if (!coords) {
|
|
35921
|
-
coords = InteractiveGraph.defaultQuadraticCoords(this.props);
|
|
35922
|
-
}
|
|
35923
|
-
const onMoveHandler = () => {
|
|
35924
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
35925
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'. | TS2531 - Object is possibly 'null'. | TS2531 - Object is possibly 'null'.
|
|
35926
|
-
coords: [pointA.coord(), pointB.coord(), pointC.coord()]
|
|
35927
|
-
});
|
|
35928
|
-
this.onChange({
|
|
35929
|
-
graph
|
|
35930
|
-
});
|
|
35931
|
-
this.updateQuadratic();
|
|
35932
|
-
};
|
|
35933
|
-
|
|
35934
|
-
// NOTE(jeresig): This code attempts to access the variable while it's
|
|
35935
|
-
// still being defined, it should be refactored! The callbacks appear
|
|
35936
|
-
// to be executed synchronously, which causes this issue.
|
|
35937
|
-
let pointA = null;
|
|
35938
|
-
pointA = this.pointA = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
35939
|
-
coord: coords[0],
|
|
35940
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
35941
|
-
return !pointA ||
|
|
35942
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35943
|
-
coord[0] !== pointB.coord()[0] &&
|
|
35944
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35945
|
-
coord[0] !== pointC.coord()[0];
|
|
35946
|
-
}],
|
|
35947
|
-
onMove: onMoveHandler
|
|
35948
|
-
});
|
|
35949
|
-
|
|
35950
|
-
// NOTE(jeresig): This code attempts to access the variable while it's
|
|
35951
|
-
// still being defined, it should be refactored! The callbacks appear
|
|
35952
|
-
// to be executed synchronously, which causes this issue.
|
|
35953
|
-
let pointB = null;
|
|
35954
|
-
pointB = this.pointB = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
35955
|
-
coord: coords[1],
|
|
35956
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
35957
|
-
return !pointB ||
|
|
35958
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35959
|
-
coord[0] !== pointA.coord()[0] &&
|
|
35960
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35961
|
-
coord[0] !== pointC.coord()[0];
|
|
35962
|
-
}],
|
|
35963
|
-
onMove: onMoveHandler
|
|
35964
|
-
});
|
|
35965
|
-
|
|
35966
|
-
// NOTE(jeresig): This code attempts to access the variable while it's
|
|
35967
|
-
// still being defined, it should be refactored! The callbacks appear
|
|
35968
|
-
// to be executed synchronously, which causes this issue.
|
|
35969
|
-
let pointC = null;
|
|
35970
|
-
pointC = this.pointC = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
35971
|
-
coord: coords[2],
|
|
35972
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
35973
|
-
return !pointC ||
|
|
35974
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35975
|
-
coord[0] !== pointA.coord()[0] &&
|
|
35976
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
35977
|
-
coord[0] !== pointB.coord()[0];
|
|
35978
|
-
}],
|
|
35979
|
-
onMove: onMoveHandler
|
|
35980
|
-
});
|
|
35981
|
-
this.updateQuadratic();
|
|
35982
|
-
};
|
|
35983
|
-
updateQuadratic = () => {
|
|
35984
|
-
const coeffs = InteractiveGraph.getCurrentQuadraticCoefficients(this.props);
|
|
35985
|
-
if (!coeffs) {
|
|
35986
|
-
return;
|
|
35987
|
-
}
|
|
35988
|
-
|
|
35989
|
-
// Extract coefficients the parabola
|
|
35990
|
-
const a = coeffs[0];
|
|
35991
|
-
const b = coeffs[1];
|
|
35992
|
-
const c = coeffs[2];
|
|
35993
|
-
|
|
35994
|
-
// Plot and style
|
|
35995
|
-
if (this.parabola) {
|
|
35996
|
-
const path = this.graphie?.svgParabolaPath(a, b, c);
|
|
35997
|
-
this.parabola?.attr({
|
|
35998
|
-
path: path
|
|
35999
|
-
});
|
|
36000
|
-
} else {
|
|
36001
|
-
this.parabola = this.graphie?.parabola(a, b, c);
|
|
36002
|
-
this.parabola?.attr({
|
|
36003
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.DYNAMIC,
|
|
36004
|
-
...this._lineStroke()
|
|
36005
|
-
});
|
|
36006
|
-
this.parabola?.toBack();
|
|
36007
|
-
}
|
|
36008
|
-
};
|
|
36009
|
-
removeQuadraticControls = () => {
|
|
36010
|
-
this.pointA?.remove();
|
|
36011
|
-
this.pointB?.remove();
|
|
36012
|
-
this.pointC?.remove();
|
|
36013
|
-
if (this.parabola) {
|
|
36014
|
-
this.parabola.remove();
|
|
36015
|
-
this.parabola = null;
|
|
36016
|
-
}
|
|
36017
|
-
};
|
|
36018
|
-
addSinusoidControls = () => {
|
|
36019
|
-
if (this.props.graph.type !== "sinusoid") {
|
|
36020
|
-
throw makeInvalidTypeError("addSinusoidControls", "sinusoid");
|
|
36021
|
-
}
|
|
36022
|
-
let coords = this.props.graph.coords;
|
|
36023
|
-
if (!coords) {
|
|
36024
|
-
coords = InteractiveGraph.defaultSinusoidCoords(this.props);
|
|
36025
|
-
}
|
|
36026
|
-
const onMoveHandler = () => {
|
|
36027
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36028
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
36029
|
-
coords: [pointA.coord(), pointB.coord()]
|
|
36030
|
-
});
|
|
36031
|
-
this.onChange({
|
|
36032
|
-
graph: graph
|
|
36033
|
-
});
|
|
36034
|
-
this.updateSinusoid();
|
|
36035
|
-
};
|
|
36036
|
-
|
|
36037
|
-
// NOTE(jeresig): This code attempts to access the variable while it's
|
|
36038
|
-
// still being defined, it should be refactored! The callbacks appear
|
|
36039
|
-
// to be executed synchronously, which causes this issue.
|
|
36040
|
-
let pointA = null;
|
|
36041
|
-
pointA = this.pointA = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
36042
|
-
coord: coords[0],
|
|
36043
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
36044
|
-
return !pointA || coord[0] !== pointB.coord()[0];
|
|
36045
|
-
}],
|
|
36046
|
-
onMove: onMoveHandler
|
|
36047
|
-
});
|
|
36048
|
-
const pointB = this.pointB = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
36049
|
-
coord: coords[1],
|
|
36050
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
36051
|
-
// @ts-expect-error - TS2339 - Property 'coord' does not exist on type 'never'.
|
|
36052
|
-
return !pointA || coord[0] !== pointA.coord()[0];
|
|
36053
|
-
}],
|
|
36054
|
-
onMove: onMoveHandler
|
|
36055
|
-
});
|
|
36056
|
-
this.updateSinusoid();
|
|
36057
|
-
};
|
|
36058
|
-
updateSinusoid = () => {
|
|
36059
|
-
const coeffs = InteractiveGraph.getCurrentSinusoidCoefficients(this.props);
|
|
36060
|
-
if (!coeffs) {
|
|
36061
|
-
return;
|
|
36062
|
-
}
|
|
36063
|
-
const a = coeffs[0];
|
|
36064
|
-
const b = coeffs[1];
|
|
36065
|
-
const c = coeffs[2];
|
|
36066
|
-
const d = coeffs[3];
|
|
36067
|
-
|
|
36068
|
-
// Plot and style
|
|
36069
|
-
if (this.sinusoid) {
|
|
36070
|
-
const path = this.graphie?.svgSinusoidPath(a, b, c, d);
|
|
36071
|
-
this.sinusoid?.attr({
|
|
36072
|
-
path: path
|
|
36073
|
-
});
|
|
36074
|
-
} else {
|
|
36075
|
-
this.sinusoid = this.graphie?.sinusoid(a, b, c, d);
|
|
36076
|
-
this.sinusoid?.attr({
|
|
36077
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.DYNAMIC,
|
|
36078
|
-
...this._lineStroke()
|
|
36079
|
-
});
|
|
36080
|
-
this.sinusoid?.toBack();
|
|
36081
|
-
}
|
|
36082
|
-
};
|
|
36083
|
-
removeSinusoidControls = () => {
|
|
36084
|
-
this.pointA?.remove();
|
|
36085
|
-
this.pointB?.remove();
|
|
36086
|
-
if (this.sinusoid) {
|
|
36087
|
-
this.sinusoid.remove();
|
|
36088
|
-
this.sinusoid = null;
|
|
36089
|
-
}
|
|
36090
|
-
};
|
|
36091
|
-
addCircleControls = () => {
|
|
36092
|
-
const graphie = this.graphie;
|
|
36093
|
-
const minSnap = ___default["default"].min(graphie.snap);
|
|
36094
|
-
const circle = this.circle = graphie?.addCircleGraph({
|
|
36095
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'.
|
|
36096
|
-
center: this.props.graph.center || [0, 0],
|
|
36097
|
-
// @ts-expect-error - TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
36098
|
-
radius: this.props.graph.radius || ___default["default"].min(this.props.step),
|
|
36099
|
-
snapX: graphie.snap[0],
|
|
36100
|
-
snapY: graphie.snap[1],
|
|
36101
|
-
minRadius: minSnap,
|
|
36102
|
-
snapRadius: minSnap
|
|
36103
|
-
});
|
|
36104
|
-
$__default["default"](circle).on("move", () => {
|
|
36105
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36106
|
-
center: circle.center,
|
|
36107
|
-
radius: circle.radius
|
|
36108
|
-
});
|
|
36109
|
-
this.onChange({
|
|
36110
|
-
graph: graph
|
|
36111
|
-
});
|
|
36112
|
-
});
|
|
36113
|
-
};
|
|
36114
|
-
removeCircleControls = () => {
|
|
36115
|
-
this.circle?.remove();
|
|
36116
|
-
};
|
|
36117
|
-
addLinearSystemControls = () => {
|
|
36118
|
-
const graphie = this.graphie;
|
|
36119
|
-
const coords = InteractiveGraph.getLinearSystemCoords(this.props.graph, this.props);
|
|
36120
|
-
const segmentColors = [KhanColors.INTERACTIVE, KhanColors.GREEN];
|
|
36121
|
-
const points = this.points = ___default["default"].map(coords, (segmentCoords, segmentIndex) => {
|
|
36122
|
-
// NOTE(jeresig): This code attempts to access the variable
|
|
36123
|
-
// while it's still being defined, it should be refactored!
|
|
36124
|
-
// The callbacks appear to be executed synchronously, which
|
|
36125
|
-
// causes this issue.
|
|
36126
|
-
const segmentPoints = [];
|
|
36127
|
-
for (let i = 0; i < segmentCoords.length; i += 1) {
|
|
36128
|
-
const coord = segmentCoords[i];
|
|
36129
|
-
segmentPoints.push(Interactive2.addMaybeMobileMovablePoint(this, {
|
|
36130
|
-
coord: coord,
|
|
36131
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
36132
|
-
const otherSegment = segmentPoints[1 - i];
|
|
36133
|
-
if (!otherSegment) {
|
|
36134
|
-
// segment hasn't been defined yet
|
|
36135
|
-
// because we're still creating them
|
|
36136
|
-
return;
|
|
36137
|
-
}
|
|
36138
|
-
return !kmath.point.equal(coord, otherSegment.coord());
|
|
36139
|
-
}],
|
|
36140
|
-
onMove: () => {
|
|
36141
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36142
|
-
coords: ___default["default"].map(this.points, segment => ___default["default"].invoke(segment, "coord"))
|
|
36143
|
-
});
|
|
36144
|
-
this.onChange({
|
|
36145
|
-
graph: graph
|
|
36146
|
-
});
|
|
36147
|
-
},
|
|
36148
|
-
normalStyle: {
|
|
36149
|
-
fill: segmentColors[segmentIndex]
|
|
36150
|
-
},
|
|
36151
|
-
highlightStyle: {
|
|
36152
|
-
fill: segmentColors[segmentIndex]
|
|
36153
|
-
}
|
|
36154
|
-
}));
|
|
36155
|
-
}
|
|
36156
|
-
return segmentPoints;
|
|
36157
|
-
});
|
|
36158
|
-
this.lines = ___default["default"].map(points, (segmentPoints, segmentIndex) => {
|
|
36159
|
-
return Interactive2.addMovableLine(graphie, {
|
|
36160
|
-
points: segmentPoints,
|
|
36161
|
-
static: true,
|
|
36162
|
-
extendLine: true,
|
|
36163
|
-
normalStyle: {
|
|
36164
|
-
stroke: segmentColors[segmentIndex]
|
|
36165
|
-
}
|
|
36166
|
-
});
|
|
36167
|
-
});
|
|
36168
|
-
};
|
|
36169
|
-
removeLinearSystemControls = () => {
|
|
36170
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
36171
|
-
___default["default"].invoke(this.lines, "remove");
|
|
36172
|
-
___default["default"].map(this.points, segment => ___default["default"].invoke(segment, "remove"));
|
|
36173
|
-
};
|
|
36174
|
-
isCoordInTrash = coord => {
|
|
36175
|
-
if (this.props.apiOptions.isMobile) {
|
|
36176
|
-
return false;
|
|
36177
|
-
}
|
|
36178
|
-
const graphie = this.graphie;
|
|
36179
|
-
const screenPoint = graphie?.scalePoint(coord);
|
|
36180
|
-
return screenPoint[0] >= graphie.xpixels - 40 && screenPoint[1] >= graphie.ypixels - 40;
|
|
36181
|
-
};
|
|
36182
|
-
createPointForPointsType = (coord, i) => {
|
|
36183
|
-
const self = this;
|
|
36184
|
-
const remove = () => {
|
|
36185
|
-
self.points = ___default["default"].filter(self.points, function (pt) {
|
|
36186
|
-
return pt !== point;
|
|
36187
|
-
});
|
|
36188
|
-
// update the correct answer box
|
|
36189
|
-
self.updateCoordsFromPoints();
|
|
36190
|
-
|
|
36191
|
-
// remove this movablePoint from graphie.
|
|
36192
|
-
// we wait to do this until we're not inside of
|
|
36193
|
-
// said point's onMoveEnd method so its state is
|
|
36194
|
-
// consistent throughout this method call
|
|
36195
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
36196
|
-
// @ts-expect-error - TS2531 - Object is possibly 'null'.
|
|
36197
|
-
setTimeout(point.remove.bind(point), 0); // eslint-disable-line no-restricted-syntax
|
|
36198
|
-
};
|
|
36199
|
-
|
|
36200
|
-
// NOTE(jeresig): This code attempts to access the variable while it's
|
|
36201
|
-
// still being defined, it should be refactored! The callbacks appear
|
|
36202
|
-
// to be executed synchronously, which causes this issue.
|
|
36203
|
-
let point = null;
|
|
36204
|
-
point = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
36205
|
-
coord: coord,
|
|
36206
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), function (coord) {
|
|
36207
|
-
// TODO(jack): There ought to be a
|
|
36208
|
-
// MovablePoint.constraints.avoid
|
|
36209
|
-
// default that lets you do things like this
|
|
36210
|
-
return ___default["default"].all(self.points, function (pt) {
|
|
36211
|
-
return point === pt || !kmath.point.equal(coord, pt.coord());
|
|
36212
|
-
});
|
|
36213
|
-
}],
|
|
36214
|
-
onMoveStart: function () {
|
|
36215
|
-
if (self.isClickToAddPoints()) {
|
|
36216
|
-
self.setTrashCanVisibility(1);
|
|
36217
|
-
}
|
|
36218
|
-
},
|
|
36219
|
-
onMove: self.updateCoordsFromPoints,
|
|
36220
|
-
onMoveEnd: function (coord) {
|
|
36221
|
-
if (self.isClickToAddPoints()) {
|
|
36222
|
-
if (self.isCoordInTrash(coord)) {
|
|
36223
|
-
remove();
|
|
36224
|
-
}
|
|
36225
|
-
// In case we mouseup'd off the graphie and that
|
|
36226
|
-
// stopped the move (in which case, we might not
|
|
36227
|
-
// be in isCoordInTrash()
|
|
36228
|
-
self.setTrashCanVisibility(0.5);
|
|
36229
|
-
}
|
|
36230
|
-
},
|
|
36231
|
-
...(this.props.apiOptions.isMobile && self.isClickToAddPoints() ? {
|
|
36232
|
-
onRemove: remove
|
|
36233
|
-
} : {})
|
|
36234
|
-
});
|
|
36235
|
-
return point;
|
|
36236
|
-
};
|
|
36237
|
-
removePoint = point => {
|
|
36238
|
-
let index = null;
|
|
36239
|
-
this.points = ___default["default"].filter(this.points, function (pt, i) {
|
|
36240
|
-
if (pt === point) {
|
|
36241
|
-
// @ts-expect-error - TS2322 - Type 'number' is not assignable to type 'null'.
|
|
36242
|
-
index = i;
|
|
36243
|
-
return false;
|
|
36244
|
-
}
|
|
36245
|
-
return true;
|
|
36246
|
-
});
|
|
36247
|
-
return index;
|
|
36248
|
-
};
|
|
36249
|
-
createPointForPolygonType = (coord, i) => {
|
|
36250
|
-
if (this.props.graph.type !== "polygon") {
|
|
36251
|
-
throw makeInvalidTypeError("createPointForPolygonType", "polygon");
|
|
36252
|
-
}
|
|
36253
|
-
|
|
36254
|
-
// TODO(alex): check against "grid" instead, use constants
|
|
36255
|
-
const snapToGrid = !___default["default"].contains(["angles", "sides"], this.props.graph.snapTo);
|
|
36256
|
-
|
|
36257
|
-
// Index relative to current point -> absolute index
|
|
36258
|
-
// NOTE: This does not work when isClickToAddPoints() == true,
|
|
36259
|
-
// as `i` can be changed by dragging a point to the trash
|
|
36260
|
-
// Currently this function is only called when !isClickToAddPoints()
|
|
36261
|
-
const rel = j => {
|
|
36262
|
-
return (i + j + this.points.length) % this.points.length;
|
|
36263
|
-
};
|
|
36264
|
-
const remove = () => {
|
|
36265
|
-
// remove this point from points
|
|
36266
|
-
const index = this.removePoint(point);
|
|
36267
|
-
if (this.polygon?.closed()) {
|
|
36268
|
-
// We should be checking if this.points is defined before rotating them.
|
|
36269
|
-
// @ts-expect-error - TS4104 - The type 'readonly any[]' is 'readonly' and cannot be assigned to the mutable type 'any[]'. | TS2345 - Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | undefined'.
|
|
36270
|
-
this.points = rotate(this.points, index);
|
|
36271
|
-
this.polygon?.update({
|
|
36272
|
-
closed: false
|
|
36273
|
-
});
|
|
36274
|
-
}
|
|
36275
|
-
this.updatePolygon();
|
|
36276
|
-
// the polygon is now unclosed, so we need to
|
|
36277
|
-
// remove any points props
|
|
36278
|
-
this.clearCoords();
|
|
36279
|
-
|
|
36280
|
-
// remove this movablePoint from graphie.
|
|
36281
|
-
// wait to do this until we're not inside of
|
|
36282
|
-
// said point's onMoveEnd method so state is
|
|
36283
|
-
// consistent throughout the method call
|
|
36284
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
36285
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
36286
|
-
setTimeout(point.remove.bind(point), 0);
|
|
36287
|
-
};
|
|
36288
|
-
const setInitialMove = debounce__default["default"]((point, bool) => {
|
|
36289
|
-
point.state.isInitialMove = bool;
|
|
36290
|
-
}, 1000);
|
|
36291
|
-
const onMoveEndHandler = coord => {
|
|
36292
|
-
// If the user clicked and dragged a point over endpoint,
|
|
36293
|
-
// join the them
|
|
36294
|
-
const overlappedPoints = this.points.length > 1 && (point === this.points[0] && kmath.point.equal(coord, ___default["default"].last(this.points).coord()) || point === ___default["default"].last(this.points) && kmath.point.equal(coord, this.points[0].coord()));
|
|
36295
|
-
if (this.isClickToAddPoints()) {
|
|
36296
|
-
if (this.isCoordInTrash(coord)) {
|
|
36297
|
-
remove();
|
|
36298
|
-
} else if (overlappedPoints) {
|
|
36299
|
-
const pointToRemove = this.points.pop();
|
|
36300
|
-
if (this.points.length > 2) {
|
|
36301
|
-
this.polygon?.update({
|
|
36302
|
-
closed: true
|
|
36303
|
-
});
|
|
36304
|
-
this.updateCoordsFromPoints();
|
|
36305
|
-
} else {
|
|
36306
|
-
this.polygon?.update({
|
|
36307
|
-
closed: false
|
|
36308
|
-
});
|
|
36309
|
-
this.clearCoords();
|
|
36310
|
-
}
|
|
36311
|
-
this.updatePolygon();
|
|
36312
|
-
// remove this movablePoint from graphie.
|
|
36313
|
-
// wait to do this until we're not inside of
|
|
36314
|
-
// said point's onMoveEnd method so state is
|
|
36315
|
-
// consistent throughout the method call
|
|
36316
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
36317
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
36318
|
-
setTimeout(pointToRemove.remove.bind(pointToRemove), 0);
|
|
36319
|
-
} else {
|
|
36320
|
-
// If the user clicked and dragged a point over any other
|
|
36321
|
-
// existing point, fix shape
|
|
36322
|
-
const shouldRemove = ___default["default"].any(this.points, function (pt) {
|
|
36323
|
-
return pt !== point && kmath.point.equal(pt.coord(), coord);
|
|
36324
|
-
});
|
|
36325
|
-
if (shouldRemove) {
|
|
36326
|
-
this.removePoint(point);
|
|
36327
|
-
if (this.points.length < 3) {
|
|
36328
|
-
this.polygon?.update({
|
|
36329
|
-
closed: false
|
|
36330
|
-
});
|
|
36331
|
-
this.clearCoords();
|
|
36332
|
-
} else if (this.polygon?.closed()) {
|
|
36333
|
-
this.updateCoordsFromPoints();
|
|
36334
|
-
}
|
|
36335
|
-
this.updatePolygon();
|
|
36336
|
-
// remove this movablePoint from graphie.
|
|
36337
|
-
// wait to do this until we're not inside
|
|
36338
|
-
// said point's onMoveEnd method so state
|
|
36339
|
-
// is consistent throughout the method call
|
|
36340
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
36341
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
36342
|
-
setTimeout(point.remove.bind(point), 0);
|
|
36343
|
-
} else {
|
|
36344
|
-
// If this was
|
|
36345
|
-
// * a click on the first or last point
|
|
36346
|
-
// * and not a deletion
|
|
36347
|
-
// * and not a drag,
|
|
36348
|
-
// * and not a creation of a new point
|
|
36349
|
-
// (see !point.state.isInitialMove, below),
|
|
36350
|
-
// * and our polygon is not closed,
|
|
36351
|
-
// * and we can close it (we need at least the same
|
|
36352
|
-
// number of points as coordinates asked for in the
|
|
36353
|
-
// question),
|
|
36354
|
-
// then close it
|
|
36355
|
-
const isFirstOrLastPoint = point === ___default["default"].head(this.points) || point === ___default["default"].last(this.points);
|
|
36356
|
-
const pointNotMoved = !point.hasMoved();
|
|
36357
|
-
// NOTE (jsatk): Currently different on mobile versus
|
|
36358
|
-
// destkop
|
|
36359
|
-
const notANewPoint = !point.state.isInitialMove;
|
|
36360
|
-
const polygonNotClosed = !this.polygon?.closed();
|
|
36361
|
-
const allPointsPlotted = this.points.length > 2;
|
|
36362
|
-
const canCloseUnclosedPolygon = isFirstOrLastPoint && pointNotMoved && notANewPoint && polygonNotClosed && allPointsPlotted;
|
|
36363
|
-
if (canCloseUnclosedPolygon) {
|
|
36364
|
-
this.polygon?.update({
|
|
36365
|
-
closed: true
|
|
36366
|
-
});
|
|
36367
|
-
this.updatePolygon();
|
|
36368
|
-
|
|
36369
|
-
// We finally have a closed polygon, so save our
|
|
36370
|
-
// points to props
|
|
36371
|
-
this.updateCoordsFromPoints();
|
|
36372
|
-
}
|
|
36373
|
-
}
|
|
36374
|
-
}
|
|
36375
|
-
|
|
36376
|
-
// In case we mouseup'd off the graphie and that
|
|
36377
|
-
// stopped the move
|
|
36378
|
-
this.setTrashCanVisibility(0.5);
|
|
36379
|
-
}
|
|
36380
|
-
setInitialMove(point, false);
|
|
36381
|
-
};
|
|
36382
|
-
const graphConstraint = coord => {
|
|
36383
|
-
// These constraints are all relative to the other points, so if
|
|
36384
|
-
// we're creating the initial points and haven't added any others
|
|
36385
|
-
// to the graph, we can't enforce them.
|
|
36386
|
-
if (this.points == null || this.points.length === 0) {
|
|
36387
|
-
return true;
|
|
36388
|
-
}
|
|
36389
|
-
const coords = ___default["default"].invoke(this.points, "coord");
|
|
36390
|
-
coords[i] = coord;
|
|
36391
|
-
|
|
36392
|
-
// Check for invalid positioning, but only if we aren't adding
|
|
36393
|
-
// points one click at a time, since those added points could
|
|
36394
|
-
// have already violated these constraints
|
|
36395
|
-
if (!this.isClickToAddPoints()) {
|
|
36396
|
-
// Polygons can't have consecutive collinear points
|
|
36397
|
-
if (collinear(coords[rel(-2)], coords[rel(-1)], coords[i]) || collinear(coords[rel(-1)], coords[i], coords[rel(1)]) || collinear(coords[i], coords[rel(1)], coords[rel(2)])) {
|
|
36398
|
-
return false;
|
|
36399
|
-
}
|
|
36400
|
-
const segments = ___default["default"].zip(coords, rotate(coords));
|
|
36401
|
-
if (this.points.length > 3) {
|
|
36402
|
-
// Constrain to simple (non self-intersecting) polygon by
|
|
36403
|
-
// testing whether adjacent segments intersect any others
|
|
36404
|
-
for (let j = -1; j <= 0; j++) {
|
|
36405
|
-
const segment = segments[rel(j)];
|
|
36406
|
-
const others = ___default["default"].without(segments, segment, segments[rel(j - 1)], segments[rel(j + 1)]);
|
|
36407
|
-
for (let k = 0; k < others.length; k++) {
|
|
36408
|
-
const other = others[k];
|
|
36409
|
-
// @ts-expect-error - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'Line'.
|
|
36410
|
-
if (intersects(segment, other)) {
|
|
36411
|
-
return false;
|
|
36412
|
-
}
|
|
36413
|
-
}
|
|
36414
|
-
}
|
|
36415
|
-
}
|
|
36416
|
-
}
|
|
36417
|
-
if (
|
|
36418
|
-
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
36419
|
-
this.props.graph.snapTo === "angles" && this.points.length > 2) {
|
|
36420
|
-
// Snap to whole degree interior angles
|
|
36421
|
-
|
|
36422
|
-
const angles = ___default["default"].map(angleMeasures(coords), function (rad) {
|
|
36423
|
-
return rad * 180 / Math.PI;
|
|
36424
|
-
});
|
|
36425
|
-
___default["default"].each([-1, 1], function (j) {
|
|
36426
|
-
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
36427
|
-
});
|
|
36428
|
-
const getAngle = function (a, vertex, b) {
|
|
36429
|
-
const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
|
|
36430
|
-
return (angle + 360) % 360;
|
|
36431
|
-
};
|
|
36432
|
-
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
36433
|
-
innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
|
|
36434
|
-
|
|
36435
|
-
// Avoid degenerate triangles
|
|
36436
|
-
if (___default["default"].any(innerAngles, function (angle) {
|
|
36437
|
-
return leq(angle, 1);
|
|
36438
|
-
})) {
|
|
36439
|
-
return false;
|
|
36440
|
-
}
|
|
36441
|
-
const knownSide = magnitude(
|
|
36442
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
36443
|
-
vector(coords[rel(-1)], coords[rel(1)]));
|
|
36444
|
-
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
36445
|
-
|
|
36446
|
-
// Solve for side by using the law of sines
|
|
36447
|
-
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
36448
|
-
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
36449
|
-
const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
36450
|
-
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
36451
|
-
}
|
|
36452
|
-
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
36453
|
-
if (this.props.graph.snapTo === "sides" && this.points.length > 1) {
|
|
36454
|
-
// Snap to whole unit side measures
|
|
36455
|
-
|
|
36456
|
-
const sides = ___default["default"].map([[coords[rel(-1)], coords[i]], [coords[i], coords[rel(1)]], [coords[rel(-1)], coords[rel(1)]]], function (coords) {
|
|
36457
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36458
|
-
return magnitude(vector(...coords));
|
|
36459
|
-
});
|
|
36460
|
-
___default["default"].each([0, 1], function (j) {
|
|
36461
|
-
sides[j] = Math.round(sides[j]);
|
|
36462
|
-
});
|
|
36463
|
-
|
|
36464
|
-
// Avoid degenerate triangles
|
|
36465
|
-
if (leq(sides[1] + sides[2], sides[0]) || leq(sides[0] + sides[2], sides[1]) || leq(sides[0] + sides[1], sides[2])) {
|
|
36466
|
-
return false;
|
|
36467
|
-
}
|
|
36468
|
-
|
|
36469
|
-
// Solve for angle by using the law of cosines
|
|
36470
|
-
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
36471
|
-
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
36472
|
-
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
36473
|
-
const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
36474
|
-
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
36475
|
-
}
|
|
36476
|
-
// Snap to grid (already done)
|
|
36477
|
-
return true;
|
|
36478
|
-
};
|
|
36479
|
-
const point = Interactive2.addMaybeMobileMovablePoint(this, {
|
|
36480
|
-
coord: coord,
|
|
36481
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), snapToGrid ? Interactive2.MovablePoint.constraints.snap() : null, graphConstraint],
|
|
36482
|
-
onMoveStart: () => {
|
|
36483
|
-
if (this.isClickToAddPoints()) {
|
|
36484
|
-
this.setTrashCanVisibility(1);
|
|
36485
|
-
}
|
|
36486
|
-
},
|
|
36487
|
-
onMove: () => {
|
|
36488
|
-
if (this.polygon?.closed()) {
|
|
36489
|
-
this.updateCoordsFromPoints();
|
|
36490
|
-
}
|
|
36491
|
-
},
|
|
36492
|
-
onMoveEnd: onMoveEndHandler,
|
|
36493
|
-
...(this.props.apiOptions.isMobile && this.isClickToAddPoints() ? {
|
|
36494
|
-
onRemove: remove
|
|
36495
|
-
} : {})
|
|
36496
|
-
});
|
|
36497
|
-
point.state.isInitialMove = true;
|
|
36498
|
-
return point;
|
|
36499
|
-
};
|
|
36500
|
-
updateCoordsFromPoints = () => {
|
|
36501
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36502
|
-
// Handle old movable points with .coord, or
|
|
36503
|
-
// Interactive2.MovablePoint's with .coord()
|
|
36504
|
-
coords: ___default["default"].map(this.points, function (point) {
|
|
36505
|
-
return ___default["default"].result(point, "coord");
|
|
36506
|
-
})
|
|
36507
|
-
});
|
|
36508
|
-
this.onChange({
|
|
36509
|
-
graph: graph
|
|
36510
|
-
});
|
|
36511
|
-
};
|
|
36512
|
-
clearCoords = () => {
|
|
36513
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36514
|
-
coords: null
|
|
36515
|
-
});
|
|
36516
|
-
this.onChange({
|
|
36517
|
-
graph: graph
|
|
36518
|
-
});
|
|
36519
|
-
};
|
|
36520
|
-
onChange = data => {
|
|
36521
|
-
this.props.onChange(data);
|
|
36522
|
-
this.props.trackInteraction();
|
|
36523
|
-
};
|
|
36524
|
-
addPointControls = () => {
|
|
36525
|
-
if (this.props.graph.type !== "point") {
|
|
36526
|
-
throw makeInvalidTypeError("addPointControls", "point");
|
|
36527
|
-
}
|
|
36528
|
-
const coords = InteractiveGraph.getPointCoords(this.props.graph, this.props);
|
|
36529
|
-
// Clear out our old points so that newly added points don't
|
|
36530
|
-
// "collide" with them and reposition when being added
|
|
36531
|
-
// Without this, when added, each point checks whether it is on top
|
|
36532
|
-
// of a point in this.points, which (a) shouldn't matter since
|
|
36533
|
-
// we're clearing out this.points anyways, and (b) can cause problems
|
|
36534
|
-
// if each of this.points is a MovablePoint instead of an
|
|
36535
|
-
// Interactive2.MovablePoint, since one has a .coord and the other
|
|
36536
|
-
// has .coord()
|
|
36537
|
-
// TODO(jack): Figure out a better way to do this
|
|
36538
|
-
this.points = [];
|
|
36539
|
-
this.points = ___default["default"].map(coords, this.createPointForPointsType, this);
|
|
36540
|
-
};
|
|
36541
|
-
removePointControls = () => {
|
|
36542
|
-
___default["default"].invoke(this.points, "remove");
|
|
36543
|
-
};
|
|
36544
|
-
addSegmentControls = () => {
|
|
36545
|
-
if (this.props.graph.type !== "segment") {
|
|
36546
|
-
throw makeInvalidTypeError("addSegmentControls", "segment");
|
|
36547
|
-
}
|
|
36548
|
-
const self = this;
|
|
36549
|
-
const graphie = this.graphie;
|
|
36550
|
-
const coords = InteractiveGraph.getSegmentCoords(this.props.graph, this.props);
|
|
36551
|
-
const createPoint = options => Interactive2.addMaybeMobileMovablePoint(this, options);
|
|
36552
|
-
this.points = [];
|
|
36553
|
-
this.lines = ___default["default"].map(coords, function (segment, i) {
|
|
36554
|
-
const updateCoordProps = function () {
|
|
36555
|
-
const graph = ___default["default"].extend({}, self.props.graph, {
|
|
36556
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
36557
|
-
coords: ___default["default"].invoke(self.lines, "coords")
|
|
36558
|
-
});
|
|
36559
|
-
self.onChange({
|
|
36560
|
-
graph: graph
|
|
36561
|
-
});
|
|
36562
|
-
};
|
|
36563
|
-
|
|
36564
|
-
// NOTE(jeresig): This code attempts to access the variable
|
|
36565
|
-
// while it's still being defined, it should be refactored!
|
|
36566
|
-
// The callbacks appear to be executed synchronously, which
|
|
36567
|
-
// causes this issue.
|
|
36568
|
-
const points = [];
|
|
36569
|
-
for (let i = 0; i < segment.length; i += 1) {
|
|
36570
|
-
const coord = segment[i];
|
|
36571
|
-
points.push(createPoint({
|
|
36572
|
-
coord: coord,
|
|
36573
|
-
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
36574
|
-
const otherPoint = points[1 - i];
|
|
36575
|
-
if (!otherPoint) {
|
|
36576
|
-
// point hasn't been defined yet
|
|
36577
|
-
// because we're still creating them
|
|
36578
|
-
// We return false as the points aren't
|
|
36579
|
-
// going to be in the same position at
|
|
36580
|
-
// this point.
|
|
36581
|
-
return false;
|
|
36582
|
-
}
|
|
36583
|
-
return !kmath.point.equal(coord, otherPoint.coord());
|
|
36584
|
-
}],
|
|
36585
|
-
onMove: updateCoordProps
|
|
36586
|
-
}));
|
|
36587
|
-
}
|
|
36588
|
-
self.points = self.points.concat(points);
|
|
36589
|
-
const line = Interactive2.addMovableLine(graphie, {
|
|
36590
|
-
points: points,
|
|
36591
|
-
static: false,
|
|
36592
|
-
constraints: [
|
|
36593
|
-
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
|
|
36594
|
-
Interactive2.MovableLine.constraints.bound(),
|
|
36595
|
-
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
|
|
36596
|
-
Interactive2.MovableLine.constraints.snap()],
|
|
36597
|
-
onMove: [
|
|
36598
|
-
// @ts-expect-error - TS2339 - Property 'onMove' does not exist on type '(graphie: any, movable: any, options: any) => void'.
|
|
36599
|
-
Interactive2.MovableLine.onMove.updatePoints, updateCoordProps],
|
|
36600
|
-
normalStyle: {
|
|
36601
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36602
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE,
|
|
36603
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36604
|
-
...this._lineStroke()
|
|
36605
|
-
},
|
|
36606
|
-
highlightStyle: {
|
|
36607
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36608
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTING,
|
|
36609
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36610
|
-
...this._lineStroke()
|
|
36611
|
-
}
|
|
36612
|
-
});
|
|
36613
|
-
___default["default"].invoke(points, "toFront");
|
|
36614
|
-
return line;
|
|
36615
|
-
}, this);
|
|
36616
|
-
};
|
|
36617
|
-
removeSegmentControls = () => {
|
|
36618
|
-
___default["default"].invoke(this.points, "remove");
|
|
36619
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly (readonly Coord[])[] | null | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
36620
|
-
___default["default"].invoke(this.lines, "remove");
|
|
36621
|
-
};
|
|
36622
|
-
addRayControls = () => {
|
|
36623
|
-
this.addLine("ray");
|
|
36624
|
-
};
|
|
36625
|
-
removeRayControls = () => {
|
|
36626
|
-
this.removeLine();
|
|
36627
|
-
};
|
|
36628
|
-
addPolygonControls = () => {
|
|
36629
|
-
this.polygon = null;
|
|
36630
|
-
const coords = InteractiveGraph.getPolygonCoords(this.props.graph, this.props);
|
|
36631
|
-
// Clear out our old points so that newly added points don't
|
|
36632
|
-
// "collide", as in `addPointControls`
|
|
36633
|
-
this.points = [];
|
|
36634
|
-
this.points = ___default["default"].map(coords, this.createPointForPolygonType, this);
|
|
36635
|
-
this.updatePolygon();
|
|
36636
|
-
};
|
|
36637
|
-
updatePolygon = () => {
|
|
36638
|
-
if (this.props.graph.type !== "polygon") {
|
|
36639
|
-
throw makeInvalidTypeError("updatePolygon", "polygon");
|
|
36640
|
-
}
|
|
36641
|
-
let closed;
|
|
36642
|
-
if (this.polygon) {
|
|
36643
|
-
closed = this.polygon.closed();
|
|
36644
|
-
} else if (this.points.length >= 3) {
|
|
36645
|
-
closed = true;
|
|
36646
|
-
} else {
|
|
36647
|
-
// There will only be fewer than 3 points in click-to-add-vertices
|
|
36648
|
-
// mode, so we don't need to explicitly check for that here.
|
|
36649
|
-
closed = false;
|
|
36650
|
-
}
|
|
36651
|
-
const graphie = this.graphie;
|
|
36652
|
-
const n = this.points.length;
|
|
36653
|
-
|
|
36654
|
-
// TODO(alex): check against "grid" instead, use constants
|
|
36655
|
-
const snapToGrid = !___default["default"].contains(["angles", "sides"], this.props.graph.snapTo);
|
|
36656
|
-
const angleLabels = ___default["default"].times(n, function (i) {
|
|
36657
|
-
if (
|
|
36658
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36659
|
-
!this.props.graph.showAngles || !closed && (i === 0 || i === n - 1)) {
|
|
36660
|
-
return "";
|
|
36661
|
-
}
|
|
36662
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36663
|
-
if (this.props.graph.snapTo === "angles") {
|
|
36664
|
-
return "$deg0";
|
|
36665
|
-
}
|
|
36666
|
-
return "$deg1";
|
|
36667
|
-
}, this);
|
|
36668
|
-
const showRightAngleMarkers = ___default["default"].times(n, function (i) {
|
|
36669
|
-
return closed || i !== 0 && i !== n - 1;
|
|
36670
|
-
}, this);
|
|
36671
|
-
const numArcs = ___default["default"].times(n, function (i) {
|
|
36672
|
-
if (
|
|
36673
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36674
|
-
this.props.graph.showAngles && (closed || i !== 0 && i !== n - 1)) {
|
|
36675
|
-
return 1;
|
|
36676
|
-
}
|
|
36677
|
-
return 0;
|
|
36678
|
-
}, this);
|
|
36679
|
-
const sideLabels = ___default["default"].times(n, function (i) {
|
|
36680
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36681
|
-
if (!this.props.graph.showSides || !closed && i === n - 1) {
|
|
36682
|
-
return "";
|
|
36683
|
-
}
|
|
36684
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36685
|
-
if (this.props.graph.snapTo === "sides") {
|
|
36686
|
-
return "$len0";
|
|
36687
|
-
}
|
|
36688
|
-
return "$len1";
|
|
36689
|
-
}, this);
|
|
36690
|
-
if (this.polygon == null) {
|
|
36691
|
-
const self = this;
|
|
36692
|
-
self.polygon = Interactive2.addMovablePolygon(graphie, {
|
|
36693
|
-
constraints: [
|
|
36694
|
-
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
|
|
36695
|
-
Interactive2.MovablePolygon.constraints.bound(), snapToGrid ?
|
|
36696
|
-
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
|
|
36697
|
-
Interactive2.MovablePolygon.constraints.snap() : null],
|
|
36698
|
-
closed: closed,
|
|
36699
|
-
points: self.points,
|
|
36700
|
-
angleLabels: angleLabels,
|
|
36701
|
-
showRightAngleMarkers: showRightAngleMarkers,
|
|
36702
|
-
numArcs: numArcs,
|
|
36703
|
-
sideLabels: sideLabels,
|
|
36704
|
-
onMove: [
|
|
36705
|
-
// @ts-expect-error - TS2339 - Property 'onMove' does not exist on type '(graphie: any, movable: any, options: any) => undefined'.
|
|
36706
|
-
Interactive2.MovablePolygon.onMove.updatePoints, function () {
|
|
36707
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36708
|
-
if (this.closed()) {
|
|
36709
|
-
self.updateCoordsFromPoints();
|
|
36710
|
-
}
|
|
36711
|
-
}],
|
|
36712
|
-
normalStyle: {
|
|
36713
|
-
stroke: this.props.apiOptions.isMobile ? KhanColors.BLUE_C : KhanColors.INTERACTIVE,
|
|
36714
|
-
...this._lineStroke()
|
|
36715
|
-
}
|
|
36716
|
-
});
|
|
36717
|
-
} else {
|
|
36718
|
-
// We only need to pass in the properties that might've changed
|
|
36719
|
-
this.polygon.update({
|
|
36720
|
-
closed: closed,
|
|
36721
|
-
points: this.points,
|
|
36722
|
-
angleLabels: angleLabels,
|
|
36723
|
-
showRightAngleMarkers: showRightAngleMarkers,
|
|
36724
|
-
numArcs: numArcs,
|
|
36725
|
-
sideLabels: sideLabels
|
|
36726
|
-
});
|
|
36727
|
-
}
|
|
36728
|
-
};
|
|
36729
|
-
removePolygonControls = () => {
|
|
36730
|
-
___default["default"].invoke(this.points, "remove");
|
|
36731
|
-
this.polygon?.remove();
|
|
36732
|
-
};
|
|
36733
|
-
addAngleControls = () => {
|
|
36734
|
-
if (this.props.graph.type !== "angle") {
|
|
36735
|
-
throw makeInvalidTypeError("addAngleControls", "angle");
|
|
36736
|
-
}
|
|
36737
|
-
const graph = this.props.graph;
|
|
36738
|
-
const graphie = this.graphie;
|
|
36739
|
-
const coords = InteractiveGraph.getAngleCoords(this.props.graph, this.props);
|
|
36740
|
-
|
|
36741
|
-
// The vertex snaps to the grid, but the rays don't...
|
|
36742
|
-
this.points = coords.map(function (coord, i) {
|
|
36743
|
-
return graphie?.addMovablePoint(___default["default"].extend({
|
|
36744
|
-
coord: coord,
|
|
36745
|
-
normalStyle: {
|
|
36746
|
-
stroke: KhanColors.INTERACTIVE,
|
|
36747
|
-
fill: KhanColors.INTERACTIVE
|
|
36748
|
-
}
|
|
36749
|
-
}, i === 1 ? {
|
|
36750
|
-
snapX: graphie.snap[0],
|
|
36751
|
-
snapY: graphie.snap[1]
|
|
36752
|
-
} : {}));
|
|
36753
|
-
});
|
|
36754
|
-
|
|
36755
|
-
// ...they snap to whole-degree angles from the vertex.
|
|
36756
|
-
this.angle = graphie?.addMovableAngle({
|
|
36757
|
-
points: this.points,
|
|
36758
|
-
snapDegrees: graph.snapDegrees || 1,
|
|
36759
|
-
snapOffsetDeg: graph.angleOffsetDeg || 0,
|
|
36760
|
-
angleLabel: graph.showAngles ? "$deg0" : "",
|
|
36761
|
-
pushOut: 2,
|
|
36762
|
-
allowReflex: defaultVal(graph.allowReflexAngles, true)
|
|
36763
|
-
});
|
|
36764
|
-
$__default["default"](this.angle).on("move", () => {
|
|
36765
|
-
this.onChange({
|
|
36766
|
-
graph: {
|
|
36767
|
-
...graph,
|
|
36768
|
-
coords: this.angle?.getClockwiseCoords()
|
|
36769
|
-
}
|
|
36770
|
-
});
|
|
36771
|
-
});
|
|
36772
|
-
};
|
|
36773
|
-
removeAngleControls = () => {
|
|
36774
|
-
___default["default"].invoke(this.points, "remove");
|
|
36775
|
-
this.angle?.remove();
|
|
36776
|
-
};
|
|
36777
|
-
toggleShowSides = () => {
|
|
36778
|
-
if (this.props.graph.type !== "polygon") {
|
|
36779
|
-
throw makeInvalidTypeError("toggleShowSides", "polygon");
|
|
36780
|
-
}
|
|
36781
|
-
const graph = ___default["default"].extend({}, this.props.graph, {
|
|
36782
|
-
showSides: !this.props.graph.showSides
|
|
36783
|
-
});
|
|
36784
|
-
this.onChange({
|
|
36785
|
-
graph: graph
|
|
36786
|
-
});
|
|
36787
|
-
};
|
|
36788
|
-
getUserInput() {
|
|
36789
|
-
return InteractiveGraph.getUserInputFromProps(this.props);
|
|
36790
|
-
}
|
|
36791
|
-
focus = $__default["default"].noop;
|
|
36792
|
-
render() {
|
|
36793
|
-
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
36794
|
-
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
36795
|
-
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
36796
|
-
let instructions;
|
|
36797
|
-
// isClickToAddPoints() only applies to points and polygons
|
|
36798
|
-
if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
|
|
36799
|
-
if (this.props.graph.type === "point") {
|
|
36800
|
-
instructions = this.context.strings.addPoints;
|
|
36801
|
-
} else if (this.props.graph.type === "polygon") {
|
|
36802
|
-
instructions = this.context.strings.addVertices;
|
|
36803
|
-
}
|
|
36804
|
-
} else {
|
|
36805
|
-
instructions = undefined;
|
|
36806
|
-
}
|
|
36807
|
-
let onMouseDown;
|
|
36808
|
-
if (this.isClickToAddPoints()) {
|
|
36809
|
-
onMouseDown = this.handleAddPointsMouseDown;
|
|
36810
|
-
}
|
|
36811
|
-
const isMobile = this.props.apiOptions.isMobile;
|
|
36812
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
36813
|
-
className: "perseus-widget " + "perseus-widget-interactive-graph",
|
|
36814
|
-
style: {
|
|
36815
|
-
width: box[0],
|
|
36816
|
-
height: box[1]
|
|
36817
|
-
}
|
|
36818
|
-
}, /*#__PURE__*/React__namespace.createElement(Graph, {
|
|
36819
|
-
instructions: instructions
|
|
36820
|
-
// eslint-disable-next-line react/no-string-refs
|
|
36821
|
-
,
|
|
36822
|
-
ref: "graph",
|
|
36823
|
-
box: box,
|
|
36824
|
-
labels: this.props.labels,
|
|
36825
|
-
range: this.props.range,
|
|
36826
|
-
step: isMobile ? Util.constrainedTickStepsFromTickSteps(this.props.step, this.props.range) : this.props.step,
|
|
36827
|
-
gridStep: gridStep,
|
|
36828
|
-
snapStep: snapStep,
|
|
36829
|
-
markings: this.props.markings,
|
|
36830
|
-
backgroundImage: this.props.backgroundImage,
|
|
36831
|
-
showProtractor: this.props.showProtractor,
|
|
36832
|
-
onMouseDown: onMouseDown,
|
|
36833
|
-
onGraphieUpdated: this.setGraphie,
|
|
36834
|
-
setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
|
|
36835
|
-
isMobile: isMobile
|
|
36836
|
-
}));
|
|
36837
|
-
}
|
|
36838
|
-
}
|
|
36839
35532
|
class InteractiveGraph extends React__namespace.Component {
|
|
36840
35533
|
legacyGraphRef = /*#__PURE__*/React__namespace.createRef();
|
|
36841
35534
|
mafsRef = /*#__PURE__*/React__namespace.createRef();
|
|
@@ -36864,25 +35557,17 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
36864
35557
|
return getPromptJSON$b(this.props, this.getUserInput());
|
|
36865
35558
|
}
|
|
36866
35559
|
render() {
|
|
36867
|
-
|
|
36868
|
-
const
|
|
36869
|
-
|
|
36870
|
-
|
|
36871
|
-
|
|
36872
|
-
|
|
36873
|
-
|
|
36874
|
-
|
|
36875
|
-
|
|
36876
|
-
|
|
36877
|
-
|
|
36878
|
-
box: box,
|
|
36879
|
-
showTooltips: !!this.props.showTooltips,
|
|
36880
|
-
readOnly: this.props.apiOptions?.readOnly
|
|
36881
|
-
}));
|
|
36882
|
-
}
|
|
36883
|
-
return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
|
|
36884
|
-
ref: this.legacyGraphRef
|
|
36885
|
-
}, this.props));
|
|
35560
|
+
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
35561
|
+
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
35562
|
+
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
35563
|
+
return /*#__PURE__*/React__namespace.createElement(StatefulMafsGraph, _extends({}, this.props, {
|
|
35564
|
+
ref: this.mafsRef,
|
|
35565
|
+
gridStep: gridStep,
|
|
35566
|
+
snapStep: snapStep,
|
|
35567
|
+
box: box,
|
|
35568
|
+
showTooltips: !!this.props.showTooltips,
|
|
35569
|
+
readOnly: this.props.apiOptions?.readOnly
|
|
35570
|
+
}));
|
|
36886
35571
|
}
|
|
36887
35572
|
|
|
36888
35573
|
/**
|
|
@@ -37198,28 +35883,6 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
37198
35883
|
}
|
|
37199
35884
|
}
|
|
37200
35885
|
|
|
37201
|
-
// exported for testing
|
|
37202
|
-
function shouldUseMafs(mafsFlags, graph) {
|
|
37203
|
-
if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
|
|
37204
|
-
return false;
|
|
37205
|
-
}
|
|
37206
|
-
switch (graph.type) {
|
|
37207
|
-
case "none":
|
|
37208
|
-
return true;
|
|
37209
|
-
case "point":
|
|
37210
|
-
if (graph.numPoints === UNLIMITED) {
|
|
37211
|
-
return Boolean(mafsFlags["unlimited-point"]);
|
|
37212
|
-
}
|
|
37213
|
-
return Boolean(mafsFlags["point"]);
|
|
37214
|
-
case "polygon":
|
|
37215
|
-
if (graph.numSides === UNLIMITED) {
|
|
37216
|
-
return Boolean(mafsFlags["unlimited-polygon"]);
|
|
37217
|
-
}
|
|
37218
|
-
return Boolean(mafsFlags["polygon"]);
|
|
37219
|
-
default:
|
|
37220
|
-
return Boolean(mafsFlags[graph.type]);
|
|
37221
|
-
}
|
|
37222
|
-
}
|
|
37223
35886
|
// We don't need to change any of the original props for static mode
|
|
37224
35887
|
const staticTransform$3 = ___default["default"].identity;
|
|
37225
35888
|
var InteractiveGraph$1 = {
|
|
@@ -37227,9 +35890,7 @@ var InteractiveGraph$1 = {
|
|
|
37227
35890
|
displayName: "Interactive graph (Assessments only)",
|
|
37228
35891
|
widget: InteractiveGraph,
|
|
37229
35892
|
staticTransform: staticTransform$3,
|
|
37230
|
-
|
|
37231
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
37232
|
-
scorer: perseusScore.scoreInteractiveGraph
|
|
35893
|
+
getPublicWidgetOptions: perseusCore.getInteractiveGraphPublicWidgetOptions
|
|
37233
35894
|
};
|
|
37234
35895
|
|
|
37235
35896
|
// TODO: can we use WB typography?
|
|
@@ -38187,9 +36848,7 @@ var LabelImage$1 = {
|
|
|
38187
36848
|
widget: LabelImageWithDependencies,
|
|
38188
36849
|
accessible: true,
|
|
38189
36850
|
isLintable: true,
|
|
38190
|
-
|
|
38191
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
38192
|
-
scorer: perseusScore.scoreLabelImage
|
|
36851
|
+
getPublicWidgetOptions: perseusCore.getLabelImagePublicWidgetOptions
|
|
38193
36852
|
};
|
|
38194
36853
|
|
|
38195
36854
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -38426,7 +37085,7 @@ class Draggable extends React__namespace.Component {
|
|
|
38426
37085
|
// We need to keep backwards compatbility with rules specified directly
|
|
38427
37086
|
// in CSS. Hence the hacky tacking on of manual classNames.
|
|
38428
37087
|
// See sortable.less for details.
|
|
38429
|
-
let className = aphrodite.css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " +
|
|
37088
|
+
let className = aphrodite.css(styles$8.card, styles$8.draggable, layout === "horizontal" && styles$8.horizontalCard, layout === "vertical" && styles$8.verticalCard, type === ItemState.DRAGGING && styles$8.dragging, type === ItemState.DISABLED && styles$8.disabled, !includePadding && styles$8.unpaddedCard) + " " + "perseus-sortable-draggable";
|
|
38430
37089
|
if (!includePadding) {
|
|
38431
37090
|
className += " perseus-sortable-draggable-unpadded";
|
|
38432
37091
|
}
|
|
@@ -39117,10 +37776,7 @@ var Matcher$1 = {
|
|
|
39117
37776
|
name: "matcher",
|
|
39118
37777
|
displayName: "Matcher (two column)",
|
|
39119
37778
|
widget: Matcher,
|
|
39120
|
-
isLintable: true
|
|
39121
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
39122
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
39123
|
-
scorer: perseusScore.scoreMatcher
|
|
37779
|
+
isLintable: true
|
|
39124
37780
|
};
|
|
39125
37781
|
|
|
39126
37782
|
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
@@ -39524,12 +38180,7 @@ var Matrix$1 = {
|
|
|
39524
38180
|
transform: propTransform$1,
|
|
39525
38181
|
staticTransform: staticTransform$2,
|
|
39526
38182
|
isLintable: true,
|
|
39527
|
-
|
|
39528
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39529
|
-
scorer: perseusScore.scoreMatrix,
|
|
39530
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
39531
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39532
|
-
validator: perseusScore.validateMatrix
|
|
38183
|
+
getPublicWidgetOptions: perseusCore.getMatrixPublicWidgetOptions
|
|
39533
38184
|
};
|
|
39534
38185
|
|
|
39535
38186
|
const getPromptJSON$7 = () => {
|
|
@@ -39619,7 +38270,7 @@ class Measurer extends React__namespace.Component {
|
|
|
39619
38270
|
// TODO(scottgrant): This isn't a11y-friendly! We should insist on
|
|
39620
38271
|
// finding some valid alt text when this widget is used.
|
|
39621
38272
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
39622
|
-
className: "perseus-widget perseus-widget-measurer " + "graphie-container
|
|
38273
|
+
className: "perseus-widget perseus-widget-measurer " + "graphie-container blank-background",
|
|
39623
38274
|
style: {
|
|
39624
38275
|
width: this.props.box[0],
|
|
39625
38276
|
height: this.props.box[1]
|
|
@@ -39644,9 +38295,7 @@ var Measurer$1 = {
|
|
|
39644
38295
|
hidden: true,
|
|
39645
38296
|
widget: Measurer,
|
|
39646
38297
|
version: perseusCore.measurerLogic.version,
|
|
39647
|
-
propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades
|
|
39648
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39649
|
-
scorer: () => scoreNoop(1)
|
|
38298
|
+
propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades
|
|
39650
38299
|
};
|
|
39651
38300
|
|
|
39652
38301
|
/**
|
|
@@ -40534,8 +39183,7 @@ var Molecule$1 = {
|
|
|
40534
39183
|
name: "molecule-renderer",
|
|
40535
39184
|
displayName: "Molecule renderer",
|
|
40536
39185
|
hidden: true,
|
|
40537
|
-
widget: MoleculeWidget
|
|
40538
|
-
scorer: () => scoreNoop()
|
|
39186
|
+
widget: MoleculeWidget
|
|
40539
39187
|
};
|
|
40540
39188
|
|
|
40541
39189
|
const getPromptJSON$6 = (renderProps, userInput) => {
|
|
@@ -41110,12 +39758,7 @@ var NumberLine$1 = {
|
|
|
41110
39758
|
widget: NumberLine,
|
|
41111
39759
|
transform: numberLineTransform,
|
|
41112
39760
|
staticTransform: staticTransform$1,
|
|
41113
|
-
|
|
41114
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
41115
|
-
scorer: perseusScore.scoreNumberLine,
|
|
41116
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
41117
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
41118
|
-
validator: perseusScore.validateNumberLine
|
|
39761
|
+
getPublicWidgetOptions: perseusCore.getNumberLinePublicWidgetOptions
|
|
41119
39762
|
};
|
|
41120
39763
|
|
|
41121
39764
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -41133,7 +39776,7 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
41133
39776
|
class PlaceholderCard extends React__namespace.Component {
|
|
41134
39777
|
render() {
|
|
41135
39778
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
41136
|
-
className: "card-wrap
|
|
39779
|
+
className: "card-wrap",
|
|
41137
39780
|
style: {
|
|
41138
39781
|
width: this.props.width
|
|
41139
39782
|
}
|
|
@@ -41148,7 +39791,7 @@ class PlaceholderCard extends React__namespace.Component {
|
|
|
41148
39791
|
class DragHintCard extends React__namespace.Component {
|
|
41149
39792
|
render() {
|
|
41150
39793
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
41151
|
-
className: "card-wrap
|
|
39794
|
+
className: "card-wrap"
|
|
41152
39795
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
41153
39796
|
className: "card drag-hint"
|
|
41154
39797
|
}));
|
|
@@ -41283,7 +39926,7 @@ class Card extends React__namespace.Component {
|
|
|
41283
39926
|
const rendererProps = ___default["default"].pick(this.props, "content");
|
|
41284
39927
|
const onMouseDown = this.props.animating ? $__default["default"].noop : this.onMouseDown;
|
|
41285
39928
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
41286
|
-
className: "card-wrap
|
|
39929
|
+
className: "card-wrap",
|
|
41287
39930
|
style: style,
|
|
41288
39931
|
onMouseDown: onMouseDown,
|
|
41289
39932
|
onTouchStart: onMouseDown,
|
|
@@ -41654,7 +40297,7 @@ class Orderer extends React__namespace.Component {
|
|
|
41654
40297
|
});
|
|
41655
40298
|
}, this));
|
|
41656
40299
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
41657
|
-
className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "
|
|
40300
|
+
className: "draggy-boxy-thing orderer " + "height-" + this.props.height + " " + "layout-" + this.props.layout + " " + "blank-background " + "perseus-clearfix "
|
|
41658
40301
|
// eslint-disable-next-line react/no-string-refs
|
|
41659
40302
|
,
|
|
41660
40303
|
ref: "orderer"
|
|
@@ -41667,12 +40310,6 @@ var Orderer$1 = {
|
|
|
41667
40310
|
hidden: true,
|
|
41668
40311
|
widget: Orderer,
|
|
41669
40312
|
isLintable: true,
|
|
41670
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
41671
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41672
|
-
scorer: perseusScore.scoreOrderer,
|
|
41673
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
41674
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41675
|
-
validator: perseusScore.validateOrderer,
|
|
41676
40313
|
getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
|
|
41677
40314
|
};
|
|
41678
40315
|
|
|
@@ -41716,15 +40353,12 @@ class PassageRefTarget extends React__namespace.Component {
|
|
|
41716
40353
|
var PassageRefTarget$1 = {
|
|
41717
40354
|
name: "passage-ref-target",
|
|
41718
40355
|
displayName: "PassageRefTarget",
|
|
41719
|
-
defaultAlignment: "inline",
|
|
41720
40356
|
widget: PassageRefTarget,
|
|
41721
40357
|
hidden: true,
|
|
41722
40358
|
transform: editorProps => {
|
|
41723
40359
|
return ___default["default"].pick(editorProps, "content");
|
|
41724
40360
|
},
|
|
41725
|
-
isLintable: true
|
|
41726
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41727
|
-
scorer: () => scoreNoop()
|
|
40361
|
+
isLintable: true
|
|
41728
40362
|
};
|
|
41729
40363
|
|
|
41730
40364
|
const getPromptJSON$4 = () => {
|
|
@@ -42726,7 +41360,7 @@ class Plotter extends React__namespace.Component {
|
|
|
42726
41360
|
marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0
|
|
42727
41361
|
};
|
|
42728
41362
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
42729
|
-
className: "perseus-widget-plotter graphie
|
|
41363
|
+
className: "perseus-widget-plotter graphie"
|
|
42730
41364
|
// eslint-disable-next-line react/no-string-refs
|
|
42731
41365
|
,
|
|
42732
41366
|
ref: "graphieDiv",
|
|
@@ -42743,12 +41377,7 @@ var Plotter$1 = {
|
|
|
42743
41377
|
hidden: true,
|
|
42744
41378
|
widget: Plotter,
|
|
42745
41379
|
staticTransform: staticTransform,
|
|
42746
|
-
|
|
42747
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42748
|
-
scorer: perseusScore.scorePlotter,
|
|
42749
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
42750
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42751
|
-
validator: perseusScore.validatePlotter
|
|
41380
|
+
getPublicWidgetOptions: perseusCore.getPlotterPublicWidgetOptions
|
|
42752
41381
|
};
|
|
42753
41382
|
|
|
42754
41383
|
const getPromptJSON$2 = () => {
|
|
@@ -42909,12 +41538,7 @@ var Sorter$1 = {
|
|
|
42909
41538
|
displayName: "Sorter",
|
|
42910
41539
|
widget: Sorter,
|
|
42911
41540
|
isLintable: true,
|
|
42912
|
-
|
|
42913
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42914
|
-
scorer: perseusScore.scoreSorter,
|
|
42915
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
42916
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42917
|
-
validator: perseusScore.validateSorter
|
|
41541
|
+
getPublicWidgetOptions: perseusCore.getSorterPublicWidgetOptions
|
|
42918
41542
|
};
|
|
42919
41543
|
|
|
42920
41544
|
const {
|
|
@@ -43131,12 +41755,7 @@ var Table$1 = {
|
|
|
43131
41755
|
transform: propTransform,
|
|
43132
41756
|
hidden: true,
|
|
43133
41757
|
isLintable: true,
|
|
43134
|
-
|
|
43135
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43136
|
-
scorer: perseusScore.scoreTable,
|
|
43137
|
-
// TODO(LEMS-2656): remove TS suppression
|
|
43138
|
-
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43139
|
-
validator: perseusScore.validateTable
|
|
41758
|
+
getPublicWidgetOptions: perseusCore.getTablePublicWidgetOptions
|
|
43140
41759
|
};
|
|
43141
41760
|
|
|
43142
41761
|
const getPromptJSON = () => {
|
|
@@ -43298,11 +41917,7 @@ class Video extends React__namespace.Component {
|
|
|
43298
41917
|
var Video$1 = {
|
|
43299
41918
|
name: "video",
|
|
43300
41919
|
displayName: "Video",
|
|
43301
|
-
|
|
43302
|
-
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
43303
|
-
widget: Video,
|
|
43304
|
-
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
43305
|
-
scorer: () => scoreNoop()
|
|
41920
|
+
widget: Video
|
|
43306
41921
|
};
|
|
43307
41922
|
|
|
43308
41923
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -43321,7 +41936,7 @@ const init = function () {
|
|
|
43321
41936
|
|
|
43322
41937
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43323
41938
|
const libName = "@khanacademy/perseus";
|
|
43324
|
-
const libVersion = "
|
|
41939
|
+
const libVersion = "53.1.0";
|
|
43325
41940
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43326
41941
|
|
|
43327
41942
|
const apiVersion = {
|
|
@@ -44197,6 +42812,42 @@ const ref = /*#__PURE__*/React__namespace.forwardRef((props, ref) => /*#__PURE__
|
|
|
44197
42812
|
}));
|
|
44198
42813
|
}));
|
|
44199
42814
|
|
|
42815
|
+
function isCorrect(score) {
|
|
42816
|
+
return score.type === "points" && score.earned >= score.total;
|
|
42817
|
+
}
|
|
42818
|
+
function keScoreFromPerseusScore(score,
|
|
42819
|
+
// It's weird, but this is what we're passing it
|
|
42820
|
+
guess, state) {
|
|
42821
|
+
if (score.type === "points") {
|
|
42822
|
+
return {
|
|
42823
|
+
empty: false,
|
|
42824
|
+
correct: isCorrect(score),
|
|
42825
|
+
message: score.message,
|
|
42826
|
+
guess: guess,
|
|
42827
|
+
state: state
|
|
42828
|
+
};
|
|
42829
|
+
}
|
|
42830
|
+
if (score.type === "invalid") {
|
|
42831
|
+
return {
|
|
42832
|
+
empty: true,
|
|
42833
|
+
correct: false,
|
|
42834
|
+
message: score.message,
|
|
42835
|
+
suppressAlmostThere: score.suppressAlmostThere,
|
|
42836
|
+
guess: guess,
|
|
42837
|
+
state: state
|
|
42838
|
+
};
|
|
42839
|
+
}
|
|
42840
|
+
throw new perseusCore.PerseusError(
|
|
42841
|
+
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
42842
|
+
"Invalid score type: " + score.type, perseusCore.Errors.InvalidInput, {
|
|
42843
|
+
metadata: {
|
|
42844
|
+
score: JSON.stringify(score),
|
|
42845
|
+
guess: JSON.stringify(guess),
|
|
42846
|
+
state: JSON.stringify(state)
|
|
42847
|
+
}
|
|
42848
|
+
});
|
|
42849
|
+
}
|
|
42850
|
+
|
|
44200
42851
|
/**
|
|
44201
42852
|
* Traverses a {content, widgets, images} renderer props object,
|
|
44202
42853
|
* such as `itemData.question`
|
|
@@ -44217,7 +42868,7 @@ const deepCallbackFor = function (contentCallback, widgetCallback, optionsCallba
|
|
|
44217
42868
|
// This doesn't modify the widget info if the widget info
|
|
44218
42869
|
// is at a later version than is supported, which is important
|
|
44219
42870
|
// for our latestVersion test below.
|
|
44220
|
-
const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
42871
|
+
const upgradedWidgetInfo = perseusCore.upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
44221
42872
|
const latestVersion = getVersion(upgradedWidgetInfo.type);
|
|
44222
42873
|
|
|
44223
42874
|
// Only traverse our children if we can understand this version
|
|
@@ -44329,7 +42980,7 @@ const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion
|
|
|
44329
42980
|
|
|
44330
42981
|
// NOTE: This doesn't modify the widget info if the widget info
|
|
44331
42982
|
// is at a later version than is supported.
|
|
44332
|
-
const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
42983
|
+
const upgradedWidgetInfo = perseusCore.upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
44333
42984
|
return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
|
|
44334
42985
|
};
|
|
44335
42986
|
const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
|
|
@@ -45677,7 +44328,6 @@ exports.mediaQueries = mediaQueries;
|
|
|
45677
44328
|
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
45678
44329
|
exports.preprocessTex = preprocessTex;
|
|
45679
44330
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
45680
|
-
exports.scorePerseusItem = scorePerseusItem;
|
|
45681
44331
|
exports.shouldHaveIndividualAnswer = shouldHaveIndividualAnswer;
|
|
45682
44332
|
exports.traverse = traverse;
|
|
45683
44333
|
exports.usePerseusI18n = usePerseusI18n;
|