@khanacademy/perseus 20.8.1 → 21.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/es/index.js +716 -359
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +739 -366
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/util/extract-perseus-data.d.ts +7 -8
- 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 +3 -3
- package/dist/widgets/interactive-graphs/axis-arrows.d.ts +2 -0
- 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/point.d.ts +5 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +2 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +15 -74
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +10 -3
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-reducer.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-state.d.ts +11 -0
- package/dist/widgets/interactive-graphs/types.d.ts +30 -9
- package/dist/widgets/interactive-graphs/utils.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18890,7 +18890,7 @@ const isAccessible = widgetInfo => {
|
|
|
18890
18890
|
}
|
|
18891
18891
|
return !!accessible;
|
|
18892
18892
|
};
|
|
18893
|
-
const getAllWidgetTypes
|
|
18893
|
+
const getAllWidgetTypes = () => {
|
|
18894
18894
|
return ___default["default"].keys(widgets);
|
|
18895
18895
|
};
|
|
18896
18896
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
@@ -19162,7 +19162,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19162
19162
|
getVersionVector: getVersionVector,
|
|
19163
19163
|
getPublicWidgets: getPublicWidgets,
|
|
19164
19164
|
isAccessible: isAccessible,
|
|
19165
|
-
getAllWidgetTypes: getAllWidgetTypes
|
|
19165
|
+
getAllWidgetTypes: getAllWidgetTypes,
|
|
19166
19166
|
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
19167
19167
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
19168
19168
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
@@ -19560,6 +19560,9 @@ class Renderer extends React__namespace.Component {
|
|
|
19560
19560
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19561
19561
|
const newValues = {};
|
|
19562
19562
|
if (!widgetInfo.type) {
|
|
19563
|
+
// TODO: why does widget have no type?
|
|
19564
|
+
// We don't want to derive type from widget ID
|
|
19565
|
+
// see: LEMS-1845
|
|
19563
19566
|
newValues.type = widgetId.split(" ")[0];
|
|
19564
19567
|
}
|
|
19565
19568
|
if (!widgetInfo.alignment) {
|
|
@@ -25023,9 +25026,22 @@ class Explanation extends React__namespace.Component {
|
|
|
25023
25026
|
// Special styling is needed to fit the button in a block of text without throwing off the line spacing.
|
|
25024
25027
|
// While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
|
|
25025
25028
|
const buttonStyleOverrides = {
|
|
25026
|
-
height: "
|
|
25029
|
+
height: "auto",
|
|
25027
25030
|
marginLeft: "-2px",
|
|
25028
|
-
|
|
25031
|
+
// transfer space on the left side to the padding
|
|
25032
|
+
marginRight: "2px",
|
|
25033
|
+
// adds space between the right border and any text that follows
|
|
25034
|
+
paddingLeft: "2px" // adds space between the left border and the button text
|
|
25035
|
+
};
|
|
25036
|
+
|
|
25037
|
+
const labelStyle = {
|
|
25038
|
+
fontSize: "18px",
|
|
25039
|
+
lineHeight: "20px",
|
|
25040
|
+
// The following property adjusts the large space between the button text and the caret icon.
|
|
25041
|
+
// Since we are unable to adjust the styling of the icon (where the extra space exists),
|
|
25042
|
+
// we are adjusting it on the text label by using a negative margin.
|
|
25043
|
+
marginRight: "-6px",
|
|
25044
|
+
"white-space": "normal"
|
|
25029
25045
|
};
|
|
25030
25046
|
const contentStyling = [styles$p.content, this.state.expanded ? styles$p.contentExpanded : styles$p.contentCollapsed, allowTransition && (this.state.expanded ? styles$p.transitionExpanded : styles$p.transitionCollapsed)];
|
|
25031
25047
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
|
|
@@ -25036,8 +25052,9 @@ class Explanation extends React__namespace.Component {
|
|
|
25036
25052
|
"aria-controls": ids.get("content"),
|
|
25037
25053
|
endIcon: caretIcon,
|
|
25038
25054
|
kind: "tertiary",
|
|
25055
|
+
labelStyle: labelStyle,
|
|
25039
25056
|
onClick: this._onClick,
|
|
25040
|
-
size: "
|
|
25057
|
+
size: "small",
|
|
25041
25058
|
style: buttonStyleOverrides
|
|
25042
25059
|
}, promptText), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
25043
25060
|
id: ids.get("content"),
|
|
@@ -28339,6 +28356,14 @@ const moveAll = delta => ({
|
|
|
28339
28356
|
type: MOVE_ALL,
|
|
28340
28357
|
delta
|
|
28341
28358
|
});
|
|
28359
|
+
const MOVE_POINT = "move-point";
|
|
28360
|
+
function movePoint(index, destination) {
|
|
28361
|
+
return {
|
|
28362
|
+
type: MOVE_POINT,
|
|
28363
|
+
index,
|
|
28364
|
+
destination
|
|
28365
|
+
};
|
|
28366
|
+
}
|
|
28342
28367
|
|
|
28343
28368
|
/**
|
|
28344
28369
|
* 44 is touch best practice and AAA compliant for WCAG
|
|
@@ -28416,6 +28441,10 @@ const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
|
|
|
28416
28441
|
return [xMax, yAtXMax];
|
|
28417
28442
|
}
|
|
28418
28443
|
};
|
|
28444
|
+
const getLines = points => points.map((point, i) => {
|
|
28445
|
+
const next = points[(i + 1) % points.length];
|
|
28446
|
+
return [point, next];
|
|
28447
|
+
});
|
|
28419
28448
|
|
|
28420
28449
|
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
28421
28450
|
const MovableLine = props => {
|
|
@@ -28560,7 +28589,9 @@ const SegmentGraph = props => {
|
|
|
28560
28589
|
onMoveLine: delta => {
|
|
28561
28590
|
dispatch(moveLine(i, delta));
|
|
28562
28591
|
},
|
|
28563
|
-
onMovePoint: (endpointIndex, destination) =>
|
|
28592
|
+
onMovePoint: (endpointIndex, destination) => {
|
|
28593
|
+
dispatch(moveControlPoint(endpointIndex, destination, i));
|
|
28594
|
+
},
|
|
28564
28595
|
"data-testid": "segment" + i
|
|
28565
28596
|
})));
|
|
28566
28597
|
};
|
|
@@ -28676,12 +28707,55 @@ const RayGraph = props => {
|
|
|
28676
28707
|
}));
|
|
28677
28708
|
};
|
|
28678
28709
|
|
|
28710
|
+
/**
|
|
28711
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
28712
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
28713
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
28714
|
+
*/
|
|
28715
|
+
const MafsCssTransformWrapper = _ref => {
|
|
28716
|
+
let {
|
|
28717
|
+
children
|
|
28718
|
+
} = _ref;
|
|
28719
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
28720
|
+
style: {
|
|
28721
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28722
|
+
}
|
|
28723
|
+
}, children);
|
|
28724
|
+
};
|
|
28725
|
+
|
|
28726
|
+
const TextLabel = _ref => {
|
|
28727
|
+
let {
|
|
28728
|
+
children,
|
|
28729
|
+
...rest
|
|
28730
|
+
} = _ref;
|
|
28731
|
+
return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
|
|
28732
|
+
size: 16,
|
|
28733
|
+
svgTextProps: {
|
|
28734
|
+
filter: "url(#background)",
|
|
28735
|
+
fontWeight: "bold"
|
|
28736
|
+
}
|
|
28737
|
+
}, rest), children);
|
|
28738
|
+
};
|
|
28739
|
+
const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs", null, /*#__PURE__*/React__default["default"].createElement("filter", {
|
|
28740
|
+
id: "background",
|
|
28741
|
+
x: "-5%",
|
|
28742
|
+
width: "110%",
|
|
28743
|
+
y: "0%",
|
|
28744
|
+
height: "100%"
|
|
28745
|
+
}, /*#__PURE__*/React__default["default"].createElement("feFlood", {
|
|
28746
|
+
floodColor: "#FFF",
|
|
28747
|
+
floodOpacity: "0.64"
|
|
28748
|
+
}), /*#__PURE__*/React__default["default"].createElement("feComposite", {
|
|
28749
|
+
operator: "over",
|
|
28750
|
+
in: "SourceGraphic"
|
|
28751
|
+
})));
|
|
28752
|
+
|
|
28679
28753
|
const Angle = _ref => {
|
|
28680
28754
|
let {
|
|
28681
28755
|
centerPoint,
|
|
28682
28756
|
endPoints,
|
|
28683
28757
|
range,
|
|
28684
|
-
|
|
28758
|
+
polygonLines,
|
|
28685
28759
|
showAngles
|
|
28686
28760
|
} = _ref;
|
|
28687
28761
|
const [centerX, centerY] = centerPoint;
|
|
@@ -28703,7 +28777,7 @@ const Angle = _ref => {
|
|
|
28703
28777
|
// Used to calculate the inside of the angle and to create square
|
|
28704
28778
|
const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
|
|
28705
28779
|
if (!showAngles) {
|
|
28706
|
-
return isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(
|
|
28780
|
+
return isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
28707
28781
|
start: [x1, y1],
|
|
28708
28782
|
vertex: [x2, y2],
|
|
28709
28783
|
end: [x3, y3]
|
|
@@ -28711,13 +28785,13 @@ const Angle = _ref => {
|
|
|
28711
28785
|
}
|
|
28712
28786
|
|
|
28713
28787
|
// Midpoint betwen ends of arc
|
|
28714
|
-
const
|
|
28715
|
-
const largeArcFlag =
|
|
28716
|
-
const sweepFlag =
|
|
28788
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
28789
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
28790
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
28717
28791
|
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
28718
28792
|
let angleInDegrees = angle * (180 / Math.PI);
|
|
28719
28793
|
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
28720
|
-
if (
|
|
28794
|
+
if (isOutside) {
|
|
28721
28795
|
angleInDegrees = 360 - angleInDegrees;
|
|
28722
28796
|
}
|
|
28723
28797
|
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
|
|
@@ -28734,92 +28808,63 @@ const Angle = _ref => {
|
|
|
28734
28808
|
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
28735
28809
|
operator: "over",
|
|
28736
28810
|
in: "SourceGraphic"
|
|
28737
|
-
}))), !
|
|
28811
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
28738
28812
|
start: [x1, y1],
|
|
28739
28813
|
vertex: [x2, y2],
|
|
28740
28814
|
end: [x3, y3]
|
|
28741
|
-
}) : /*#__PURE__*/React__namespace.createElement("
|
|
28742
|
-
style: {
|
|
28743
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28744
|
-
}
|
|
28745
|
-
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
28815
|
+
}) : /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
28746
28816
|
d: arc,
|
|
28747
28817
|
strokeWidth: 0.02,
|
|
28748
28818
|
fill: "none"
|
|
28749
|
-
})), /*#__PURE__*/React__namespace.createElement(
|
|
28819
|
+
})), /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
28750
28820
|
x: x3,
|
|
28751
28821
|
y: y3,
|
|
28752
|
-
size: 15,
|
|
28753
|
-
svgTextProps: {
|
|
28754
|
-
filter: "url(#background)",
|
|
28755
|
-
fontWeight: "bold"
|
|
28756
|
-
}
|
|
28757
|
-
// Shift position if text is too close to movable point
|
|
28758
|
-
,
|
|
28759
28822
|
attach: y3 - centerY > 0 ? "s" : "n",
|
|
28760
28823
|
attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
28761
28824
|
}, angleLabel, "\xB0"));
|
|
28762
28825
|
};
|
|
28763
|
-
|
|
28826
|
+
|
|
28827
|
+
/**
|
|
28828
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
28829
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
28830
|
+
*/
|
|
28831
|
+
const RightAngleSquare = _ref2 => {
|
|
28764
28832
|
let {
|
|
28765
28833
|
start: [x1, y1],
|
|
28766
28834
|
vertex: [x2, y2],
|
|
28767
28835
|
end: [x3, y3]
|
|
28768
28836
|
} = _ref2;
|
|
28769
|
-
return /*#__PURE__*/React__namespace.createElement("
|
|
28770
|
-
style: {
|
|
28771
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28772
|
-
}
|
|
28773
|
-
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
28837
|
+
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
28774
28838
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
28775
28839
|
strokeWidth: 0.02,
|
|
28776
28840
|
fill: "none"
|
|
28777
28841
|
}));
|
|
28778
28842
|
};
|
|
28779
28843
|
const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
// get array sans point and adjacent points
|
|
28795
|
-
const [prev, next] = [(midpointIndex - 1 + polygonPoints.length) % polygonPoints.length, (midpointIndex + 1) % polygonPoints.length];
|
|
28796
|
-
const [[lineAx, lineAy], [lineBx, lineBy]] = [midPoint, rangeIntersectionPoint];
|
|
28797
|
-
for (let i = 0; i < polygonPoints.length; i++) {
|
|
28798
|
-
if ([prev, midpointIndex, next].includes(i)) {
|
|
28799
|
-
continue;
|
|
28800
|
-
}
|
|
28801
|
-
const [x, y] = polygonPoints[i];
|
|
28802
|
-
const intersectsPoint = (y - lineAy) * (lineBx - lineAx) === (lineBy - lineAy) * (x - lineAx);
|
|
28803
|
-
if (intersectsPoint) {
|
|
28804
|
-
return false;
|
|
28805
|
-
}
|
|
28806
|
-
}
|
|
28807
|
-
return true;
|
|
28808
|
-
}
|
|
28844
|
+
|
|
28845
|
+
/**
|
|
28846
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
28847
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
28848
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
28849
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
28850
|
+
* will intersect with an even number of lines.
|
|
28851
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
28852
|
+
*/
|
|
28853
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
28854
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
28855
|
+
const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
|
|
28856
|
+
let lineIntersectionCount = 0;
|
|
28857
|
+
polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
28809
28858
|
|
|
28810
28859
|
// If the number of intersections is even, the angle is inside the polygon
|
|
28811
|
-
return isEven(
|
|
28860
|
+
return !isEven(lineIntersectionCount);
|
|
28812
28861
|
};
|
|
28813
|
-
const getLines = points => points.map((point, i) => {
|
|
28814
|
-
const next = points[(i + 1) % points.length];
|
|
28815
|
-
return [point, next];
|
|
28816
|
-
});
|
|
28817
28862
|
|
|
28818
28863
|
// https://stackoverflow.com/a/24392281/7347484
|
|
28819
28864
|
// The "intersects" function in geometry doesn't seem to work for this use case
|
|
28820
|
-
const linesIntersect = (
|
|
28821
|
-
let [[a, b], [c, d]] =
|
|
28822
|
-
let [[p, q], [r, s]] =
|
|
28865
|
+
const linesIntersect = (_ref3, _ref4) => {
|
|
28866
|
+
let [[a, b], [c, d]] = _ref3;
|
|
28867
|
+
let [[p, q], [r, s]] = _ref4;
|
|
28823
28868
|
const determinant = (c - a) * (s - q) - (r - p) * (d - b);
|
|
28824
28869
|
if (determinant === 0) {
|
|
28825
28870
|
return false;
|
|
@@ -28841,6 +28886,7 @@ const PolygonGraph = props => {
|
|
|
28841
28886
|
coords,
|
|
28842
28887
|
type,
|
|
28843
28888
|
showAngles,
|
|
28889
|
+
showSides,
|
|
28844
28890
|
range
|
|
28845
28891
|
} = props.graphState;
|
|
28846
28892
|
const points = coords ?? [[0, 0]];
|
|
@@ -28859,6 +28905,7 @@ const PolygonGraph = props => {
|
|
|
28859
28905
|
constrain: p => p
|
|
28860
28906
|
});
|
|
28861
28907
|
const active = hovered || focused || dragging;
|
|
28908
|
+
const lines = getLines(points);
|
|
28862
28909
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
28863
28910
|
points: [...points],
|
|
28864
28911
|
color: "var(--movable-line-stroke-color)",
|
|
@@ -28877,9 +28924,18 @@ const PolygonGraph = props => {
|
|
|
28877
28924
|
endPoints: [pt1, pt2],
|
|
28878
28925
|
active: active,
|
|
28879
28926
|
range: range,
|
|
28880
|
-
|
|
28927
|
+
polygonLines: lines,
|
|
28881
28928
|
showAngles: !!showAngles
|
|
28882
28929
|
});
|
|
28930
|
+
}), showSides && lines.map((_ref, i) => {
|
|
28931
|
+
let [start, end] = _ref;
|
|
28932
|
+
const [x, y] = mafs.vec.midpoint(start, end);
|
|
28933
|
+
const length = parseFloat(mafs.vec.dist(start, end).toFixed(1));
|
|
28934
|
+
return /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
28935
|
+
key: "side-" + i,
|
|
28936
|
+
x: x,
|
|
28937
|
+
y: y
|
|
28938
|
+
}, !Number.isInteger(length) && "≈ ", length);
|
|
28883
28939
|
}), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
28884
28940
|
points: [...points],
|
|
28885
28941
|
color: "transparent",
|
|
@@ -28899,11 +28955,73 @@ const PolygonGraph = props => {
|
|
|
28899
28955
|
}), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
28900
28956
|
key: "point-" + i,
|
|
28901
28957
|
point: point,
|
|
28902
|
-
onMove: destination => dispatch(
|
|
28958
|
+
onMove: destination => dispatch(movePoint(i, destination)),
|
|
28903
28959
|
"data-testid": type + i
|
|
28904
28960
|
})));
|
|
28905
28961
|
};
|
|
28906
28962
|
|
|
28963
|
+
function PointGraph(props) {
|
|
28964
|
+
const {
|
|
28965
|
+
dispatch
|
|
28966
|
+
} = props;
|
|
28967
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
28968
|
+
key: i,
|
|
28969
|
+
point: point,
|
|
28970
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
28971
|
+
})));
|
|
28972
|
+
}
|
|
28973
|
+
|
|
28974
|
+
const GraphStateContext = /*#__PURE__*/React.createContext({
|
|
28975
|
+
state: {},
|
|
28976
|
+
dispatch: () => {}
|
|
28977
|
+
});
|
|
28978
|
+
function useGraphState() {
|
|
28979
|
+
return React__default["default"].useContext(GraphStateContext);
|
|
28980
|
+
}
|
|
28981
|
+
|
|
28982
|
+
function Arrow(props) {
|
|
28983
|
+
const {
|
|
28984
|
+
userTransform,
|
|
28985
|
+
viewTransform
|
|
28986
|
+
} = mafs.useTransformContext();
|
|
28987
|
+
const point = [props.x, props.y];
|
|
28988
|
+
const userTransformedPoint = mafs.vec.transform(point, userTransform);
|
|
28989
|
+
const viewTransformedPoint = mafs.vec.transform(userTransformedPoint, viewTransform);
|
|
28990
|
+
return /*#__PURE__*/React__default["default"].createElement("g", {
|
|
28991
|
+
transform: `translate(${viewTransformedPoint[0]} ${viewTransformedPoint[1]}) rotate(${props.rotate}) scale(1.25)`
|
|
28992
|
+
}, /*#__PURE__*/React__default["default"].createElement("g", {
|
|
28993
|
+
transform: "translate(-1)"
|
|
28994
|
+
}, /*#__PURE__*/React__default["default"].createElement("path", {
|
|
28995
|
+
d: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
|
|
28996
|
+
fill: "none"
|
|
28997
|
+
})));
|
|
28998
|
+
}
|
|
28999
|
+
function AxisArrows() {
|
|
29000
|
+
const {
|
|
29001
|
+
state
|
|
29002
|
+
} = useGraphState();
|
|
29003
|
+
const range = state.range;
|
|
29004
|
+
const [xMin, xMax] = range[0];
|
|
29005
|
+
const [yMin, yMax] = range[1];
|
|
29006
|
+
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrow, {
|
|
29007
|
+
x: xMax,
|
|
29008
|
+
y: 0,
|
|
29009
|
+
rotate: 0
|
|
29010
|
+
}), /*#__PURE__*/React__default["default"].createElement(Arrow, {
|
|
29011
|
+
x: 0,
|
|
29012
|
+
y: yMin,
|
|
29013
|
+
rotate: 90
|
|
29014
|
+
}), /*#__PURE__*/React__default["default"].createElement(Arrow, {
|
|
29015
|
+
x: xMin,
|
|
29016
|
+
y: 0,
|
|
29017
|
+
rotate: 180
|
|
29018
|
+
}), /*#__PURE__*/React__default["default"].createElement(Arrow, {
|
|
29019
|
+
x: 0,
|
|
29020
|
+
y: yMax,
|
|
29021
|
+
rotate: 270
|
|
29022
|
+
}));
|
|
29023
|
+
}
|
|
29024
|
+
|
|
28907
29025
|
const renderLineLabel = (n, _ref) => {
|
|
28908
29026
|
let [min, max] = _ref;
|
|
28909
29027
|
return n !== -1 && n !== min && n !== max;
|
|
@@ -28914,10 +29032,10 @@ const axisOptions = (props, index) => ({
|
|
|
28914
29032
|
subdivisions: props.step[index] / props.gridStep[index],
|
|
28915
29033
|
labels: n => renderLineLabel(n, props.range[index]) ? n : ""
|
|
28916
29034
|
});
|
|
28917
|
-
const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
|
|
29035
|
+
const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
|
|
28918
29036
|
xAxis: axisOptions(props, 0),
|
|
28919
29037
|
yAxis: axisOptions(props, 1)
|
|
28920
|
-
});
|
|
29038
|
+
}), props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisArrows, null));
|
|
28921
29039
|
|
|
28922
29040
|
// Displays a stylized open eye: lint warnings are visible
|
|
28923
29041
|
const VisibleIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
|
|
@@ -29615,22 +29733,32 @@ const LegacyGrid = _ref => {
|
|
|
29615
29733
|
return null;
|
|
29616
29734
|
};
|
|
29617
29735
|
|
|
29618
|
-
/** Determine if coords is type CollinearTuple[] */
|
|
29619
|
-
const isCollinearTuples = coords => Array.isArray(coords[0][0]);
|
|
29620
|
-
|
|
29621
|
-
// Generic type makes returned state match input state
|
|
29622
29736
|
function interactiveGraphReducer(state, action) {
|
|
29737
|
+
switch (action.type) {
|
|
29738
|
+
case MOVE_CONTROL_POINT:
|
|
29739
|
+
return doMoveControlPoint(state, action);
|
|
29740
|
+
case MOVE_LINE:
|
|
29741
|
+
return doMoveLine(state, action);
|
|
29742
|
+
case MOVE_ALL:
|
|
29743
|
+
return doMoveAll(state, action);
|
|
29744
|
+
case MOVE_POINT:
|
|
29745
|
+
return doMovePoint(state, action);
|
|
29746
|
+
default:
|
|
29747
|
+
throw new wonderStuffCore.UnreachableCaseError(action);
|
|
29748
|
+
}
|
|
29749
|
+
}
|
|
29750
|
+
function doMoveControlPoint(state, action) {
|
|
29623
29751
|
const {
|
|
29624
29752
|
snapStep,
|
|
29625
29753
|
range
|
|
29626
29754
|
} = state;
|
|
29627
|
-
|
|
29628
|
-
|
|
29629
|
-
|
|
29630
|
-
|
|
29631
|
-
case
|
|
29755
|
+
switch (state.type) {
|
|
29756
|
+
case "segment":
|
|
29757
|
+
case "linear":
|
|
29758
|
+
case "linear-system":
|
|
29759
|
+
case "ray":
|
|
29632
29760
|
{
|
|
29633
|
-
const newCoords =
|
|
29761
|
+
const newCoords = updateAtIndex({
|
|
29634
29762
|
array: state.coords,
|
|
29635
29763
|
index: action.itemIndex,
|
|
29636
29764
|
update: tuple => setAtIndex({
|
|
@@ -29645,23 +29773,9 @@ function interactiveGraphReducer(state, action) {
|
|
|
29645
29773
|
})
|
|
29646
29774
|
})
|
|
29647
29775
|
})
|
|
29648
|
-
}) : setAtIndex({
|
|
29649
|
-
array: state.coords,
|
|
29650
|
-
index: action.pointIndex,
|
|
29651
|
-
newValue: snap({
|
|
29652
|
-
snapStep,
|
|
29653
|
-
point: bound$1({
|
|
29654
|
-
snapStep,
|
|
29655
|
-
range,
|
|
29656
|
-
point: action.destination
|
|
29657
|
-
})
|
|
29658
|
-
})
|
|
29659
29776
|
});
|
|
29660
|
-
|
|
29661
|
-
|
|
29662
|
-
// so this may seem redundant, but it's necessary for type safety.
|
|
29663
|
-
const coordsToCheck = isCollinearTuples(newCoords) && action.itemIndex !== undefined ? newCoords[action.itemIndex] : newCoords;
|
|
29664
|
-
if (!isCollinearTuples(coordsToCheck) && coordsOverlap(coordsToCheck)) {
|
|
29777
|
+
const coordsToCheck = newCoords[action.itemIndex];
|
|
29778
|
+
if (coordsOverlap(coordsToCheck)) {
|
|
29665
29779
|
return state;
|
|
29666
29780
|
}
|
|
29667
29781
|
return {
|
|
@@ -29670,15 +29784,30 @@ function interactiveGraphReducer(state, action) {
|
|
|
29670
29784
|
coords: newCoords
|
|
29671
29785
|
};
|
|
29672
29786
|
}
|
|
29673
|
-
case
|
|
29787
|
+
case "circle":
|
|
29788
|
+
throw new Error("FIXME implement circle reducer");
|
|
29789
|
+
case "point":
|
|
29790
|
+
case "polygon":
|
|
29791
|
+
throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
|
|
29792
|
+
default:
|
|
29793
|
+
throw new wonderStuffCore.UnreachableCaseError(state);
|
|
29794
|
+
}
|
|
29795
|
+
}
|
|
29796
|
+
function doMoveLine(state, action) {
|
|
29797
|
+
const {
|
|
29798
|
+
snapStep,
|
|
29799
|
+
range
|
|
29800
|
+
} = state;
|
|
29801
|
+
switch (state.type) {
|
|
29802
|
+
case "segment":
|
|
29803
|
+
case "linear":
|
|
29804
|
+
case "linear-system":
|
|
29805
|
+
case "ray":
|
|
29674
29806
|
{
|
|
29675
|
-
if (!isCollinearTuples(state.coords)) {
|
|
29676
|
-
throw new Error("Cannot call this action unless coords is array of CollinearTuple.");
|
|
29677
|
-
}
|
|
29678
29807
|
if (action.itemIndex === undefined) {
|
|
29679
29808
|
throw new Error("Please provide index of line to move");
|
|
29680
29809
|
}
|
|
29681
|
-
const currentLine = state.coords
|
|
29810
|
+
const currentLine = state.coords[action.itemIndex];
|
|
29682
29811
|
if (!currentLine) {
|
|
29683
29812
|
throw new Error("No line to move");
|
|
29684
29813
|
}
|
|
@@ -29700,15 +29829,25 @@ function interactiveGraphReducer(state, action) {
|
|
|
29700
29829
|
});
|
|
29701
29830
|
return {
|
|
29702
29831
|
...state,
|
|
29832
|
+
type: state.type,
|
|
29703
29833
|
hasBeenInteractedWith: true,
|
|
29704
29834
|
coords: newCoords
|
|
29705
29835
|
};
|
|
29706
29836
|
}
|
|
29707
|
-
|
|
29837
|
+
default:
|
|
29838
|
+
// The MoveLine action doesn't make sense for other graph types;
|
|
29839
|
+
// ignore it if it somehow happens
|
|
29840
|
+
return state;
|
|
29841
|
+
}
|
|
29842
|
+
}
|
|
29843
|
+
function doMoveAll(state, action) {
|
|
29844
|
+
const {
|
|
29845
|
+
snapStep,
|
|
29846
|
+
range
|
|
29847
|
+
} = state;
|
|
29848
|
+
switch (state.type) {
|
|
29849
|
+
case "polygon":
|
|
29708
29850
|
{
|
|
29709
|
-
if (isCollinearTuples(state.coords)) {
|
|
29710
|
-
throw new Error("Cannot call this action unless coords is array of vectors.");
|
|
29711
|
-
}
|
|
29712
29851
|
const change = getChange(state.coords, action.delta, {
|
|
29713
29852
|
snapStep,
|
|
29714
29853
|
range
|
|
@@ -29724,7 +29863,34 @@ function interactiveGraphReducer(state, action) {
|
|
|
29724
29863
|
};
|
|
29725
29864
|
}
|
|
29726
29865
|
default:
|
|
29727
|
-
|
|
29866
|
+
// MoveAll is not supported for other state types; just ignore it.
|
|
29867
|
+
return state;
|
|
29868
|
+
}
|
|
29869
|
+
}
|
|
29870
|
+
function doMovePoint(state, action) {
|
|
29871
|
+
switch (state.type) {
|
|
29872
|
+
case "point":
|
|
29873
|
+
case "polygon":
|
|
29874
|
+
{
|
|
29875
|
+
return {
|
|
29876
|
+
...state,
|
|
29877
|
+
hasBeenInteractedWith: true,
|
|
29878
|
+
coords: setAtIndex({
|
|
29879
|
+
array: state.coords,
|
|
29880
|
+
index: action.index,
|
|
29881
|
+
newValue: snap({
|
|
29882
|
+
snapStep: state.snapStep,
|
|
29883
|
+
point: bound$1({
|
|
29884
|
+
snapStep: state.snapStep,
|
|
29885
|
+
range: state.range,
|
|
29886
|
+
point: action.destination
|
|
29887
|
+
})
|
|
29888
|
+
})
|
|
29889
|
+
})
|
|
29890
|
+
};
|
|
29891
|
+
}
|
|
29892
|
+
default:
|
|
29893
|
+
throw new Error("The movePoint action is only for point and polygon graphs");
|
|
29728
29894
|
}
|
|
29729
29895
|
}
|
|
29730
29896
|
const getChange = (coords, delta, constraintOpts) => {
|
|
@@ -29811,7 +29977,7 @@ function clamp(value, min, max) {
|
|
|
29811
29977
|
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && kmath.vector.equal(coord, c)));
|
|
29812
29978
|
function updateAtIndex(args) {
|
|
29813
29979
|
const {
|
|
29814
|
-
array
|
|
29980
|
+
array,
|
|
29815
29981
|
index,
|
|
29816
29982
|
update
|
|
29817
29983
|
} = args;
|
|
@@ -29824,13 +29990,12 @@ function updateAtIndex(args) {
|
|
|
29824
29990
|
}
|
|
29825
29991
|
function setAtIndex(args) {
|
|
29826
29992
|
const {
|
|
29827
|
-
array
|
|
29993
|
+
array,
|
|
29828
29994
|
index,
|
|
29829
29995
|
newValue
|
|
29830
29996
|
} = args;
|
|
29831
29997
|
const copy = [...array];
|
|
29832
29998
|
copy[index] = newValue;
|
|
29833
|
-
// restoring readonly to array
|
|
29834
29999
|
return copy;
|
|
29835
30000
|
}
|
|
29836
30001
|
|
|
@@ -29872,18 +30037,31 @@ function initializeGraphState(params) {
|
|
|
29872
30037
|
};
|
|
29873
30038
|
case "polygon":
|
|
29874
30039
|
return {
|
|
30040
|
+
type: "polygon",
|
|
29875
30041
|
hasBeenInteractedWith: false,
|
|
29876
30042
|
range,
|
|
29877
30043
|
snapStep,
|
|
29878
|
-
|
|
30044
|
+
showAngles: Boolean(graph.showAngles),
|
|
30045
|
+
showSides: Boolean(graph.showSides),
|
|
29879
30046
|
coords: getPolygonCoords({
|
|
29880
30047
|
graph,
|
|
29881
30048
|
range,
|
|
29882
30049
|
step
|
|
29883
30050
|
})
|
|
29884
30051
|
};
|
|
29885
|
-
case "angle":
|
|
29886
30052
|
case "point":
|
|
30053
|
+
return {
|
|
30054
|
+
type: graph.type,
|
|
30055
|
+
hasBeenInteractedWith: false,
|
|
30056
|
+
range,
|
|
30057
|
+
snapStep,
|
|
30058
|
+
coords: getDefaultPoints({
|
|
30059
|
+
graph,
|
|
30060
|
+
step,
|
|
30061
|
+
range
|
|
30062
|
+
})
|
|
30063
|
+
};
|
|
30064
|
+
case "angle":
|
|
29887
30065
|
case "circle":
|
|
29888
30066
|
case "sinusoid":
|
|
29889
30067
|
case "quadratic":
|
|
@@ -29892,43 +30070,99 @@ function initializeGraphState(params) {
|
|
|
29892
30070
|
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
29893
30071
|
}
|
|
29894
30072
|
}
|
|
30073
|
+
const getDefaultPoints = _ref => {
|
|
30074
|
+
let {
|
|
30075
|
+
graph,
|
|
30076
|
+
range,
|
|
30077
|
+
step
|
|
30078
|
+
} = _ref;
|
|
30079
|
+
const numPoints = graph.numPoints || 1;
|
|
30080
|
+
let coords = graph.coords?.slice();
|
|
30081
|
+
if (coords) {
|
|
30082
|
+
return coords;
|
|
30083
|
+
}
|
|
30084
|
+
switch (numPoints) {
|
|
30085
|
+
case 1:
|
|
30086
|
+
// Back in the day, one point's coords were in graph.coord
|
|
30087
|
+
coords = [graph.coord || [0, 0]];
|
|
30088
|
+
break;
|
|
30089
|
+
case 2:
|
|
30090
|
+
coords = [[-5, 0], [5, 0]];
|
|
30091
|
+
break;
|
|
30092
|
+
case 3:
|
|
30093
|
+
coords = [[-5, 0], [0, 0], [5, 0]];
|
|
30094
|
+
break;
|
|
30095
|
+
case 4:
|
|
30096
|
+
coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
|
|
30097
|
+
break;
|
|
30098
|
+
case 5:
|
|
30099
|
+
coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
|
|
30100
|
+
break;
|
|
30101
|
+
case 6:
|
|
30102
|
+
coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
|
|
30103
|
+
break;
|
|
30104
|
+
default:
|
|
30105
|
+
coords = [];
|
|
30106
|
+
break;
|
|
30107
|
+
}
|
|
30108
|
+
// Transform coords from their -10 to 10 space to 0 to 1
|
|
30109
|
+
// because of the old graph.coord, and also it's easier.
|
|
30110
|
+
const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
|
|
30111
|
+
return normalizePoints(range, step, newCoords);
|
|
30112
|
+
};
|
|
29895
30113
|
|
|
29896
30114
|
// TS v4 doesn't narrow return types, while v5 does.
|
|
29897
30115
|
// Instead of updating to v5, using generic type to relate input and output types.
|
|
29898
30116
|
function getGradableGraph(state, initialGraph) {
|
|
29899
30117
|
if (!state.hasBeenInteractedWith) {
|
|
29900
|
-
return
|
|
30118
|
+
return {
|
|
30119
|
+
type: initialGraph.type
|
|
30120
|
+
};
|
|
29901
30121
|
}
|
|
29902
|
-
|
|
29903
|
-
|
|
29904
|
-
|
|
29905
|
-
|
|
29906
|
-
|
|
29907
|
-
|
|
29908
|
-
|
|
29909
|
-
|
|
29910
|
-
|
|
29911
|
-
|
|
29912
|
-
|
|
29913
|
-
|
|
29914
|
-
|
|
29915
|
-
|
|
29916
|
-
|
|
29917
|
-
|
|
29918
|
-
|
|
29919
|
-
|
|
29920
|
-
|
|
29921
|
-
|
|
29922
|
-
|
|
29923
|
-
|
|
30122
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
30123
|
+
return {
|
|
30124
|
+
...initialGraph,
|
|
30125
|
+
coords: state.coords
|
|
30126
|
+
};
|
|
30127
|
+
}
|
|
30128
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
30129
|
+
return {
|
|
30130
|
+
...initialGraph,
|
|
30131
|
+
coords: state.coords
|
|
30132
|
+
};
|
|
30133
|
+
}
|
|
30134
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
30135
|
+
return {
|
|
30136
|
+
...initialGraph,
|
|
30137
|
+
coords: state.coords[0]
|
|
30138
|
+
};
|
|
30139
|
+
}
|
|
30140
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
30141
|
+
return {
|
|
30142
|
+
...initialGraph,
|
|
30143
|
+
coords: state.coords[0]
|
|
30144
|
+
};
|
|
30145
|
+
}
|
|
30146
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
30147
|
+
return {
|
|
30148
|
+
...initialGraph,
|
|
30149
|
+
coords: state.coords
|
|
30150
|
+
};
|
|
29924
30151
|
}
|
|
30152
|
+
if (state.type === "point" && initialGraph.type === "point") {
|
|
30153
|
+
return {
|
|
30154
|
+
...initialGraph,
|
|
30155
|
+
coords: state.coords
|
|
30156
|
+
};
|
|
30157
|
+
}
|
|
30158
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29925
30159
|
}
|
|
29926
|
-
const getDefaultSegments =
|
|
30160
|
+
const getDefaultSegments = _ref2 => {
|
|
29927
30161
|
let {
|
|
29928
30162
|
graph,
|
|
29929
30163
|
range,
|
|
29930
30164
|
step
|
|
29931
|
-
} =
|
|
30165
|
+
} = _ref2;
|
|
29932
30166
|
const ys = n => {
|
|
29933
30167
|
switch (n) {
|
|
29934
30168
|
case 2:
|
|
@@ -29954,24 +30188,24 @@ const getDefaultSegments = _ref => {
|
|
|
29954
30188
|
});
|
|
29955
30189
|
};
|
|
29956
30190
|
const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
|
|
29957
|
-
const getLineCoords =
|
|
30191
|
+
const getLineCoords = _ref3 => {
|
|
29958
30192
|
let {
|
|
29959
30193
|
graph,
|
|
29960
30194
|
range,
|
|
29961
30195
|
step
|
|
29962
|
-
} =
|
|
30196
|
+
} = _ref3;
|
|
29963
30197
|
return (
|
|
29964
30198
|
// Return two lines for a linear system, one for a ray or linear
|
|
29965
30199
|
graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
|
|
29966
30200
|
);
|
|
29967
30201
|
};
|
|
29968
|
-
const getPolygonCoords =
|
|
30202
|
+
const getPolygonCoords = _ref4 => {
|
|
29969
30203
|
let {
|
|
29970
30204
|
graph,
|
|
29971
30205
|
range,
|
|
29972
30206
|
step
|
|
29973
|
-
} =
|
|
29974
|
-
let coords = graph.coords;
|
|
30207
|
+
} = _ref4;
|
|
30208
|
+
let coords = graph.coords?.slice();
|
|
29975
30209
|
if (coords) {
|
|
29976
30210
|
return coords;
|
|
29977
30211
|
}
|
|
@@ -30025,6 +30259,13 @@ const renderGraph = props => {
|
|
|
30025
30259
|
graphState: state,
|
|
30026
30260
|
dispatch: dispatch
|
|
30027
30261
|
});
|
|
30262
|
+
case "point":
|
|
30263
|
+
return /*#__PURE__*/React__namespace.createElement(PointGraph, {
|
|
30264
|
+
graphState: state,
|
|
30265
|
+
dispatch: dispatch
|
|
30266
|
+
});
|
|
30267
|
+
case "circle":
|
|
30268
|
+
throw new Error("the circle graph type is not yet implemented");
|
|
30028
30269
|
default:
|
|
30029
30270
|
return new wonderStuffCore.UnreachableCaseError(type);
|
|
30030
30271
|
}
|
|
@@ -30032,10 +30273,24 @@ const renderGraph = props => {
|
|
|
30032
30273
|
const MafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
30033
30274
|
const [width, height] = props.box;
|
|
30034
30275
|
const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
|
|
30276
|
+
const prevState = React.useRef(state);
|
|
30277
|
+
React.useEffect(() => {
|
|
30278
|
+
if (prevState.current !== state) {
|
|
30279
|
+
props.onChange({
|
|
30280
|
+
graph: state
|
|
30281
|
+
});
|
|
30282
|
+
}
|
|
30283
|
+
prevState.current = state;
|
|
30284
|
+
}, [props, state]);
|
|
30035
30285
|
React__namespace.useImperativeHandle(ref, () => ({
|
|
30036
30286
|
getUserInput: () => getGradableGraph(state, props.graph)
|
|
30037
30287
|
}));
|
|
30038
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
30288
|
+
return /*#__PURE__*/React__namespace.createElement(GraphStateContext.Provider, {
|
|
30289
|
+
value: {
|
|
30290
|
+
state,
|
|
30291
|
+
dispatch
|
|
30292
|
+
}
|
|
30293
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
30039
30294
|
style: {
|
|
30040
30295
|
width,
|
|
30041
30296
|
height,
|
|
@@ -30061,12 +30316,12 @@ const MafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
|
30061
30316
|
zoom: false,
|
|
30062
30317
|
width: width,
|
|
30063
30318
|
height: height
|
|
30064
|
-
}, /*#__PURE__*/React__namespace.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
30319
|
+
}, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
30065
30320
|
lockedFigures: props.lockedFigures
|
|
30066
30321
|
}), renderGraph({
|
|
30067
30322
|
state,
|
|
30068
30323
|
dispatch
|
|
30069
|
-
}))));
|
|
30324
|
+
})))));
|
|
30070
30325
|
});
|
|
30071
30326
|
|
|
30072
30327
|
const {
|
|
@@ -31233,6 +31488,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
31233
31488
|
});
|
|
31234
31489
|
$__default["default"](this.angle).on("move", () => {
|
|
31235
31490
|
this.onChange({
|
|
31491
|
+
// @ts-expect-error Type '{ coords: any; type: "angle"; showAngles?: boolean | undefined; allowReflexAngles?: boolean | undefined; angleOffsetDeg?: number | undefined; snapDegrees?: number | undefined; match?: "congruent" | undefined; }' is not assignable to type 'InteractiveGraphState | undefined'.
|
|
31236
31492
|
graph: {
|
|
31237
31493
|
...graph,
|
|
31238
31494
|
coords: this.angle?.getClockwiseCoords()
|
|
@@ -31687,18 +31943,18 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31687
31943
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
31688
31944
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
31689
31945
|
}
|
|
31690
|
-
static validate(
|
|
31691
|
-
// TODO(kevinb): rename state to userInput
|
|
31692
|
-
// state === userInput
|
|
31693
|
-
state, rubric, component) {
|
|
31946
|
+
static validate(userInput, rubric, component) {
|
|
31694
31947
|
// When nothing has moved, there will neither be coords nor the
|
|
31695
31948
|
// circle's center/radius fields. When those fields are absent, skip
|
|
31696
31949
|
// all these checks; just go mark the answer as empty.
|
|
31697
|
-
|
|
31698
|
-
|
|
31699
|
-
|
|
31700
|
-
|
|
31701
|
-
|
|
31950
|
+
const hasValue = Boolean(
|
|
31951
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
31952
|
+
userInput.coords ||
|
|
31953
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
31954
|
+
userInput.center && userInput.radius);
|
|
31955
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
31956
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
31957
|
+
const guess = userInput.coords;
|
|
31702
31958
|
const correct = rubric.correct.coords;
|
|
31703
31959
|
|
|
31704
31960
|
// If both of the guess points are on the correct line, it's
|
|
@@ -31715,8 +31971,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31715
31971
|
message: null
|
|
31716
31972
|
};
|
|
31717
31973
|
}
|
|
31718
|
-
} else if (
|
|
31719
|
-
const guess =
|
|
31974
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
31975
|
+
const guess = userInput.coords;
|
|
31720
31976
|
const correct = rubric.correct.coords;
|
|
31721
31977
|
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])) {
|
|
31722
31978
|
return {
|
|
@@ -31726,9 +31982,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31726
31982
|
message: null
|
|
31727
31983
|
};
|
|
31728
31984
|
}
|
|
31729
|
-
} else if (
|
|
31985
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
31730
31986
|
// If the parabola coefficients match, it's correct.
|
|
31731
|
-
const guessCoeffs = this.getQuadraticCoefficients(
|
|
31987
|
+
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
31732
31988
|
const correctCoeffs = this.getQuadraticCoefficients(
|
|
31733
31989
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31734
31990
|
rubric.correct.coords);
|
|
@@ -31740,8 +31996,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31740
31996
|
message: null
|
|
31741
31997
|
};
|
|
31742
31998
|
}
|
|
31743
|
-
} else if (
|
|
31744
|
-
const guessCoeffs = this.getSinusoidCoefficients(
|
|
31999
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
32000
|
+
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
31745
32001
|
const correctCoeffs = this.getSinusoidCoefficients(
|
|
31746
32002
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31747
32003
|
rubric.correct.coords);
|
|
@@ -31756,8 +32012,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31756
32012
|
message: null
|
|
31757
32013
|
};
|
|
31758
32014
|
}
|
|
31759
|
-
} else if (
|
|
31760
|
-
if (deepEq$1(
|
|
32015
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
32016
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
31761
32017
|
return {
|
|
31762
32018
|
type: "points",
|
|
31763
32019
|
earned: 1,
|
|
@@ -31765,9 +32021,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31765
32021
|
message: null
|
|
31766
32022
|
};
|
|
31767
32023
|
}
|
|
31768
|
-
} else if (
|
|
32024
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
31769
32025
|
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
31770
|
-
const guess =
|
|
32026
|
+
const guess = userInput.coords.slice();
|
|
31771
32027
|
correct = correct.slice();
|
|
31772
32028
|
// Everything's already rounded so we shouldn't need to do an
|
|
31773
32029
|
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
@@ -31784,8 +32040,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31784
32040
|
message: null
|
|
31785
32041
|
};
|
|
31786
32042
|
}
|
|
31787
|
-
} else if (
|
|
31788
|
-
const guess =
|
|
32043
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
32044
|
+
const guess = userInput.coords?.slice();
|
|
31789
32045
|
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
31790
32046
|
const correct = rubric.correct.coords?.slice();
|
|
31791
32047
|
let match;
|
|
@@ -31809,8 +32065,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31809
32065
|
message: null
|
|
31810
32066
|
};
|
|
31811
32067
|
}
|
|
31812
|
-
} else if (
|
|
31813
|
-
let guess =
|
|
32068
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
32069
|
+
let guess = userInput.coords.slice();
|
|
31814
32070
|
let correct = rubric.correct.coords?.slice();
|
|
31815
32071
|
guess = ___default["default"].invoke(guess, "sort").sort();
|
|
31816
32072
|
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
@@ -31823,8 +32079,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31823
32079
|
message: null
|
|
31824
32080
|
};
|
|
31825
32081
|
}
|
|
31826
|
-
} else if (
|
|
31827
|
-
const guess =
|
|
32082
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
32083
|
+
const guess = userInput.coords;
|
|
31828
32084
|
const correct = rubric.correct.coords;
|
|
31829
32085
|
if (
|
|
31830
32086
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
@@ -31838,8 +32094,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31838
32094
|
message: null
|
|
31839
32095
|
};
|
|
31840
32096
|
}
|
|
31841
|
-
} else if (
|
|
31842
|
-
const guess =
|
|
32097
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
32098
|
+
const guess = userInput.coords;
|
|
31843
32099
|
const correct = rubric.correct.coords;
|
|
31844
32100
|
let match;
|
|
31845
32101
|
if (rubric.correct.match === "congruent") {
|
|
@@ -31878,7 +32134,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
31878
32134
|
|
|
31879
32135
|
// The input wasn't correct, so check if it's a blank input or if it's
|
|
31880
32136
|
// actually just wrong
|
|
31881
|
-
if (!hasValue || ___default["default"].isEqual(
|
|
32137
|
+
if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
|
|
31882
32138
|
// We're where we started.
|
|
31883
32139
|
return {
|
|
31884
32140
|
type: "invalid",
|
|
@@ -42073,7 +42329,7 @@ const init = function (options) {
|
|
|
42073
42329
|
|
|
42074
42330
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
42075
42331
|
const libName = "@khanacademy/perseus";
|
|
42076
|
-
const libVersion = "
|
|
42332
|
+
const libVersion = "21.1.0";
|
|
42077
42333
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
42078
42334
|
|
|
42079
42335
|
const apiVersion = {
|
|
@@ -44322,6 +44578,138 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44322
44578
|
replaceDeprecatedWidgets();
|
|
44323
44579
|
};
|
|
44324
44580
|
|
|
44581
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44582
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44583
|
+
|
|
44584
|
+
/**
|
|
44585
|
+
* Regex for widget placeholders in a string.
|
|
44586
|
+
*
|
|
44587
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
44588
|
+
* Second capture group is the widget type (ex. "radio)
|
|
44589
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44590
|
+
*/
|
|
44591
|
+
function getWidgetRegex() {
|
|
44592
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
44593
|
+
}
|
|
44594
|
+
|
|
44595
|
+
/**
|
|
44596
|
+
* Add a widget placeholder using the widget ID.
|
|
44597
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44598
|
+
*
|
|
44599
|
+
* @param {string} id
|
|
44600
|
+
* @returns {string}
|
|
44601
|
+
*/
|
|
44602
|
+
function addWidget(id) {
|
|
44603
|
+
return `[[☃ ${id}]]`;
|
|
44604
|
+
}
|
|
44605
|
+
|
|
44606
|
+
/**
|
|
44607
|
+
* Get a widget type by a widget's ID
|
|
44608
|
+
* (please don't derive type from ID)
|
|
44609
|
+
*
|
|
44610
|
+
* @param {string} widgetId the ID of the widget
|
|
44611
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44612
|
+
* @returns {string} the widget type (ie "radio")
|
|
44613
|
+
*/
|
|
44614
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44615
|
+
const widget = widgetMap[WidgetId];
|
|
44616
|
+
return widget?.type ?? null;
|
|
44617
|
+
}
|
|
44618
|
+
|
|
44619
|
+
/**
|
|
44620
|
+
* Does the content have a specific type of widget?
|
|
44621
|
+
*
|
|
44622
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44623
|
+
* @param {string} content the string to search through
|
|
44624
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44625
|
+
* @returns {boolean} if the content includes the widget type
|
|
44626
|
+
*/
|
|
44627
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44628
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44629
|
+
}
|
|
44630
|
+
|
|
44631
|
+
/**
|
|
44632
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44633
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44634
|
+
*
|
|
44635
|
+
* Content should contain Perseus widget placeholders,
|
|
44636
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44637
|
+
*
|
|
44638
|
+
* @param {string} content
|
|
44639
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
44640
|
+
*/
|
|
44641
|
+
function getWidgetIdsFromContent(content) {
|
|
44642
|
+
const widgets = [];
|
|
44643
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44644
|
+
let match = localWidgetRegex.exec(content);
|
|
44645
|
+
while (match !== null) {
|
|
44646
|
+
widgets.push(match[1]);
|
|
44647
|
+
match = localWidgetRegex.exec(content);
|
|
44648
|
+
}
|
|
44649
|
+
return widgets;
|
|
44650
|
+
}
|
|
44651
|
+
|
|
44652
|
+
/**
|
|
44653
|
+
* Get a list of widget IDs from content,
|
|
44654
|
+
* but only for specific widget types
|
|
44655
|
+
*
|
|
44656
|
+
* @param {string} type the type of widget (ie "radio")
|
|
44657
|
+
* @param {string} content the string to parse
|
|
44658
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44659
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
44660
|
+
*/
|
|
44661
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
44662
|
+
const rv = [];
|
|
44663
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
44664
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
44665
|
+
const widget = widgetMap[widgetId];
|
|
44666
|
+
if (widget?.type === type) {
|
|
44667
|
+
rv.push(widgetId);
|
|
44668
|
+
}
|
|
44669
|
+
});
|
|
44670
|
+
return rv;
|
|
44671
|
+
}
|
|
44672
|
+
|
|
44673
|
+
/**
|
|
44674
|
+
* Pull the widget map out of ItemData
|
|
44675
|
+
*
|
|
44676
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44677
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44678
|
+
*/
|
|
44679
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44680
|
+
return itemData.question.widgets;
|
|
44681
|
+
}
|
|
44682
|
+
|
|
44683
|
+
/**
|
|
44684
|
+
* Get the widget information from a WidgetMap
|
|
44685
|
+
*
|
|
44686
|
+
* @param {string} widgetId the ID of the widget
|
|
44687
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44688
|
+
*/
|
|
44689
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44690
|
+
return widgetMap[widgetId] ?? null;
|
|
44691
|
+
}
|
|
44692
|
+
|
|
44693
|
+
/**
|
|
44694
|
+
* Get select widgets from a widget map.
|
|
44695
|
+
* Useful for multi-items that needs to split
|
|
44696
|
+
* one PerseusItem into several
|
|
44697
|
+
*
|
|
44698
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44699
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44700
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44701
|
+
*/
|
|
44702
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44703
|
+
const widgets = {};
|
|
44704
|
+
widgetIds.forEach(widgetId => {
|
|
44705
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44706
|
+
if (widget) {
|
|
44707
|
+
widgets[widgetId] = widget;
|
|
44708
|
+
}
|
|
44709
|
+
});
|
|
44710
|
+
return widgets;
|
|
44711
|
+
}
|
|
44712
|
+
|
|
44325
44713
|
/**
|
|
44326
44714
|
* This function extracts the answers from the widgets.
|
|
44327
44715
|
*
|
|
@@ -44443,7 +44831,7 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44443
44831
|
// E.g. 'item 1\nitem 2\nitem 3'
|
|
44444
44832
|
const orderer = widget;
|
|
44445
44833
|
if (orderer.options?.correctOptions) {
|
|
44446
|
-
answers.push(orderer.options.correctOptions
|
|
44834
|
+
answers.push(joinOptionContents(orderer.options.correctOptions));
|
|
44447
44835
|
}
|
|
44448
44836
|
break;
|
|
44449
44837
|
case "sorter":
|
|
@@ -44502,6 +44890,31 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44502
44890
|
return answers;
|
|
44503
44891
|
}
|
|
44504
44892
|
|
|
44893
|
+
/**
|
|
44894
|
+
* Join the content of the options together.
|
|
44895
|
+
* @param {Array<{content: string}>} options
|
|
44896
|
+
* @returns {string}
|
|
44897
|
+
*/
|
|
44898
|
+
const joinOptionContents = options => options.map(_ref => {
|
|
44899
|
+
let {
|
|
44900
|
+
content
|
|
44901
|
+
} = _ref;
|
|
44902
|
+
return content;
|
|
44903
|
+
}).join("\n");
|
|
44904
|
+
|
|
44905
|
+
/**
|
|
44906
|
+
* Convert an index to an option letter.
|
|
44907
|
+
* @param {number} index
|
|
44908
|
+
* @returns {string}
|
|
44909
|
+
* @example
|
|
44910
|
+
* toOptionLetter(0) // 'A'
|
|
44911
|
+
* toOptionLetter(1) // 'B'
|
|
44912
|
+
* toOptionLetter(25) // 'Z'
|
|
44913
|
+
* // Once the index goes past 25, it will start returning special characters
|
|
44914
|
+
* toOptionLetter(26) // '['
|
|
44915
|
+
*/
|
|
44916
|
+
const toOptionLetter = index => String.fromCharCode("A".charCodeAt(0) + index);
|
|
44917
|
+
|
|
44505
44918
|
/**
|
|
44506
44919
|
* Inject a string equivalent of the widgets into the content.
|
|
44507
44920
|
*
|
|
@@ -44512,7 +44925,7 @@ function getAnswersFromWidgets(widgets) {
|
|
|
44512
44925
|
* @param {PerseusRenderer["widgets"]} widgets
|
|
44513
44926
|
* @returns
|
|
44514
44927
|
*/
|
|
44515
|
-
function injectWidgets(content, widgets) {
|
|
44928
|
+
function injectWidgets(content, widgets, widgetProps) {
|
|
44516
44929
|
// The types for taskProgress.itemData are not well defined,
|
|
44517
44930
|
// so there is a chance that widgets or content could be undefined.
|
|
44518
44931
|
// In that scenario, simply exit early and return the content as is.
|
|
@@ -44531,11 +44944,22 @@ function injectWidgets(content, widgets) {
|
|
|
44531
44944
|
switch (widget.type) {
|
|
44532
44945
|
case "radio":
|
|
44533
44946
|
// Replace radio with the radio options
|
|
44534
|
-
// '[[☃ Radio 1]]' -> '
|
|
44947
|
+
// '[[☃ Radio 1]]' -> 'choice 1\nchoice 2\nchoice 3'
|
|
44948
|
+
// or if the current widget state is available with proper order,
|
|
44949
|
+
// '[[☃ Radio 1]]' ->
|
|
44950
|
+
// 'Option A: choice 1\nOption B: choice 2\nOption C: choice 3'
|
|
44535
44951
|
const radio = widget;
|
|
44952
|
+
const radioProps = widgetProps?.[widgetID];
|
|
44536
44953
|
if (radio.options?.choices?.length) {
|
|
44537
|
-
let radioContext =
|
|
44538
|
-
|
|
44954
|
+
let radioContext = joinOptionContents(radioProps ? radioProps.choices.map((_ref2, i) => {
|
|
44955
|
+
let {
|
|
44956
|
+
content
|
|
44957
|
+
} = _ref2;
|
|
44958
|
+
return {
|
|
44959
|
+
content: `Option ${toOptionLetter(i)}: ${content}`
|
|
44960
|
+
};
|
|
44961
|
+
}) : radio.options.choices);
|
|
44962
|
+
if (!radioProps && radio.options?.randomize) {
|
|
44539
44963
|
radioContext += "\nThose options are displayed in a different order to the user. If the user says the letter, number, or ordinal number, always ask them clarify which option they are referring to.\n";
|
|
44540
44964
|
}
|
|
44541
44965
|
context = context.replace(`[[☃ ${widgetID}]]`, radioContext);
|
|
@@ -44643,7 +45067,7 @@ function injectWidgets(content, widgets) {
|
|
|
44643
45067
|
// '[[☃ Orderer 1]]' -> 'option 1\noption 2\noption 3'
|
|
44644
45068
|
const orderer = widget;
|
|
44645
45069
|
if (orderer.options?.options) {
|
|
44646
|
-
context = context.replace(`[[☃ ${widgetID}]]`, orderer.options.options
|
|
45070
|
+
context = context.replace(`[[☃ ${widgetID}]]`, joinOptionContents(orderer.options.options));
|
|
44647
45071
|
}
|
|
44648
45072
|
break;
|
|
44649
45073
|
case "sorter":
|
|
@@ -44726,18 +45150,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
|
|
|
44726
45150
|
|
|
44727
45151
|
/* Widgets that are supported for automatic scoring */
|
|
44728
45152
|
const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
|
|
44729
|
-
const getWidgetTypeFromWidgetKey = widgetKey => {
|
|
44730
|
-
return widgetKey.split(" ")[0];
|
|
44731
|
-
};
|
|
44732
45153
|
|
|
44733
45154
|
/* Verify if the perseus item has supported widgets for automatic scoring */
|
|
44734
|
-
const isWrongAnswerSupported =
|
|
44735
|
-
return
|
|
45155
|
+
const isWrongAnswerSupported = (widgetIds, widgetMap) => {
|
|
45156
|
+
return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
|
|
44736
45157
|
};
|
|
44737
45158
|
|
|
44738
|
-
/* Verify if the widget
|
|
44739
|
-
const shouldHaveIndividualAnswer =
|
|
44740
|
-
return INDIVIDUAL_ANSWER_WIDGETS.includes(
|
|
45159
|
+
/* Verify if the widget ID has an individual answer for the coach report view */
|
|
45160
|
+
const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
|
|
45161
|
+
return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
|
|
44741
45162
|
};
|
|
44742
45163
|
|
|
44743
45164
|
/* Returns the answer userInput submission for currently supported widgets */
|
|
@@ -44756,29 +45177,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
|
|
|
44756
45177
|
return userInput;
|
|
44757
45178
|
};
|
|
44758
45179
|
|
|
44759
|
-
/* Returns the correct answer for a given widget
|
|
45180
|
+
/* Returns the correct answer for a given widget ID and Perseus Item */
|
|
44760
45181
|
// TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
|
|
44761
45182
|
// TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
|
|
44762
|
-
const
|
|
44763
|
-
const rubric = itemData.question.widgets[
|
|
44764
|
-
const
|
|
45183
|
+
const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
|
|
45184
|
+
const rubric = itemData.question.widgets[widgetId].options;
|
|
45185
|
+
const widgetMap = getWidgetsMapFromItemData(itemData);
|
|
45186
|
+
const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
|
|
44765
45187
|
const widget = getWidget(widgetType);
|
|
44766
45188
|
|
|
44767
45189
|
// @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
|
|
44768
45190
|
return widget?.getOneCorrectAnswerFromRubric?.(rubric);
|
|
44769
45191
|
};
|
|
44770
45192
|
|
|
44771
|
-
/* Verify if the widget
|
|
44772
|
-
const
|
|
44773
|
-
return perseusItem.question.content.indexOf(
|
|
45193
|
+
/* Verify if the widget ID exists in the content string of the Perseus Item */
|
|
45194
|
+
const isWidgetIdInContent = (perseusItem, widgetId) => {
|
|
45195
|
+
return perseusItem.question.content.indexOf(widgetId) !== -1;
|
|
44774
45196
|
};
|
|
44775
45197
|
|
|
44776
|
-
/* Return an array of all the widget
|
|
44777
|
-
const
|
|
45198
|
+
/* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
|
|
45199
|
+
const getValidWidgetIds = perseusItem => {
|
|
44778
45200
|
const {
|
|
44779
45201
|
widgets
|
|
44780
45202
|
} = perseusItem.question;
|
|
44781
|
-
return wonderStuffCore.keys(widgets).filter(
|
|
45203
|
+
return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44782
45204
|
};
|
|
44783
45205
|
|
|
44784
45206
|
/**
|
|
@@ -44826,7 +45248,7 @@ const genericPerseusItemData = {
|
|
|
44826
45248
|
* uses a basic Perseus object and updates it with custom values as needed.
|
|
44827
45249
|
*
|
|
44828
45250
|
* @param {Partial<PerseusItem>} customFields
|
|
44829
|
-
* @returns PerseusItem
|
|
45251
|
+
* @returns {PerseusItem}
|
|
44830
45252
|
*/
|
|
44831
45253
|
function generateTestPerseusItem() {
|
|
44832
45254
|
let customFields = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
@@ -44836,6 +45258,107 @@ function generateTestPerseusItem() {
|
|
|
44836
45258
|
};
|
|
44837
45259
|
}
|
|
44838
45260
|
|
|
45261
|
+
/**
|
|
45262
|
+
* Creates an object with the bare amount
|
|
45263
|
+
* of data to be a properly typed RadioWidget
|
|
45264
|
+
*
|
|
45265
|
+
* @returns {RadioWidget} skeleton RadioWidget for testing
|
|
45266
|
+
*/
|
|
45267
|
+
function generateTestRadioWidget() {
|
|
45268
|
+
return {
|
|
45269
|
+
type: "radio",
|
|
45270
|
+
options: {
|
|
45271
|
+
choices: []
|
|
45272
|
+
}
|
|
45273
|
+
};
|
|
45274
|
+
}
|
|
45275
|
+
|
|
45276
|
+
/**
|
|
45277
|
+
* Creates an object with the bare amount
|
|
45278
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
45279
|
+
*
|
|
45280
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
45281
|
+
*/
|
|
45282
|
+
function generateTestInteractiveGraphWidget() {
|
|
45283
|
+
return {
|
|
45284
|
+
type: "interactive-graph",
|
|
45285
|
+
options: {
|
|
45286
|
+
step: [1, 1],
|
|
45287
|
+
gridStep: [1, 1],
|
|
45288
|
+
snapStep: [1, 1],
|
|
45289
|
+
markings: "graph",
|
|
45290
|
+
labels: ["x", "y"],
|
|
45291
|
+
showProtractor: false,
|
|
45292
|
+
showRuler: false,
|
|
45293
|
+
rulerLabel: "mm",
|
|
45294
|
+
rulerTicks: 2,
|
|
45295
|
+
range: [[-10, 10], [-10, 10]],
|
|
45296
|
+
graph: {
|
|
45297
|
+
type: "angle"
|
|
45298
|
+
},
|
|
45299
|
+
correct: {
|
|
45300
|
+
type: "angle"
|
|
45301
|
+
}
|
|
45302
|
+
}
|
|
45303
|
+
};
|
|
45304
|
+
}
|
|
45305
|
+
|
|
45306
|
+
/**
|
|
45307
|
+
* Creates an object with the bare amount
|
|
45308
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
45309
|
+
*
|
|
45310
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
45311
|
+
*/
|
|
45312
|
+
function generateTestCategorizerWidget() {
|
|
45313
|
+
return {
|
|
45314
|
+
type: "categorizer",
|
|
45315
|
+
options: {
|
|
45316
|
+
items: [],
|
|
45317
|
+
categories: [],
|
|
45318
|
+
randomizeItems: false,
|
|
45319
|
+
static: false,
|
|
45320
|
+
values: []
|
|
45321
|
+
}
|
|
45322
|
+
};
|
|
45323
|
+
}
|
|
45324
|
+
|
|
45325
|
+
/**
|
|
45326
|
+
* Creates an object with the bare amount
|
|
45327
|
+
* of data to be a properly typed ExpressionWidget
|
|
45328
|
+
*
|
|
45329
|
+
* @returns {ExpressionWidget} skeleton ExpressionWidget for testing
|
|
45330
|
+
*/
|
|
45331
|
+
function generateTestExpressionWidget() {
|
|
45332
|
+
return {
|
|
45333
|
+
type: "expression",
|
|
45334
|
+
options: {
|
|
45335
|
+
answerForms: [],
|
|
45336
|
+
buttonSets: ["basic"],
|
|
45337
|
+
functions: [],
|
|
45338
|
+
times: false
|
|
45339
|
+
}
|
|
45340
|
+
};
|
|
45341
|
+
}
|
|
45342
|
+
|
|
45343
|
+
/**
|
|
45344
|
+
* Creates an object with the bare amount
|
|
45345
|
+
* of data to be a properly typed NumericInputWidget
|
|
45346
|
+
*
|
|
45347
|
+
* @returns {NumericInputWidget} skeleton NumericInputWidget for testing
|
|
45348
|
+
*/
|
|
45349
|
+
function generateTestNumericInputWidget() {
|
|
45350
|
+
return {
|
|
45351
|
+
type: "numeric-input",
|
|
45352
|
+
options: {
|
|
45353
|
+
answers: [],
|
|
45354
|
+
labelText: "",
|
|
45355
|
+
size: "normal",
|
|
45356
|
+
coefficient: false,
|
|
45357
|
+
static: false
|
|
45358
|
+
}
|
|
45359
|
+
};
|
|
45360
|
+
}
|
|
45361
|
+
|
|
44839
45362
|
/**
|
|
44840
45363
|
* Some helpers for CEDAR logging. CEDAR convention dictates
|
|
44841
45364
|
* that enum values should be upper case. Ex: for the
|
|
@@ -44890,162 +45413,6 @@ function convertWidgetNameToEnum(name) {
|
|
|
44890
45413
|
}
|
|
44891
45414
|
return widgetEnum;
|
|
44892
45415
|
}
|
|
44893
|
-
function getWidgetTypeFromWidgetId(widgetId) {
|
|
44894
|
-
const [widgetType, widgetNumber] = widgetId.split(" ");
|
|
44895
|
-
if (!widgetNumber) {
|
|
44896
|
-
throw new Error(`Incorrect format for widget ID: ${widgetId}`);
|
|
44897
|
-
}
|
|
44898
|
-
if (!widgetNameToEnum[widgetType]) {
|
|
44899
|
-
throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
|
|
44900
|
-
}
|
|
44901
|
-
return widgetType;
|
|
44902
|
-
}
|
|
44903
|
-
|
|
44904
|
-
let WidgetType = /*#__PURE__*/function (WidgetType) {
|
|
44905
|
-
WidgetType["Categorizer"] = "categorizer";
|
|
44906
|
-
WidgetType["CSProgram"] = "cs-program";
|
|
44907
|
-
WidgetType["Definition"] = "definition";
|
|
44908
|
-
WidgetType["Dropdown"] = "dropdown";
|
|
44909
|
-
WidgetType["Explanation"] = "explanation";
|
|
44910
|
-
WidgetType["Expression"] = "expression";
|
|
44911
|
-
WidgetType["GradedGroup"] = "graded-group";
|
|
44912
|
-
WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
|
|
44913
|
-
WidgetType["GradedGroupSet"] = "graded-group-set";
|
|
44914
|
-
WidgetType["Grapher"] = "grapher";
|
|
44915
|
-
WidgetType["Group"] = "group";
|
|
44916
|
-
WidgetType["Iframe"] = "iframe";
|
|
44917
|
-
WidgetType["Image"] = "image";
|
|
44918
|
-
WidgetType["InputNumber"] = "input-number";
|
|
44919
|
-
WidgetType["Interaction"] = "interaction";
|
|
44920
|
-
WidgetType["InteractiveGraph"] = "interactive-graph";
|
|
44921
|
-
WidgetType["InteractiveNumberLine"] = "interactive-number-line";
|
|
44922
|
-
WidgetType["LabelImage"] = "label-image";
|
|
44923
|
-
WidgetType["Matcher"] = "matcher";
|
|
44924
|
-
WidgetType["Matrix"] = "matrix";
|
|
44925
|
-
WidgetType["Measurer"] = "measurer";
|
|
44926
|
-
WidgetType["Molecule"] = "molecule";
|
|
44927
|
-
WidgetType["NumberLine"] = "number-line";
|
|
44928
|
-
WidgetType["NumericInput"] = "numeric-input";
|
|
44929
|
-
WidgetType["Orderer"] = "orderer";
|
|
44930
|
-
WidgetType["Passage"] = "passage";
|
|
44931
|
-
WidgetType["PassageRef"] = "passage-ref";
|
|
44932
|
-
WidgetType["PassageRefTarget"] = "passage-ref-target";
|
|
44933
|
-
WidgetType["Plotter"] = "plotter";
|
|
44934
|
-
WidgetType["Protractor"] = "protractor";
|
|
44935
|
-
WidgetType["PythonProgram"] = "python-program";
|
|
44936
|
-
WidgetType["Radio"] = "radio";
|
|
44937
|
-
WidgetType["Sorter"] = "sorter";
|
|
44938
|
-
WidgetType["Table"] = "table";
|
|
44939
|
-
WidgetType["Unit"] = "unit";
|
|
44940
|
-
WidgetType["Video"] = "video";
|
|
44941
|
-
WidgetType["VideoTranscriptLink"] = "video-transcript-link";
|
|
44942
|
-
return WidgetType;
|
|
44943
|
-
}({});
|
|
44944
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44945
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44946
|
-
|
|
44947
|
-
/**
|
|
44948
|
-
* Regex for widget placeholders in a string.
|
|
44949
|
-
*
|
|
44950
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
44951
|
-
* Second capture group is the widget type (ex. "radio)
|
|
44952
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44953
|
-
*/
|
|
44954
|
-
function getWidgetRegex() {
|
|
44955
|
-
return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
|
|
44956
|
-
}
|
|
44957
|
-
|
|
44958
|
-
/**
|
|
44959
|
-
* Add a widget placeholder using the widget ID.
|
|
44960
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44961
|
-
*
|
|
44962
|
-
* @param {string} id
|
|
44963
|
-
* @returns {string}
|
|
44964
|
-
*/
|
|
44965
|
-
function addWidget(id) {
|
|
44966
|
-
return `[[☃ ${id}]]`;
|
|
44967
|
-
}
|
|
44968
|
-
|
|
44969
|
-
/**
|
|
44970
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44971
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44972
|
-
*
|
|
44973
|
-
* Content should contain Perseus widget placeholders,
|
|
44974
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44975
|
-
*
|
|
44976
|
-
* @param {string} content
|
|
44977
|
-
* @returns {Array<WidgetId>} widgets
|
|
44978
|
-
*/
|
|
44979
|
-
function getAllWidgetIds(content) {
|
|
44980
|
-
const widgets = [];
|
|
44981
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44982
|
-
let match = localWidgetRegex.exec(content);
|
|
44983
|
-
while (match !== null) {
|
|
44984
|
-
widgets.push(match[1]);
|
|
44985
|
-
match = localWidgetRegex.exec(content);
|
|
44986
|
-
}
|
|
44987
|
-
return widgets;
|
|
44988
|
-
}
|
|
44989
|
-
|
|
44990
|
-
/**
|
|
44991
|
-
* Check if a specific widget is an actual widget type using its name.
|
|
44992
|
-
* The widget name should include a widget type, without an instance number,
|
|
44993
|
-
* as a string. ex. 'radio'
|
|
44994
|
-
*
|
|
44995
|
-
* @param {string} widgetName
|
|
44996
|
-
* @returns {boolean}
|
|
44997
|
-
*/
|
|
44998
|
-
function isWidgetType(widgetName) {
|
|
44999
|
-
return Object.values(WidgetType).includes(widgetName);
|
|
45000
|
-
}
|
|
45001
|
-
|
|
45002
|
-
/**
|
|
45003
|
-
* Extract all widget types from a Perseus JSON content string.
|
|
45004
|
-
* This does not include the instance number and prevents duplicates.
|
|
45005
|
-
* example output: ['radio', 'categorizer']
|
|
45006
|
-
*
|
|
45007
|
-
* @param {string} content
|
|
45008
|
-
* @returns {Array<WidgetId>}
|
|
45009
|
-
*/
|
|
45010
|
-
function getAllWidgetTypes(content) {
|
|
45011
|
-
const widgetTypes = [];
|
|
45012
|
-
const localWidgetRegex = getWidgetRegex();
|
|
45013
|
-
let match = localWidgetRegex.exec(content);
|
|
45014
|
-
while (match !== null) {
|
|
45015
|
-
const matchType = match[2];
|
|
45016
|
-
if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
|
|
45017
|
-
widgetTypes.push(matchType);
|
|
45018
|
-
}
|
|
45019
|
-
match = localWidgetRegex.exec(content);
|
|
45020
|
-
}
|
|
45021
|
-
return widgetTypes;
|
|
45022
|
-
}
|
|
45023
|
-
|
|
45024
|
-
/**
|
|
45025
|
-
* Check if a specific widget is a question widget type using its ID.
|
|
45026
|
-
* The widget ID includes the widget type and the instance number.
|
|
45027
|
-
*
|
|
45028
|
-
* @param {WidgetId} widgetId
|
|
45029
|
-
* @returns {boolean}
|
|
45030
|
-
*/
|
|
45031
|
-
function isQuestionWidgetType(widgetId) {
|
|
45032
|
-
return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
|
|
45033
|
-
}
|
|
45034
|
-
|
|
45035
|
-
/**
|
|
45036
|
-
* Extract the widget IDs of all widgets considered question widgets. Widget IDs
|
|
45037
|
-
* include widget type and instance number.
|
|
45038
|
-
* example output: ['radio 1', 'categorizer 1']
|
|
45039
|
-
*
|
|
45040
|
-
* Content should contain Perseus widget placeholders,
|
|
45041
|
-
* which look like: '[[☃ radio 1]]'.
|
|
45042
|
-
*
|
|
45043
|
-
* @param {string} content
|
|
45044
|
-
* @returns Array<WidgetId>
|
|
45045
|
-
*/
|
|
45046
|
-
function getQuestionWidgetIds(content) {
|
|
45047
|
-
return getAllWidgetIds(content).filter(isQuestionWidgetType);
|
|
45048
|
-
}
|
|
45049
45416
|
|
|
45050
45417
|
const EditorJsonify = {
|
|
45051
45418
|
serialize: function () {
|
|
@@ -45097,7 +45464,6 @@ exports.TableWidget = Table$1;
|
|
|
45097
45464
|
exports.Util = Util;
|
|
45098
45465
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
45099
45466
|
exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
|
|
45100
|
-
exports.WidgetType = WidgetType;
|
|
45101
45467
|
exports.Widgets = widgets$1;
|
|
45102
45468
|
exports.addWidget = addWidget;
|
|
45103
45469
|
exports.apiVersion = apiVersion;
|
|
@@ -45105,19 +45471,26 @@ exports.buildEmptyItemTreeForShape = buildEmptyItemTreeForShape;
|
|
|
45105
45471
|
exports.buildMapper = buildMapper;
|
|
45106
45472
|
exports.components = components;
|
|
45107
45473
|
exports.constants = constants;
|
|
45474
|
+
exports.contentHasWidgetType = contentHasWidgetType;
|
|
45108
45475
|
exports.convertWidgetNameToEnum = convertWidgetNameToEnum;
|
|
45109
45476
|
exports.displaySigFigs = displaySigFigs;
|
|
45477
|
+
exports.generateTestCategorizerWidget = generateTestCategorizerWidget;
|
|
45478
|
+
exports.generateTestExpressionWidget = generateTestExpressionWidget;
|
|
45479
|
+
exports.generateTestInteractiveGraphWidget = generateTestInteractiveGraphWidget;
|
|
45480
|
+
exports.generateTestNumericInputWidget = generateTestNumericInputWidget;
|
|
45110
45481
|
exports.generateTestPerseusItem = generateTestPerseusItem;
|
|
45482
|
+
exports.generateTestRadioWidget = generateTestRadioWidget;
|
|
45111
45483
|
exports.genericPerseusItemData = genericPerseusItemData;
|
|
45112
|
-
exports.getAllWidgetIds = getAllWidgetIds;
|
|
45113
|
-
exports.getAllWidgetTypes = getAllWidgetTypes;
|
|
45114
45484
|
exports.getAnswerFromUserInput = getAnswerFromUserInput;
|
|
45115
45485
|
exports.getAnswersFromWidgets = getAnswersFromWidgets;
|
|
45116
|
-
exports.
|
|
45117
|
-
exports.
|
|
45118
|
-
exports.
|
|
45119
|
-
exports.
|
|
45120
|
-
exports.
|
|
45486
|
+
exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
|
|
45487
|
+
exports.getValidWidgetIds = getValidWidgetIds;
|
|
45488
|
+
exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
|
|
45489
|
+
exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
|
|
45490
|
+
exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
|
|
45491
|
+
exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
|
|
45492
|
+
exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
|
|
45493
|
+
exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;
|
|
45121
45494
|
exports.globalConstants = globalConstants;
|
|
45122
45495
|
exports.globalStyles = globalStyles;
|
|
45123
45496
|
exports.icons = iconPaths;
|
|
@@ -45125,7 +45498,7 @@ exports.init = init;
|
|
|
45125
45498
|
exports.injectWidgets = injectWidgets;
|
|
45126
45499
|
exports.interactiveSizes = interactiveSizes$1;
|
|
45127
45500
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
45128
|
-
exports.
|
|
45501
|
+
exports.isWidgetIdInContent = isWidgetIdInContent;
|
|
45129
45502
|
exports.isWrongAnswerSupported = isWrongAnswerSupported;
|
|
45130
45503
|
exports.itemDataVersion = itemDataVersion;
|
|
45131
45504
|
exports.itemToTree = itemToTree;
|