@khanacademy/perseus 20.8.1 → 21.0.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/es/index.js +413 -295
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +426 -303
- package/dist/index.js.map +1 -1
- package/dist/util/extract-perseus-data.d.ts +6 -7
- package/dist/util/snowman-utils.d.ts +0 -72
- package/dist/util/test-utils.d.ts +37 -2
- package/dist/util/widget-enum-utils.d.ts +1 -3
- package/dist/widget-type-utils.d.ts +64 -0
- package/dist/widgets/interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/components/angle.d.ts +12 -2
- package/dist/widgets/interactive-graphs/graphs/components/css-transform-wrapper.d.ts +7 -0
- package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +6 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +2 -0
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -1921,7 +1921,7 @@ const KhanAnswerTypes = {
|
|
|
1921
1921
|
}
|
|
1922
1922
|
};
|
|
1923
1923
|
|
|
1924
|
-
const _excluded$
|
|
1924
|
+
const _excluded$5 = ["scoring"];
|
|
1925
1925
|
const ERROR_TITLE = i18n._("Oops!");
|
|
1926
1926
|
const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
|
|
1927
1927
|
|
|
@@ -2009,7 +2009,7 @@ class Expression extends React.Component {
|
|
|
2009
2009
|
let {
|
|
2010
2010
|
scoring
|
|
2011
2011
|
} = _ref,
|
|
2012
|
-
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
2012
|
+
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
|
|
2013
2013
|
const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
|
|
2014
2014
|
|
|
2015
2015
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
@@ -18832,7 +18832,7 @@ const isAccessible = widgetInfo => {
|
|
|
18832
18832
|
}
|
|
18833
18833
|
return !!accessible;
|
|
18834
18834
|
};
|
|
18835
|
-
const getAllWidgetTypes
|
|
18835
|
+
const getAllWidgetTypes = () => {
|
|
18836
18836
|
return _.keys(widgets);
|
|
18837
18837
|
};
|
|
18838
18838
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
@@ -19101,7 +19101,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19101
19101
|
getVersionVector: getVersionVector,
|
|
19102
19102
|
getPublicWidgets: getPublicWidgets,
|
|
19103
19103
|
isAccessible: isAccessible,
|
|
19104
|
-
getAllWidgetTypes: getAllWidgetTypes
|
|
19104
|
+
getAllWidgetTypes: getAllWidgetTypes,
|
|
19105
19105
|
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
19106
19106
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
19107
19107
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
@@ -19339,6 +19339,9 @@ class Renderer extends React.Component {
|
|
|
19339
19339
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19340
19340
|
const newValues = {};
|
|
19341
19341
|
if (!widgetInfo.type) {
|
|
19342
|
+
// TODO: why does widget have no type?
|
|
19343
|
+
// We don't want to derive type from widget ID
|
|
19344
|
+
// see: LEMS-1845
|
|
19342
19345
|
newValues.type = widgetId.split(" ")[0];
|
|
19343
19346
|
}
|
|
19344
19347
|
if (!widgetInfo.alignment) {
|
|
@@ -21428,7 +21431,7 @@ InputWithExamples.defaultProps = {
|
|
|
21428
21431
|
className: ""
|
|
21429
21432
|
};
|
|
21430
21433
|
|
|
21431
|
-
const _excluded$
|
|
21434
|
+
const _excluded$4 = ["keypadElement", "onFocus", "value"];
|
|
21432
21435
|
class SimpleKeypadInput extends React.Component {
|
|
21433
21436
|
constructor(...args) {
|
|
21434
21437
|
super(...args);
|
|
@@ -21467,7 +21470,7 @@ class SimpleKeypadInput extends React.Component {
|
|
|
21467
21470
|
onFocus,
|
|
21468
21471
|
value
|
|
21469
21472
|
} = _this$props,
|
|
21470
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
21473
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
|
|
21471
21474
|
return (
|
|
21472
21475
|
/*#__PURE__*/
|
|
21473
21476
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
@@ -23298,14 +23301,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
|
|
|
23298
23301
|
forwardedRef: ref
|
|
23299
23302
|
})));
|
|
23300
23303
|
|
|
23301
|
-
const _excluded$
|
|
23304
|
+
const _excluded$3 = ["showContent", "content", "forwardedRef"];
|
|
23302
23305
|
const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
|
|
23303
23306
|
const {
|
|
23304
23307
|
showContent,
|
|
23305
23308
|
content,
|
|
23306
23309
|
forwardedRef
|
|
23307
23310
|
} = props,
|
|
23308
|
-
rest = _objectWithoutPropertiesLoose(props, _excluded$
|
|
23311
|
+
rest = _objectWithoutPropertiesLoose(props, _excluded$3);
|
|
23309
23312
|
const choiceProps = _extends({}, rest, {
|
|
23310
23313
|
content: showContent ? content :
|
|
23311
23314
|
/*#__PURE__*/
|
|
@@ -28261,6 +28264,10 @@ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
|
|
|
28261
28264
|
return [xMax, yAtXMax];
|
|
28262
28265
|
}
|
|
28263
28266
|
};
|
|
28267
|
+
const getLines = points => points.map((point, i) => {
|
|
28268
|
+
const next = points[(i + 1) % points.length];
|
|
28269
|
+
return [point, next];
|
|
28270
|
+
});
|
|
28264
28271
|
|
|
28265
28272
|
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
28266
28273
|
const MovableLine = props => {
|
|
@@ -28521,11 +28528,52 @@ const RayGraph = props => {
|
|
|
28521
28528
|
}));
|
|
28522
28529
|
};
|
|
28523
28530
|
|
|
28531
|
+
/**
|
|
28532
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
28533
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
28534
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
28535
|
+
*/
|
|
28536
|
+
const MafsCssTransformWrapper = ({
|
|
28537
|
+
children
|
|
28538
|
+
}) => /*#__PURE__*/React.createElement("g", {
|
|
28539
|
+
style: {
|
|
28540
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28541
|
+
}
|
|
28542
|
+
}, children);
|
|
28543
|
+
|
|
28544
|
+
const _excluded$2 = ["children"];
|
|
28545
|
+
const TextLabel = _ref => {
|
|
28546
|
+
let {
|
|
28547
|
+
children
|
|
28548
|
+
} = _ref,
|
|
28549
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
28550
|
+
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
28551
|
+
size: 16,
|
|
28552
|
+
svgTextProps: {
|
|
28553
|
+
filter: "url(#background)",
|
|
28554
|
+
fontWeight: "bold"
|
|
28555
|
+
}
|
|
28556
|
+
}, rest), children);
|
|
28557
|
+
};
|
|
28558
|
+
const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*#__PURE__*/React__default.createElement("filter", {
|
|
28559
|
+
id: "background",
|
|
28560
|
+
x: "-5%",
|
|
28561
|
+
width: "110%",
|
|
28562
|
+
y: "0%",
|
|
28563
|
+
height: "100%"
|
|
28564
|
+
}, /*#__PURE__*/React__default.createElement("feFlood", {
|
|
28565
|
+
floodColor: "#FFF",
|
|
28566
|
+
floodOpacity: "0.64"
|
|
28567
|
+
}), /*#__PURE__*/React__default.createElement("feComposite", {
|
|
28568
|
+
operator: "over",
|
|
28569
|
+
in: "SourceGraphic"
|
|
28570
|
+
})));
|
|
28571
|
+
|
|
28524
28572
|
const Angle = ({
|
|
28525
28573
|
centerPoint,
|
|
28526
28574
|
endPoints,
|
|
28527
28575
|
range,
|
|
28528
|
-
|
|
28576
|
+
polygonLines,
|
|
28529
28577
|
showAngles
|
|
28530
28578
|
}) => {
|
|
28531
28579
|
const [centerX, centerY] = centerPoint;
|
|
@@ -28547,7 +28595,7 @@ const Angle = ({
|
|
|
28547
28595
|
// Used to calculate the inside of the angle and to create square
|
|
28548
28596
|
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
28549
28597
|
if (!showAngles) {
|
|
28550
|
-
return isRightAngle(angle) ? /*#__PURE__*/React.createElement(
|
|
28598
|
+
return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28551
28599
|
start: [x1, y1],
|
|
28552
28600
|
vertex: [x2, y2],
|
|
28553
28601
|
end: [x3, y3]
|
|
@@ -28555,13 +28603,13 @@ const Angle = ({
|
|
|
28555
28603
|
}
|
|
28556
28604
|
|
|
28557
28605
|
// Midpoint betwen ends of arc
|
|
28558
|
-
const
|
|
28559
|
-
const largeArcFlag =
|
|
28560
|
-
const sweepFlag =
|
|
28606
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
28607
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
28608
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
28561
28609
|
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
28562
28610
|
let angleInDegrees = angle * (180 / Math.PI);
|
|
28563
28611
|
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
28564
|
-
if (
|
|
28612
|
+
if (isOutside) {
|
|
28565
28613
|
angleInDegrees = 360 - angleInDegrees;
|
|
28566
28614
|
}
|
|
28567
28615
|
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
|
|
@@ -28578,80 +28626,54 @@ const Angle = ({
|
|
|
28578
28626
|
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
28579
28627
|
operator: "over",
|
|
28580
28628
|
in: "SourceGraphic"
|
|
28581
|
-
}))), !
|
|
28629
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28582
28630
|
start: [x1, y1],
|
|
28583
28631
|
vertex: [x2, y2],
|
|
28584
28632
|
end: [x3, y3]
|
|
28585
|
-
}) : /*#__PURE__*/React.createElement("
|
|
28586
|
-
style: {
|
|
28587
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28588
|
-
}
|
|
28589
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
28633
|
+
}) : /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28590
28634
|
d: arc,
|
|
28591
28635
|
strokeWidth: 0.02,
|
|
28592
28636
|
fill: "none"
|
|
28593
|
-
})), /*#__PURE__*/React.createElement(
|
|
28637
|
+
})), /*#__PURE__*/React.createElement(TextLabel, {
|
|
28594
28638
|
x: x3,
|
|
28595
28639
|
y: y3,
|
|
28596
|
-
size: 15,
|
|
28597
|
-
svgTextProps: {
|
|
28598
|
-
filter: "url(#background)",
|
|
28599
|
-
fontWeight: "bold"
|
|
28600
|
-
}
|
|
28601
|
-
// Shift position if text is too close to movable point
|
|
28602
|
-
,
|
|
28603
28640
|
attach: y3 - centerY > 0 ? "s" : "n",
|
|
28604
28641
|
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
28605
28642
|
}, angleLabel, "\xB0"));
|
|
28606
28643
|
};
|
|
28607
|
-
|
|
28644
|
+
|
|
28645
|
+
/**
|
|
28646
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
28647
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
28648
|
+
*/
|
|
28649
|
+
const RightAngleSquare = ({
|
|
28608
28650
|
start: [x1, y1],
|
|
28609
28651
|
vertex: [x2, y2],
|
|
28610
28652
|
end: [x3, y3]
|
|
28611
|
-
}) => /*#__PURE__*/React.createElement("
|
|
28612
|
-
style: {
|
|
28613
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28614
|
-
}
|
|
28615
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
28653
|
+
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28616
28654
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
28617
28655
|
strokeWidth: 0.02,
|
|
28618
28656
|
fill: "none"
|
|
28619
28657
|
}));
|
|
28620
28658
|
const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
|
|
28634
|
-
|
|
28635
|
-
for (let i = 0; i < polygonPoints.length; i++) {
|
|
28636
|
-
if ([prev, midpointIndex, next].includes(i)) {
|
|
28637
|
-
continue;
|
|
28638
|
-
}
|
|
28639
|
-
const [x, y] = polygonPoints[i];
|
|
28640
|
-
const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
|
|
28641
|
-
if (intersectsPoint) {
|
|
28642
|
-
return false;
|
|
28643
|
-
}
|
|
28644
|
-
}
|
|
28645
|
-
return true;
|
|
28646
|
-
}
|
|
28659
|
+
|
|
28660
|
+
/**
|
|
28661
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
28662
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
28663
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
28664
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
28665
|
+
* will intersect with an even number of lines.
|
|
28666
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
28667
|
+
*/
|
|
28668
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
28669
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
28670
|
+
const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
|
|
28671
|
+
let lineIntersectionCount = 0;
|
|
28672
|
+
polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
28647
28673
|
|
|
28648
28674
|
// If the number of intersections is even, the angle is inside the polygon
|
|
28649
|
-
return isEven(
|
|
28675
|
+
return !isEven(lineIntersectionCount);
|
|
28650
28676
|
};
|
|
28651
|
-
const getLines = points => points.map((point, i) => {
|
|
28652
|
-
const next = points[(i + 1) % points.length];
|
|
28653
|
-
return [point, next];
|
|
28654
|
-
});
|
|
28655
28677
|
|
|
28656
28678
|
// https://stackoverflow.com/a/24392281/7347484
|
|
28657
28679
|
// The "intersects" function in geometry doesn't seem to work for this use case
|
|
@@ -28678,6 +28700,7 @@ const PolygonGraph = props => {
|
|
|
28678
28700
|
coords,
|
|
28679
28701
|
type,
|
|
28680
28702
|
showAngles,
|
|
28703
|
+
showSides,
|
|
28681
28704
|
range
|
|
28682
28705
|
} = props.graphState;
|
|
28683
28706
|
const points = coords != null ? coords : [[0, 0]];
|
|
@@ -28696,6 +28719,7 @@ const PolygonGraph = props => {
|
|
|
28696
28719
|
constrain: p => p
|
|
28697
28720
|
});
|
|
28698
28721
|
const active = hovered || focused || dragging;
|
|
28722
|
+
const lines = getLines(points);
|
|
28699
28723
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
|
|
28700
28724
|
points: [...points],
|
|
28701
28725
|
color: "var(--movable-line-stroke-color)",
|
|
@@ -28714,9 +28738,17 @@ const PolygonGraph = props => {
|
|
|
28714
28738
|
endPoints: [pt1, pt2],
|
|
28715
28739
|
active: active,
|
|
28716
28740
|
range: range,
|
|
28717
|
-
|
|
28741
|
+
polygonLines: lines,
|
|
28718
28742
|
showAngles: !!showAngles
|
|
28719
28743
|
});
|
|
28744
|
+
}), showSides && lines.map(([start, end], i) => {
|
|
28745
|
+
const [x, y] = vec.midpoint(start, end);
|
|
28746
|
+
const length = parseFloat(vec.dist(start, end).toFixed(1));
|
|
28747
|
+
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
28748
|
+
key: "side-" + i,
|
|
28749
|
+
x: x,
|
|
28750
|
+
y: y
|
|
28751
|
+
}, !Number.isInteger(length) && "≈ ", length);
|
|
28720
28752
|
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
28721
28753
|
points: [...points],
|
|
28722
28754
|
color: "transparent",
|
|
@@ -29721,30 +29753,39 @@ function initializeGraphState(params) {
|
|
|
29721
29753
|
// TS v4 doesn't narrow return types, while v5 does.
|
|
29722
29754
|
// Instead of updating to v5, using generic type to relate input and output types.
|
|
29723
29755
|
function getGradableGraph(state, initialGraph) {
|
|
29724
|
-
var _state$coords;
|
|
29725
29756
|
if (!state.hasBeenInteractedWith) {
|
|
29726
|
-
return
|
|
29757
|
+
return {
|
|
29758
|
+
type: initialGraph.type
|
|
29759
|
+
};
|
|
29727
29760
|
}
|
|
29728
|
-
|
|
29729
|
-
|
|
29730
|
-
|
|
29731
|
-
|
|
29732
|
-
|
|
29733
|
-
|
|
29734
|
-
|
|
29735
|
-
|
|
29736
|
-
|
|
29737
|
-
|
|
29738
|
-
|
|
29739
|
-
|
|
29740
|
-
|
|
29741
|
-
|
|
29742
|
-
|
|
29743
|
-
|
|
29744
|
-
|
|
29745
|
-
|
|
29746
|
-
|
|
29761
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
29762
|
+
return _extends({}, initialGraph, {
|
|
29763
|
+
coords: state.coords
|
|
29764
|
+
});
|
|
29765
|
+
}
|
|
29766
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
29767
|
+
return _extends({}, initialGraph, {
|
|
29768
|
+
coords: state.coords
|
|
29769
|
+
});
|
|
29770
|
+
}
|
|
29771
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
29772
|
+
var _state$coords;
|
|
29773
|
+
return _extends({}, initialGraph, {
|
|
29774
|
+
coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
|
|
29775
|
+
});
|
|
29776
|
+
}
|
|
29777
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
29778
|
+
var _state$coords2;
|
|
29779
|
+
return _extends({}, initialGraph, {
|
|
29780
|
+
coords: (_state$coords2 = state.coords) == null ? void 0 : _state$coords2[0]
|
|
29781
|
+
});
|
|
29782
|
+
}
|
|
29783
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
29784
|
+
return _extends({}, initialGraph, {
|
|
29785
|
+
coords: state.coords
|
|
29786
|
+
});
|
|
29747
29787
|
}
|
|
29788
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29748
29789
|
}
|
|
29749
29790
|
const getDefaultSegments = ({
|
|
29750
29791
|
graph,
|
|
@@ -29882,7 +29923,7 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
29882
29923
|
zoom: false,
|
|
29883
29924
|
width: width,
|
|
29884
29925
|
height: height
|
|
29885
|
-
}, /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
29926
|
+
}, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
29886
29927
|
lockedFigures: props.lockedFigures
|
|
29887
29928
|
}), renderGraph({
|
|
29888
29929
|
state,
|
|
@@ -31536,18 +31577,18 @@ class InteractiveGraph extends React.Component {
|
|
|
31536
31577
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
31537
31578
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
31538
31579
|
}
|
|
31539
|
-
static validate(
|
|
31540
|
-
// TODO(kevinb): rename state to userInput
|
|
31541
|
-
// state === userInput
|
|
31542
|
-
state, rubric, component) {
|
|
31580
|
+
static validate(userInput, rubric, component) {
|
|
31543
31581
|
// When nothing has moved, there will neither be coords nor the
|
|
31544
31582
|
// circle's center/radius fields. When those fields are absent, skip
|
|
31545
31583
|
// all these checks; just go mark the answer as empty.
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31584
|
+
const hasValue = Boolean(
|
|
31585
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
31586
|
+
userInput.coords ||
|
|
31587
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
31588
|
+
userInput.center && userInput.radius);
|
|
31589
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
31590
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
31591
|
+
const guess = userInput.coords;
|
|
31551
31592
|
const correct = rubric.correct.coords;
|
|
31552
31593
|
|
|
31553
31594
|
// If both of the guess points are on the correct line, it's
|
|
@@ -31564,8 +31605,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31564
31605
|
message: null
|
|
31565
31606
|
};
|
|
31566
31607
|
}
|
|
31567
|
-
} else if (
|
|
31568
|
-
const guess =
|
|
31608
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
31609
|
+
const guess = userInput.coords;
|
|
31569
31610
|
const correct = rubric.correct.coords;
|
|
31570
31611
|
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
31571
31612
|
return {
|
|
@@ -31575,9 +31616,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31575
31616
|
message: null
|
|
31576
31617
|
};
|
|
31577
31618
|
}
|
|
31578
|
-
} else if (
|
|
31619
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
31579
31620
|
// If the parabola coefficients match, it's correct.
|
|
31580
|
-
const guessCoeffs = this.getQuadraticCoefficients(
|
|
31621
|
+
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
31581
31622
|
const correctCoeffs = this.getQuadraticCoefficients(
|
|
31582
31623
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31583
31624
|
rubric.correct.coords);
|
|
@@ -31589,8 +31630,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31589
31630
|
message: null
|
|
31590
31631
|
};
|
|
31591
31632
|
}
|
|
31592
|
-
} else if (
|
|
31593
|
-
const guessCoeffs = this.getSinusoidCoefficients(
|
|
31633
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
31634
|
+
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
31594
31635
|
const correctCoeffs = this.getSinusoidCoefficients(
|
|
31595
31636
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31596
31637
|
rubric.correct.coords);
|
|
@@ -31605,8 +31646,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31605
31646
|
message: null
|
|
31606
31647
|
};
|
|
31607
31648
|
}
|
|
31608
|
-
} else if (
|
|
31609
|
-
if (deepEq$1(
|
|
31649
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
31650
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
31610
31651
|
return {
|
|
31611
31652
|
type: "points",
|
|
31612
31653
|
earned: 1,
|
|
@@ -31614,10 +31655,10 @@ class InteractiveGraph extends React.Component {
|
|
|
31614
31655
|
message: null
|
|
31615
31656
|
};
|
|
31616
31657
|
}
|
|
31617
|
-
} else if (
|
|
31618
|
-
var
|
|
31658
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
31659
|
+
var _userInput$coords;
|
|
31619
31660
|
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
31620
|
-
const guess = (
|
|
31661
|
+
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
31621
31662
|
correct = correct.slice();
|
|
31622
31663
|
// Everything's already rounded so we shouldn't need to do an
|
|
31623
31664
|
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
@@ -31634,9 +31675,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31634
31675
|
message: null
|
|
31635
31676
|
};
|
|
31636
31677
|
}
|
|
31637
|
-
} else if (
|
|
31638
|
-
var
|
|
31639
|
-
const guess = (
|
|
31678
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
31679
|
+
var _userInput$coords2, _rubric$correct$coord;
|
|
31680
|
+
const guess = (_userInput$coords2 = userInput.coords) == null ? void 0 : _userInput$coords2.slice();
|
|
31640
31681
|
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
31641
31682
|
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
31642
31683
|
let match;
|
|
@@ -31660,9 +31701,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31660
31701
|
message: null
|
|
31661
31702
|
};
|
|
31662
31703
|
}
|
|
31663
|
-
} else if (
|
|
31704
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
31664
31705
|
var _rubric$correct$coord2;
|
|
31665
|
-
let guess =
|
|
31706
|
+
let guess = userInput.coords.slice();
|
|
31666
31707
|
let correct = (_rubric$correct$coord2 = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord2.slice();
|
|
31667
31708
|
guess = _.invoke(guess, "sort").sort();
|
|
31668
31709
|
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
@@ -31675,8 +31716,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31675
31716
|
message: null
|
|
31676
31717
|
};
|
|
31677
31718
|
}
|
|
31678
|
-
} else if (
|
|
31679
|
-
const guess =
|
|
31719
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
31720
|
+
const guess = userInput.coords;
|
|
31680
31721
|
const correct = rubric.correct.coords;
|
|
31681
31722
|
if (
|
|
31682
31723
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
@@ -31690,8 +31731,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31690
31731
|
message: null
|
|
31691
31732
|
};
|
|
31692
31733
|
}
|
|
31693
|
-
} else if (
|
|
31694
|
-
const guess =
|
|
31734
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
31735
|
+
const guess = userInput.coords;
|
|
31695
31736
|
const correct = rubric.correct.coords;
|
|
31696
31737
|
let match;
|
|
31697
31738
|
if (rubric.correct.match === "congruent") {
|
|
@@ -31730,7 +31771,7 @@ class InteractiveGraph extends React.Component {
|
|
|
31730
31771
|
|
|
31731
31772
|
// The input wasn't correct, so check if it's a blank input or if it's
|
|
31732
31773
|
// actually just wrong
|
|
31733
|
-
if (!hasValue || _.isEqual(
|
|
31774
|
+
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
31734
31775
|
// We're where we started.
|
|
31735
31776
|
return {
|
|
31736
31777
|
type: "invalid",
|
|
@@ -41931,7 +41972,7 @@ const init = function init(options) {
|
|
|
41931
41972
|
|
|
41932
41973
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41933
41974
|
const libName = "@khanacademy/perseus";
|
|
41934
|
-
const libVersion = "
|
|
41975
|
+
const libVersion = "21.0.0";
|
|
41935
41976
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41936
41977
|
|
|
41937
41978
|
const apiVersion = {
|
|
@@ -44183,6 +44224,140 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44183
44224
|
replaceDeprecatedWidgets();
|
|
44184
44225
|
};
|
|
44185
44226
|
|
|
44227
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44228
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44229
|
+
|
|
44230
|
+
/**
|
|
44231
|
+
* Regex for widget placeholders in a string.
|
|
44232
|
+
*
|
|
44233
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
44234
|
+
* Second capture group is the widget type (ex. "radio)
|
|
44235
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44236
|
+
*/
|
|
44237
|
+
function getWidgetRegex() {
|
|
44238
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
44239
|
+
}
|
|
44240
|
+
|
|
44241
|
+
/**
|
|
44242
|
+
* Add a widget placeholder using the widget ID.
|
|
44243
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44244
|
+
*
|
|
44245
|
+
* @param {string} id
|
|
44246
|
+
* @returns {string}
|
|
44247
|
+
*/
|
|
44248
|
+
function addWidget(id) {
|
|
44249
|
+
return `[[☃ ${id}]]`;
|
|
44250
|
+
}
|
|
44251
|
+
|
|
44252
|
+
/**
|
|
44253
|
+
* Get a widget type by a widget's ID
|
|
44254
|
+
* (please don't derive type from ID)
|
|
44255
|
+
*
|
|
44256
|
+
* @param {string} widgetId the ID of the widget
|
|
44257
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44258
|
+
* @returns {string} the widget type (ie "radio")
|
|
44259
|
+
*/
|
|
44260
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44261
|
+
var _widget$type;
|
|
44262
|
+
const widget = widgetMap[WidgetId];
|
|
44263
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
44264
|
+
}
|
|
44265
|
+
|
|
44266
|
+
/**
|
|
44267
|
+
* Does the content have a specific type of widget?
|
|
44268
|
+
*
|
|
44269
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44270
|
+
* @param {string} content the string to search through
|
|
44271
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44272
|
+
* @returns {boolean} if the content includes the widget type
|
|
44273
|
+
*/
|
|
44274
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44275
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44276
|
+
}
|
|
44277
|
+
|
|
44278
|
+
/**
|
|
44279
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44280
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44281
|
+
*
|
|
44282
|
+
* Content should contain Perseus widget placeholders,
|
|
44283
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44284
|
+
*
|
|
44285
|
+
* @param {string} content
|
|
44286
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
44287
|
+
*/
|
|
44288
|
+
function getWidgetIdsFromContent(content) {
|
|
44289
|
+
const widgets = [];
|
|
44290
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44291
|
+
let match = localWidgetRegex.exec(content);
|
|
44292
|
+
while (match !== null) {
|
|
44293
|
+
widgets.push(match[1]);
|
|
44294
|
+
match = localWidgetRegex.exec(content);
|
|
44295
|
+
}
|
|
44296
|
+
return widgets;
|
|
44297
|
+
}
|
|
44298
|
+
|
|
44299
|
+
/**
|
|
44300
|
+
* Get a list of widget IDs from content,
|
|
44301
|
+
* but only for specific widget types
|
|
44302
|
+
*
|
|
44303
|
+
* @param {string} type the type of widget (ie "radio")
|
|
44304
|
+
* @param {string} content the string to parse
|
|
44305
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44306
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
44307
|
+
*/
|
|
44308
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
44309
|
+
const rv = [];
|
|
44310
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
44311
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
44312
|
+
const widget = widgetMap[widgetId];
|
|
44313
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
44314
|
+
rv.push(widgetId);
|
|
44315
|
+
}
|
|
44316
|
+
});
|
|
44317
|
+
return rv;
|
|
44318
|
+
}
|
|
44319
|
+
|
|
44320
|
+
/**
|
|
44321
|
+
* Pull the widget map out of ItemData
|
|
44322
|
+
*
|
|
44323
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44324
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44325
|
+
*/
|
|
44326
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44327
|
+
return itemData.question.widgets;
|
|
44328
|
+
}
|
|
44329
|
+
|
|
44330
|
+
/**
|
|
44331
|
+
* Get the widget information from a WidgetMap
|
|
44332
|
+
*
|
|
44333
|
+
* @param {string} widgetId the ID of the widget
|
|
44334
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44335
|
+
*/
|
|
44336
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44337
|
+
var _widgetMap$widgetId;
|
|
44338
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
44339
|
+
}
|
|
44340
|
+
|
|
44341
|
+
/**
|
|
44342
|
+
* Get select widgets from a widget map.
|
|
44343
|
+
* Useful for multi-items that needs to split
|
|
44344
|
+
* one PerseusItem into several
|
|
44345
|
+
*
|
|
44346
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44347
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44348
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44349
|
+
*/
|
|
44350
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44351
|
+
const widgets = {};
|
|
44352
|
+
widgetIds.forEach(widgetId => {
|
|
44353
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44354
|
+
if (widget) {
|
|
44355
|
+
widgets[widgetId] = widget;
|
|
44356
|
+
}
|
|
44357
|
+
});
|
|
44358
|
+
return widgets;
|
|
44359
|
+
}
|
|
44360
|
+
|
|
44186
44361
|
/**
|
|
44187
44362
|
* This function extracts the answers from the widgets.
|
|
44188
44363
|
*
|
|
@@ -44593,18 +44768,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
|
|
|
44593
44768
|
|
|
44594
44769
|
/* Widgets that are supported for automatic scoring */
|
|
44595
44770
|
const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
|
|
44596
|
-
const getWidgetTypeFromWidgetKey = widgetKey => {
|
|
44597
|
-
return widgetKey.split(" ")[0];
|
|
44598
|
-
};
|
|
44599
44771
|
|
|
44600
44772
|
/* Verify if the perseus item has supported widgets for automatic scoring */
|
|
44601
|
-
const isWrongAnswerSupported =
|
|
44602
|
-
return
|
|
44773
|
+
const isWrongAnswerSupported = (widgetIds, widgetMap) => {
|
|
44774
|
+
return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
|
|
44603
44775
|
};
|
|
44604
44776
|
|
|
44605
|
-
/* Verify if the widget
|
|
44606
|
-
const shouldHaveIndividualAnswer =
|
|
44607
|
-
return INDIVIDUAL_ANSWER_WIDGETS.includes(
|
|
44777
|
+
/* Verify if the widget ID has an individual answer for the coach report view */
|
|
44778
|
+
const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
|
|
44779
|
+
return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
|
|
44608
44780
|
};
|
|
44609
44781
|
|
|
44610
44782
|
/* Returns the answer userInput submission for currently supported widgets */
|
|
@@ -44623,29 +44795,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
|
|
|
44623
44795
|
return userInput;
|
|
44624
44796
|
};
|
|
44625
44797
|
|
|
44626
|
-
/* Returns the correct answer for a given widget
|
|
44798
|
+
/* Returns the correct answer for a given widget ID and Perseus Item */
|
|
44627
44799
|
// TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
|
|
44628
44800
|
// TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
|
|
44629
|
-
const
|
|
44630
|
-
const rubric = itemData.question.widgets[
|
|
44631
|
-
const
|
|
44801
|
+
const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
|
|
44802
|
+
const rubric = itemData.question.widgets[widgetId].options;
|
|
44803
|
+
const widgetMap = getWidgetsMapFromItemData(itemData);
|
|
44804
|
+
const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
|
|
44632
44805
|
const widget = getWidget(widgetType);
|
|
44633
44806
|
|
|
44634
44807
|
// @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
|
|
44635
44808
|
return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
|
|
44636
44809
|
};
|
|
44637
44810
|
|
|
44638
|
-
/* Verify if the widget
|
|
44639
|
-
const
|
|
44640
|
-
return perseusItem.question.content.indexOf(
|
|
44811
|
+
/* Verify if the widget ID exists in the content string of the Perseus Item */
|
|
44812
|
+
const isWidgetIdInContent = (perseusItem, widgetId) => {
|
|
44813
|
+
return perseusItem.question.content.indexOf(widgetId) !== -1;
|
|
44641
44814
|
};
|
|
44642
44815
|
|
|
44643
|
-
/* Return an array of all the widget
|
|
44644
|
-
const
|
|
44816
|
+
/* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
|
|
44817
|
+
const getValidWidgetIds = perseusItem => {
|
|
44645
44818
|
const {
|
|
44646
44819
|
widgets
|
|
44647
44820
|
} = perseusItem.question;
|
|
44648
|
-
return keys(widgets).filter(
|
|
44821
|
+
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44649
44822
|
};
|
|
44650
44823
|
|
|
44651
44824
|
/**
|
|
@@ -44693,12 +44866,113 @@ const genericPerseusItemData = {
|
|
|
44693
44866
|
* uses a basic Perseus object and updates it with custom values as needed.
|
|
44694
44867
|
*
|
|
44695
44868
|
* @param {Partial<PerseusItem>} customFields
|
|
44696
|
-
* @returns PerseusItem
|
|
44869
|
+
* @returns {PerseusItem}
|
|
44697
44870
|
*/
|
|
44698
44871
|
function generateTestPerseusItem(customFields = {}) {
|
|
44699
44872
|
return _extends({}, genericPerseusItemData, customFields);
|
|
44700
44873
|
}
|
|
44701
44874
|
|
|
44875
|
+
/**
|
|
44876
|
+
* Creates an object with the bare amount
|
|
44877
|
+
* of data to be a properly typed RadioWidget
|
|
44878
|
+
*
|
|
44879
|
+
* @returns {RadioWidget} skeleton RadioWidget for testing
|
|
44880
|
+
*/
|
|
44881
|
+
function generateTestRadioWidget() {
|
|
44882
|
+
return {
|
|
44883
|
+
type: "radio",
|
|
44884
|
+
options: {
|
|
44885
|
+
choices: []
|
|
44886
|
+
}
|
|
44887
|
+
};
|
|
44888
|
+
}
|
|
44889
|
+
|
|
44890
|
+
/**
|
|
44891
|
+
* Creates an object with the bare amount
|
|
44892
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
44893
|
+
*
|
|
44894
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
44895
|
+
*/
|
|
44896
|
+
function generateTestInteractiveGraphWidget() {
|
|
44897
|
+
return {
|
|
44898
|
+
type: "interactive-graph",
|
|
44899
|
+
options: {
|
|
44900
|
+
step: [1, 1],
|
|
44901
|
+
gridStep: [1, 1],
|
|
44902
|
+
snapStep: [1, 1],
|
|
44903
|
+
markings: "graph",
|
|
44904
|
+
labels: ["x", "y"],
|
|
44905
|
+
showProtractor: false,
|
|
44906
|
+
showRuler: false,
|
|
44907
|
+
rulerLabel: "mm",
|
|
44908
|
+
rulerTicks: 2,
|
|
44909
|
+
range: [[-10, 10], [-10, 10]],
|
|
44910
|
+
graph: {
|
|
44911
|
+
type: "angle"
|
|
44912
|
+
},
|
|
44913
|
+
correct: {
|
|
44914
|
+
type: "angle"
|
|
44915
|
+
}
|
|
44916
|
+
}
|
|
44917
|
+
};
|
|
44918
|
+
}
|
|
44919
|
+
|
|
44920
|
+
/**
|
|
44921
|
+
* Creates an object with the bare amount
|
|
44922
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
44923
|
+
*
|
|
44924
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
44925
|
+
*/
|
|
44926
|
+
function generateTestCategorizerWidget() {
|
|
44927
|
+
return {
|
|
44928
|
+
type: "categorizer",
|
|
44929
|
+
options: {
|
|
44930
|
+
items: [],
|
|
44931
|
+
categories: [],
|
|
44932
|
+
randomizeItems: false,
|
|
44933
|
+
static: false,
|
|
44934
|
+
values: []
|
|
44935
|
+
}
|
|
44936
|
+
};
|
|
44937
|
+
}
|
|
44938
|
+
|
|
44939
|
+
/**
|
|
44940
|
+
* Creates an object with the bare amount
|
|
44941
|
+
* of data to be a properly typed ExpressionWidget
|
|
44942
|
+
*
|
|
44943
|
+
* @returns {ExpressionWidget} skeleton ExpressionWidget for testing
|
|
44944
|
+
*/
|
|
44945
|
+
function generateTestExpressionWidget() {
|
|
44946
|
+
return {
|
|
44947
|
+
type: "expression",
|
|
44948
|
+
options: {
|
|
44949
|
+
answerForms: [],
|
|
44950
|
+
buttonSets: ["basic"],
|
|
44951
|
+
functions: [],
|
|
44952
|
+
times: false
|
|
44953
|
+
}
|
|
44954
|
+
};
|
|
44955
|
+
}
|
|
44956
|
+
|
|
44957
|
+
/**
|
|
44958
|
+
* Creates an object with the bare amount
|
|
44959
|
+
* of data to be a properly typed NumericInputWidget
|
|
44960
|
+
*
|
|
44961
|
+
* @returns {NumericInputWidget} skeleton NumericInputWidget for testing
|
|
44962
|
+
*/
|
|
44963
|
+
function generateTestNumericInputWidget() {
|
|
44964
|
+
return {
|
|
44965
|
+
type: "numeric-input",
|
|
44966
|
+
options: {
|
|
44967
|
+
answers: [],
|
|
44968
|
+
labelText: "",
|
|
44969
|
+
size: "normal",
|
|
44970
|
+
coefficient: false,
|
|
44971
|
+
static: false
|
|
44972
|
+
}
|
|
44973
|
+
};
|
|
44974
|
+
}
|
|
44975
|
+
|
|
44702
44976
|
/**
|
|
44703
44977
|
* Some helpers for CEDAR logging. CEDAR convention dictates
|
|
44704
44978
|
* that enum values should be upper case. Ex: for the
|
|
@@ -44753,162 +45027,6 @@ function convertWidgetNameToEnum(name) {
|
|
|
44753
45027
|
}
|
|
44754
45028
|
return widgetEnum;
|
|
44755
45029
|
}
|
|
44756
|
-
function getWidgetTypeFromWidgetId(widgetId) {
|
|
44757
|
-
const [widgetType, widgetNumber] = widgetId.split(" ");
|
|
44758
|
-
if (!widgetNumber) {
|
|
44759
|
-
throw new Error(`Incorrect format for widget ID: ${widgetId}`);
|
|
44760
|
-
}
|
|
44761
|
-
if (!widgetNameToEnum[widgetType]) {
|
|
44762
|
-
throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
|
|
44763
|
-
}
|
|
44764
|
-
return widgetType;
|
|
44765
|
-
}
|
|
44766
|
-
|
|
44767
|
-
let WidgetType = /*#__PURE__*/function (WidgetType) {
|
|
44768
|
-
WidgetType["Categorizer"] = "categorizer";
|
|
44769
|
-
WidgetType["CSProgram"] = "cs-program";
|
|
44770
|
-
WidgetType["Definition"] = "definition";
|
|
44771
|
-
WidgetType["Dropdown"] = "dropdown";
|
|
44772
|
-
WidgetType["Explanation"] = "explanation";
|
|
44773
|
-
WidgetType["Expression"] = "expression";
|
|
44774
|
-
WidgetType["GradedGroup"] = "graded-group";
|
|
44775
|
-
WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
|
|
44776
|
-
WidgetType["GradedGroupSet"] = "graded-group-set";
|
|
44777
|
-
WidgetType["Grapher"] = "grapher";
|
|
44778
|
-
WidgetType["Group"] = "group";
|
|
44779
|
-
WidgetType["Iframe"] = "iframe";
|
|
44780
|
-
WidgetType["Image"] = "image";
|
|
44781
|
-
WidgetType["InputNumber"] = "input-number";
|
|
44782
|
-
WidgetType["Interaction"] = "interaction";
|
|
44783
|
-
WidgetType["InteractiveGraph"] = "interactive-graph";
|
|
44784
|
-
WidgetType["InteractiveNumberLine"] = "interactive-number-line";
|
|
44785
|
-
WidgetType["LabelImage"] = "label-image";
|
|
44786
|
-
WidgetType["Matcher"] = "matcher";
|
|
44787
|
-
WidgetType["Matrix"] = "matrix";
|
|
44788
|
-
WidgetType["Measurer"] = "measurer";
|
|
44789
|
-
WidgetType["Molecule"] = "molecule";
|
|
44790
|
-
WidgetType["NumberLine"] = "number-line";
|
|
44791
|
-
WidgetType["NumericInput"] = "numeric-input";
|
|
44792
|
-
WidgetType["Orderer"] = "orderer";
|
|
44793
|
-
WidgetType["Passage"] = "passage";
|
|
44794
|
-
WidgetType["PassageRef"] = "passage-ref";
|
|
44795
|
-
WidgetType["PassageRefTarget"] = "passage-ref-target";
|
|
44796
|
-
WidgetType["Plotter"] = "plotter";
|
|
44797
|
-
WidgetType["Protractor"] = "protractor";
|
|
44798
|
-
WidgetType["PythonProgram"] = "python-program";
|
|
44799
|
-
WidgetType["Radio"] = "radio";
|
|
44800
|
-
WidgetType["Sorter"] = "sorter";
|
|
44801
|
-
WidgetType["Table"] = "table";
|
|
44802
|
-
WidgetType["Unit"] = "unit";
|
|
44803
|
-
WidgetType["Video"] = "video";
|
|
44804
|
-
WidgetType["VideoTranscriptLink"] = "video-transcript-link";
|
|
44805
|
-
return WidgetType;
|
|
44806
|
-
}({});
|
|
44807
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44808
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44809
|
-
|
|
44810
|
-
/**
|
|
44811
|
-
* Regex for widget placeholders in a string.
|
|
44812
|
-
*
|
|
44813
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
44814
|
-
* Second capture group is the widget type (ex. "radio)
|
|
44815
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44816
|
-
*/
|
|
44817
|
-
function getWidgetRegex() {
|
|
44818
|
-
return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
|
|
44819
|
-
}
|
|
44820
|
-
|
|
44821
|
-
/**
|
|
44822
|
-
* Add a widget placeholder using the widget ID.
|
|
44823
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44824
|
-
*
|
|
44825
|
-
* @param {string} id
|
|
44826
|
-
* @returns {string}
|
|
44827
|
-
*/
|
|
44828
|
-
function addWidget(id) {
|
|
44829
|
-
return `[[☃ ${id}]]`;
|
|
44830
|
-
}
|
|
44831
|
-
|
|
44832
|
-
/**
|
|
44833
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44834
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44835
|
-
*
|
|
44836
|
-
* Content should contain Perseus widget placeholders,
|
|
44837
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44838
|
-
*
|
|
44839
|
-
* @param {string} content
|
|
44840
|
-
* @returns {Array<WidgetId>} widgets
|
|
44841
|
-
*/
|
|
44842
|
-
function getAllWidgetIds(content) {
|
|
44843
|
-
const widgets = [];
|
|
44844
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44845
|
-
let match = localWidgetRegex.exec(content);
|
|
44846
|
-
while (match !== null) {
|
|
44847
|
-
widgets.push(match[1]);
|
|
44848
|
-
match = localWidgetRegex.exec(content);
|
|
44849
|
-
}
|
|
44850
|
-
return widgets;
|
|
44851
|
-
}
|
|
44852
|
-
|
|
44853
|
-
/**
|
|
44854
|
-
* Check if a specific widget is an actual widget type using its name.
|
|
44855
|
-
* The widget name should include a widget type, without an instance number,
|
|
44856
|
-
* as a string. ex. 'radio'
|
|
44857
|
-
*
|
|
44858
|
-
* @param {string} widgetName
|
|
44859
|
-
* @returns {boolean}
|
|
44860
|
-
*/
|
|
44861
|
-
function isWidgetType(widgetName) {
|
|
44862
|
-
return Object.values(WidgetType).includes(widgetName);
|
|
44863
|
-
}
|
|
44864
|
-
|
|
44865
|
-
/**
|
|
44866
|
-
* Extract all widget types from a Perseus JSON content string.
|
|
44867
|
-
* This does not include the instance number and prevents duplicates.
|
|
44868
|
-
* example output: ['radio', 'categorizer']
|
|
44869
|
-
*
|
|
44870
|
-
* @param {string} content
|
|
44871
|
-
* @returns {Array<WidgetId>}
|
|
44872
|
-
*/
|
|
44873
|
-
function getAllWidgetTypes(content) {
|
|
44874
|
-
const widgetTypes = [];
|
|
44875
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44876
|
-
let match = localWidgetRegex.exec(content);
|
|
44877
|
-
while (match !== null) {
|
|
44878
|
-
const matchType = match[2];
|
|
44879
|
-
if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
|
|
44880
|
-
widgetTypes.push(matchType);
|
|
44881
|
-
}
|
|
44882
|
-
match = localWidgetRegex.exec(content);
|
|
44883
|
-
}
|
|
44884
|
-
return widgetTypes;
|
|
44885
|
-
}
|
|
44886
|
-
|
|
44887
|
-
/**
|
|
44888
|
-
* Check if a specific widget is a question widget type using its ID.
|
|
44889
|
-
* The widget ID includes the widget type and the instance number.
|
|
44890
|
-
*
|
|
44891
|
-
* @param {WidgetId} widgetId
|
|
44892
|
-
* @returns {boolean}
|
|
44893
|
-
*/
|
|
44894
|
-
function isQuestionWidgetType(widgetId) {
|
|
44895
|
-
return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
|
|
44896
|
-
}
|
|
44897
|
-
|
|
44898
|
-
/**
|
|
44899
|
-
* Extract the widget IDs of all widgets considered question widgets. Widget IDs
|
|
44900
|
-
* include widget type and instance number.
|
|
44901
|
-
* example output: ['radio 1', 'categorizer 1']
|
|
44902
|
-
*
|
|
44903
|
-
* Content should contain Perseus widget placeholders,
|
|
44904
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44905
|
-
*
|
|
44906
|
-
* @param {string} content
|
|
44907
|
-
* @returns Array<WidgetId>
|
|
44908
|
-
*/
|
|
44909
|
-
function getQuestionWidgetIds(content) {
|
|
44910
|
-
return getAllWidgetIds(content).filter(isQuestionWidgetType);
|
|
44911
|
-
}
|
|
44912
45030
|
|
|
44913
45031
|
const EditorJsonify = {
|
|
44914
45032
|
serialize: function () {
|
|
@@ -44918,5 +45036,5 @@ const EditorJsonify = {
|
|
|
44918
45036
|
}
|
|
44919
45037
|
};
|
|
44920
45038
|
|
|
44921
|
-
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated,
|
|
45039
|
+
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
|
|
44922
45040
|
//# sourceMappingURL=index.js.map
|