@khanacademy/perseus 20.5.0 → 20.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/icon.d.ts +1 -0
- package/dist/es/index.css +19 -6
- package/dist/es/index.js +750 -269
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +19 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +853 -358
- package/dist/index.js.map +1 -1
- package/dist/interactive2/movable.d.ts +1 -1
- package/dist/interactive2/objective_.d.ts +2 -2
- package/dist/styles/global-styles.d.ts +1 -0
- package/dist/styles/shared.d.ts +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/util/a11y.d.ts +1 -0
- package/dist/util/extract-perseus-data.d.ts +8 -1
- package/dist/util/snowman-utils.d.ts +90 -0
- package/dist/util/widget-enum-utils.d.ts +11 -0
- package/dist/util.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +9 -2
- package/dist/widgets/interactive-graphs/graphs/index.d.ts +1 -0
- package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +5 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +27 -0
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-reducer.d.ts +3 -0
- package/dist/widgets/interactive-graphs/{interactive-graph-state.d.ts → reducer/interactive-graph-state.d.ts} +2 -2
- package/dist/widgets/interactive-graphs/types.d.ts +12 -6
- package/dist/widgets/interactive-graphs/utils.d.ts +5 -0
- package/dist/widgets/label-image/answer-pill.d.ts +1 -0
- package/dist/widgets/label-image/marker.d.ts +1 -0
- package/dist/widgets/radio/choice-icon/shared-styles.d.ts +1 -0
- package/dist/widgets/radio.d.ts +1 -1
- package/package.json +4 -6
- package/dist/widgets/interactive-graphs/interactive-graph-action.d.ts +0 -17
- package/dist/widgets/interactive-graphs/interactive-graph-reducer.d.ts +0 -3
- /package/dist/widgets/radio/{widget.d.ts → radio.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -18894,7 +18894,7 @@ const isAccessible = widgetInfo => {
|
|
|
18894
18894
|
}
|
|
18895
18895
|
return !!accessible;
|
|
18896
18896
|
};
|
|
18897
|
-
const getAllWidgetTypes = () => {
|
|
18897
|
+
const getAllWidgetTypes$1 = () => {
|
|
18898
18898
|
return ___default["default"].keys(widgets);
|
|
18899
18899
|
};
|
|
18900
18900
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
@@ -19166,7 +19166,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19166
19166
|
getVersionVector: getVersionVector,
|
|
19167
19167
|
getPublicWidgets: getPublicWidgets,
|
|
19168
19168
|
isAccessible: isAccessible,
|
|
19169
|
-
getAllWidgetTypes: getAllWidgetTypes,
|
|
19169
|
+
getAllWidgetTypes: getAllWidgetTypes$1,
|
|
19170
19170
|
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
19171
19171
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
19172
19172
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
@@ -19560,7 +19560,6 @@ class Renderer extends React__namespace.Component {
|
|
|
19560
19560
|
|
|
19561
19561
|
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19562
19562
|
_getAllWidgetsInfo = props => {
|
|
19563
|
-
// @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
|
|
19564
19563
|
return mapObject$1(props.widgets, (widgetInfo, widgetId) => {
|
|
19565
19564
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19566
19565
|
const newValues = {};
|
|
@@ -28321,22 +28320,55 @@ const GraphLockedLayer = props => {
|
|
|
28321
28320
|
};
|
|
28322
28321
|
|
|
28323
28322
|
const MOVE_CONTROL_POINT = "move-control-point";
|
|
28324
|
-
function moveControlPoint(
|
|
28323
|
+
function moveControlPoint(pointIndex, destination, itemIndex) {
|
|
28325
28324
|
return {
|
|
28326
28325
|
type: MOVE_CONTROL_POINT,
|
|
28327
|
-
|
|
28326
|
+
itemIndex,
|
|
28328
28327
|
pointIndex,
|
|
28329
28328
|
destination
|
|
28330
28329
|
};
|
|
28331
28330
|
}
|
|
28331
|
+
const MOVE_ALL = "move-all";
|
|
28332
28332
|
const MOVE_LINE = "move-line";
|
|
28333
|
-
|
|
28333
|
+
/** This action assumes the state.coords holds an array of collinear tuples that define lines */
|
|
28334
|
+
function moveLine(itemIndex, delta) {
|
|
28334
28335
|
return {
|
|
28335
28336
|
type: MOVE_LINE,
|
|
28336
|
-
|
|
28337
|
+
itemIndex,
|
|
28337
28338
|
delta
|
|
28338
28339
|
};
|
|
28339
28340
|
}
|
|
28341
|
+
/** This action assumes a flat array of vectors */
|
|
28342
|
+
const moveAll = delta => ({
|
|
28343
|
+
type: MOVE_ALL,
|
|
28344
|
+
delta
|
|
28345
|
+
});
|
|
28346
|
+
|
|
28347
|
+
/**
|
|
28348
|
+
* 44 is touch best practice and AAA compliant for WCAG
|
|
28349
|
+
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
28350
|
+
*/
|
|
28351
|
+
const TARGET_SIZE = 44;
|
|
28352
|
+
|
|
28353
|
+
// same as pointsFromNormalized in interactive-graph.tsx
|
|
28354
|
+
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
28355
|
+
// maps over [x, y]
|
|
28356
|
+
coords.map((coord, i) => {
|
|
28357
|
+
const axisRange = range[i];
|
|
28358
|
+
if (noSnap) {
|
|
28359
|
+
return axisRange[0] + (axisRange[1] - axisRange[0]) * coord;
|
|
28360
|
+
}
|
|
28361
|
+
const axisStep = step[i];
|
|
28362
|
+
const nSteps = Math.floor((axisRange[1] - axisRange[0]) / axisStep);
|
|
28363
|
+
const tick = Math.round(coord * nSteps);
|
|
28364
|
+
return axisRange[0] + axisStep * tick;
|
|
28365
|
+
}));
|
|
28366
|
+
|
|
28367
|
+
// same as normalizeCoords in interactive-graph.tsx
|
|
28368
|
+
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
28369
|
+
const extent = ranges[i][1] - ranges[i][0];
|
|
28370
|
+
return (coord + ranges[i][1]) / extent;
|
|
28371
|
+
}));
|
|
28340
28372
|
|
|
28341
28373
|
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
28342
28374
|
const MovableLine = props => {
|
|
@@ -28384,7 +28416,7 @@ const MovableLine = props => {
|
|
|
28384
28416
|
end: endPtPx,
|
|
28385
28417
|
style: {
|
|
28386
28418
|
stroke: "transparent",
|
|
28387
|
-
strokeWidth:
|
|
28419
|
+
strokeWidth: TARGET_SIZE
|
|
28388
28420
|
}
|
|
28389
28421
|
}), /*#__PURE__*/React__namespace.createElement(SVGLine, {
|
|
28390
28422
|
start: startPtPx,
|
|
@@ -28417,7 +28449,7 @@ function SVGLine(props) {
|
|
|
28417
28449
|
x2: end[0],
|
|
28418
28450
|
y2: end[1],
|
|
28419
28451
|
style: style,
|
|
28420
|
-
className: dragging ? "movable-
|
|
28452
|
+
className: dragging ? "movable-dragging" : undefined
|
|
28421
28453
|
});
|
|
28422
28454
|
}
|
|
28423
28455
|
|
|
@@ -28492,7 +28524,7 @@ const StyledMovablePoint = props => {
|
|
|
28492
28524
|
}
|
|
28493
28525
|
}, /*#__PURE__*/React__namespace.createElement("circle", {
|
|
28494
28526
|
className: "movable-point-hitbox",
|
|
28495
|
-
r:
|
|
28527
|
+
r: TARGET_SIZE / 2,
|
|
28496
28528
|
cx: x,
|
|
28497
28529
|
cy: y
|
|
28498
28530
|
}), /*#__PURE__*/React__namespace.createElement("circle", {
|
|
@@ -28528,9 +28560,9 @@ const SegmentGraph = props => {
|
|
|
28528
28560
|
snaps: snapStep,
|
|
28529
28561
|
range: range,
|
|
28530
28562
|
onMoveLine: delta => {
|
|
28531
|
-
dispatch(
|
|
28563
|
+
dispatch(moveLine(i, delta));
|
|
28532
28564
|
},
|
|
28533
|
-
onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(
|
|
28565
|
+
onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
|
|
28534
28566
|
"data-testid": "segment" + i
|
|
28535
28567
|
})));
|
|
28536
28568
|
};
|
|
@@ -28573,9 +28605,9 @@ const LinearGraph = props => {
|
|
|
28573
28605
|
snaps: snapStep,
|
|
28574
28606
|
range: range,
|
|
28575
28607
|
onMoveLine: delta => {
|
|
28576
|
-
dispatch(
|
|
28608
|
+
dispatch(moveLine(i, delta));
|
|
28577
28609
|
},
|
|
28578
|
-
onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(
|
|
28610
|
+
onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i))
|
|
28579
28611
|
// "linear" or "linear-system" + index
|
|
28580
28612
|
,
|
|
28581
28613
|
"data-testid": type + i,
|
|
@@ -28626,8 +28658,8 @@ const RayGraph = props => {
|
|
|
28626
28658
|
|
|
28627
28659
|
// a ray only has one line
|
|
28628
28660
|
const [start, end] = lines[0];
|
|
28629
|
-
const handleMoveLine = delta => dispatch(
|
|
28630
|
-
const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(
|
|
28661
|
+
const handleMoveLine = delta => dispatch(moveLine(0, delta));
|
|
28662
|
+
const handleMovePoint = (newPoint, pointIndex) => dispatch(moveControlPoint(pointIndex, newPoint, 0));
|
|
28631
28663
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
28632
28664
|
start: start,
|
|
28633
28665
|
end: end,
|
|
@@ -28646,6 +28678,62 @@ const RayGraph = props => {
|
|
|
28646
28678
|
}));
|
|
28647
28679
|
};
|
|
28648
28680
|
|
|
28681
|
+
const PolygonGraph = props => {
|
|
28682
|
+
const [focused, setFocused] = React__namespace.useState(false);
|
|
28683
|
+
const [hovered, setHovered] = React__namespace.useState(false);
|
|
28684
|
+
const {
|
|
28685
|
+
dispatch
|
|
28686
|
+
} = props;
|
|
28687
|
+
const {
|
|
28688
|
+
coords,
|
|
28689
|
+
type
|
|
28690
|
+
} = props.graphState;
|
|
28691
|
+
const points = coords ?? [[0, 0]];
|
|
28692
|
+
const pointsSum = points.reduce((acc, point) => mafs.vec.add(acc, point), [0, 0]);
|
|
28693
|
+
const midpoint = pointsSum && mafs.vec.scale(pointsSum, 1 / (points.length ?? 1));
|
|
28694
|
+
const ref = React__namespace.useRef(null);
|
|
28695
|
+
const {
|
|
28696
|
+
dragging
|
|
28697
|
+
} = mafs.useMovable({
|
|
28698
|
+
gestureTarget: ref,
|
|
28699
|
+
point: midpoint,
|
|
28700
|
+
onMove: newPoint => {
|
|
28701
|
+
const delta = mafs.vec.sub(newPoint, midpoint);
|
|
28702
|
+
dispatch(moveAll(delta));
|
|
28703
|
+
},
|
|
28704
|
+
constrain: p => p
|
|
28705
|
+
});
|
|
28706
|
+
const active = hovered || focused || dragging;
|
|
28707
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
28708
|
+
points: [...points],
|
|
28709
|
+
color: "var(--movable-line-stroke-color)",
|
|
28710
|
+
svgPolygonProps: {
|
|
28711
|
+
strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)"
|
|
28712
|
+
}
|
|
28713
|
+
}), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
28714
|
+
points: [...points],
|
|
28715
|
+
color: "transparent",
|
|
28716
|
+
svgPolygonProps: {
|
|
28717
|
+
ref,
|
|
28718
|
+
tabIndex: 0,
|
|
28719
|
+
strokeWidth: TARGET_SIZE,
|
|
28720
|
+
style: {
|
|
28721
|
+
cursor: dragging ? "grabbing" : "grab"
|
|
28722
|
+
},
|
|
28723
|
+
onFocus: () => setFocused(true),
|
|
28724
|
+
onBlur: () => setFocused(false),
|
|
28725
|
+
onMouseEnter: () => setHovered(true),
|
|
28726
|
+
onMouseLeave: () => setHovered(false),
|
|
28727
|
+
className: "movable-polygon"
|
|
28728
|
+
}
|
|
28729
|
+
}), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
28730
|
+
key: i,
|
|
28731
|
+
point: point,
|
|
28732
|
+
onMove: destination => dispatch(moveControlPoint(i, destination)),
|
|
28733
|
+
"data-testid": type + i
|
|
28734
|
+
})));
|
|
28735
|
+
};
|
|
28736
|
+
|
|
28649
28737
|
const renderLineLabel = (n, _ref) => {
|
|
28650
28738
|
let [min, max] = _ref;
|
|
28651
28739
|
return n !== -1 && n !== min && n !== max;
|
|
@@ -28661,234 +28749,6 @@ const Grid = props => props.markings === "none" ? null : /*#__PURE__*/React__nam
|
|
|
28661
28749
|
yAxis: axisOptions(props, 1)
|
|
28662
28750
|
});
|
|
28663
28751
|
|
|
28664
|
-
function interactiveGraphReducer(state, action) {
|
|
28665
|
-
switch (action.type) {
|
|
28666
|
-
case MOVE_CONTROL_POINT:
|
|
28667
|
-
{
|
|
28668
|
-
const newCoords = updateAtIndex({
|
|
28669
|
-
array: state.coords,
|
|
28670
|
-
index: action.objectIndex,
|
|
28671
|
-
update: tuple => setAtIndex({
|
|
28672
|
-
array: tuple,
|
|
28673
|
-
index: action.pointIndex,
|
|
28674
|
-
newValue: snap(state, bound$1(state, action.destination))
|
|
28675
|
-
})
|
|
28676
|
-
});
|
|
28677
|
-
if (!validSegments(newCoords)) {
|
|
28678
|
-
return state;
|
|
28679
|
-
}
|
|
28680
|
-
return {
|
|
28681
|
-
...state,
|
|
28682
|
-
hasBeenInteractedWith: true,
|
|
28683
|
-
coords: newCoords
|
|
28684
|
-
};
|
|
28685
|
-
}
|
|
28686
|
-
case MOVE_LINE:
|
|
28687
|
-
{
|
|
28688
|
-
const currentLine = state.coords?.[action.lineIndex];
|
|
28689
|
-
if (!currentLine) {
|
|
28690
|
-
throw new Error("No line to move");
|
|
28691
|
-
}
|
|
28692
|
-
const maxMoves = currentLine.map(point => maxMove(state, point));
|
|
28693
|
-
const minMoves = currentLine.map(point => minMove(state, point));
|
|
28694
|
-
const maxXMove = Math.min(...maxMoves.map(move => move[0]));
|
|
28695
|
-
const maxYMove = Math.min(...maxMoves.map(move => move[1]));
|
|
28696
|
-
const minXMove = Math.max(...minMoves.map(move => move[0]));
|
|
28697
|
-
const minYMove = Math.max(...minMoves.map(move => move[1]));
|
|
28698
|
-
const dx = clamp(action.delta[0], minXMove, maxXMove);
|
|
28699
|
-
const dy = clamp(action.delta[1], minYMove, maxYMove);
|
|
28700
|
-
const newValue = [snap(state, mafs.vec.add(currentLine[0], [dx, dy])), snap(state, mafs.vec.add(currentLine[1], [dx, dy]))];
|
|
28701
|
-
const newLine = setAtIndex({
|
|
28702
|
-
array: state.coords,
|
|
28703
|
-
index: action.lineIndex,
|
|
28704
|
-
newValue
|
|
28705
|
-
});
|
|
28706
|
-
return {
|
|
28707
|
-
...state,
|
|
28708
|
-
hasBeenInteractedWith: true,
|
|
28709
|
-
coords: newLine
|
|
28710
|
-
};
|
|
28711
|
-
}
|
|
28712
|
-
default:
|
|
28713
|
-
throw new wonderStuffCore.UnreachableCaseError(action);
|
|
28714
|
-
}
|
|
28715
|
-
}
|
|
28716
|
-
function snap(state, point) {
|
|
28717
|
-
const [requestedX, requestedY] = point;
|
|
28718
|
-
const [snapX, snapY] = state.snapStep;
|
|
28719
|
-
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
28720
|
-
}
|
|
28721
|
-
|
|
28722
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
28723
|
-
// bounds given in `state`.
|
|
28724
|
-
function bound$1(state, point) {
|
|
28725
|
-
const [requestedX, requestedY] = point;
|
|
28726
|
-
const [snapX, snapY] = state.snapStep;
|
|
28727
|
-
const [[minX, maxX], [minY, maxY]] = state.range;
|
|
28728
|
-
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
28729
|
-
}
|
|
28730
|
-
|
|
28731
|
-
// Returns the vector from the given point to the top-right corner of the graph
|
|
28732
|
-
function maxMove(state, point) {
|
|
28733
|
-
const topRight = bound$1(state, [Infinity, Infinity]);
|
|
28734
|
-
return mafs.vec.sub(topRight, point);
|
|
28735
|
-
}
|
|
28736
|
-
|
|
28737
|
-
// Returns the vector from the given point to the bottom-left corner of the
|
|
28738
|
-
// graph
|
|
28739
|
-
function minMove(state, point) {
|
|
28740
|
-
const bottomLeft = bound$1(state, [-Infinity, -Infinity]);
|
|
28741
|
-
return mafs.vec.sub(bottomLeft, point);
|
|
28742
|
-
}
|
|
28743
|
-
function clamp(value, min, max) {
|
|
28744
|
-
if (value < min) {
|
|
28745
|
-
return min;
|
|
28746
|
-
}
|
|
28747
|
-
if (value > max) {
|
|
28748
|
-
return max;
|
|
28749
|
-
}
|
|
28750
|
-
return value;
|
|
28751
|
-
}
|
|
28752
|
-
function validSegments(segments) {
|
|
28753
|
-
return segments.every(_ref => {
|
|
28754
|
-
let [start, end] = _ref;
|
|
28755
|
-
return !kmath.vector.equal(start, end);
|
|
28756
|
-
});
|
|
28757
|
-
}
|
|
28758
|
-
function updateAtIndex(args) {
|
|
28759
|
-
const {
|
|
28760
|
-
array = [],
|
|
28761
|
-
index,
|
|
28762
|
-
update
|
|
28763
|
-
} = args;
|
|
28764
|
-
const newValue = update(array[index]);
|
|
28765
|
-
return setAtIndex({
|
|
28766
|
-
array,
|
|
28767
|
-
index,
|
|
28768
|
-
newValue
|
|
28769
|
-
});
|
|
28770
|
-
}
|
|
28771
|
-
function setAtIndex(args) {
|
|
28772
|
-
const {
|
|
28773
|
-
array = [],
|
|
28774
|
-
index,
|
|
28775
|
-
newValue
|
|
28776
|
-
} = args;
|
|
28777
|
-
const copy = [...array];
|
|
28778
|
-
copy[index] = newValue;
|
|
28779
|
-
// restoring readonly to array
|
|
28780
|
-
return copy;
|
|
28781
|
-
}
|
|
28782
|
-
|
|
28783
|
-
// same as pointsFromNormalized in interactive-graph.tsx
|
|
28784
|
-
const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coords =>
|
|
28785
|
-
// maps over [x, y]
|
|
28786
|
-
coords.map((coord, i) => {
|
|
28787
|
-
const axisRange = range[i];
|
|
28788
|
-
if (noSnap) {
|
|
28789
|
-
return axisRange[0] + (axisRange[1] - axisRange[0]) * coord;
|
|
28790
|
-
}
|
|
28791
|
-
const axisStep = step[i];
|
|
28792
|
-
const nSteps = Math.floor((axisRange[1] - axisRange[0]) / axisStep);
|
|
28793
|
-
const tick = Math.round(coord * nSteps);
|
|
28794
|
-
return axisRange[0] + axisStep * tick;
|
|
28795
|
-
}));
|
|
28796
|
-
|
|
28797
|
-
// same as normalizeCoords in interactive-graph.tsx
|
|
28798
|
-
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
28799
|
-
const extent = ranges[i][1] - ranges[i][0];
|
|
28800
|
-
return (coord + ranges[i][1]) / extent;
|
|
28801
|
-
}));
|
|
28802
|
-
|
|
28803
|
-
function initializeGraphState(params) {
|
|
28804
|
-
const {
|
|
28805
|
-
graph,
|
|
28806
|
-
step,
|
|
28807
|
-
snapStep,
|
|
28808
|
-
range
|
|
28809
|
-
} = params;
|
|
28810
|
-
switch (graph.type) {
|
|
28811
|
-
case "segment":
|
|
28812
|
-
return {
|
|
28813
|
-
type: "segment",
|
|
28814
|
-
hasBeenInteractedWith: false,
|
|
28815
|
-
range,
|
|
28816
|
-
snapStep,
|
|
28817
|
-
coords: getDefaultSegments({
|
|
28818
|
-
graph,
|
|
28819
|
-
step,
|
|
28820
|
-
range
|
|
28821
|
-
})
|
|
28822
|
-
};
|
|
28823
|
-
case "linear":
|
|
28824
|
-
case "linear-system":
|
|
28825
|
-
case "ray":
|
|
28826
|
-
return {
|
|
28827
|
-
type: graph.type,
|
|
28828
|
-
hasBeenInteractedWith: false,
|
|
28829
|
-
range,
|
|
28830
|
-
snapStep,
|
|
28831
|
-
// Linear and ray graphs have a single tuple of points, while a
|
|
28832
|
-
// linear system has two tuples of points.
|
|
28833
|
-
coords: getLineCoords(graph, range, step)
|
|
28834
|
-
};
|
|
28835
|
-
}
|
|
28836
|
-
throw new Error("Mafs not yet implemented for graph type: " + graph.type);
|
|
28837
|
-
}
|
|
28838
|
-
const getDefaultSegments = props => {
|
|
28839
|
-
const ys = n => {
|
|
28840
|
-
switch (n) {
|
|
28841
|
-
case 2:
|
|
28842
|
-
return [5, -5];
|
|
28843
|
-
case 3:
|
|
28844
|
-
return [5, 0, -5];
|
|
28845
|
-
case 4:
|
|
28846
|
-
return [6, 2, -2, -6];
|
|
28847
|
-
case 5:
|
|
28848
|
-
return [6, 3, 0, -3, -6];
|
|
28849
|
-
case 6:
|
|
28850
|
-
return [5, 3, 1, -1, -3, -5];
|
|
28851
|
-
default:
|
|
28852
|
-
return [5];
|
|
28853
|
-
}
|
|
28854
|
-
};
|
|
28855
|
-
return ys(props.graph.numSegments).map(y => {
|
|
28856
|
-
let endpoints = [[-5, y], [5, y]];
|
|
28857
|
-
endpoints = normalizeCoords(endpoints, props.range);
|
|
28858
|
-
endpoints = normalizePoints(props.range, props.step, endpoints);
|
|
28859
|
-
return endpoints;
|
|
28860
|
-
});
|
|
28861
|
-
};
|
|
28862
|
-
|
|
28863
|
-
// TS v4 doesn't narrow return types, while v5 does.
|
|
28864
|
-
// Instead of updating to v5, using generic type to relate input and output types.
|
|
28865
|
-
function getGradableGraph(state, initialGraph) {
|
|
28866
|
-
if (!state.hasBeenInteractedWith) {
|
|
28867
|
-
return initialGraph;
|
|
28868
|
-
}
|
|
28869
|
-
switch (true) {
|
|
28870
|
-
// coords: Array of CollinearTuple
|
|
28871
|
-
case state.type === "linear-system" && initialGraph.type === "linear-system":
|
|
28872
|
-
case state.type === "segment" && initialGraph.type === "segment":
|
|
28873
|
-
return {
|
|
28874
|
-
...initialGraph,
|
|
28875
|
-
coords: state.coords
|
|
28876
|
-
};
|
|
28877
|
-
// coords: CollinearTuple
|
|
28878
|
-
case state.type === "linear" && initialGraph.type === "linear":
|
|
28879
|
-
case state.type === "ray" && initialGraph.type === "ray":
|
|
28880
|
-
return {
|
|
28881
|
-
...initialGraph,
|
|
28882
|
-
coords: state.coords?.[0]
|
|
28883
|
-
};
|
|
28884
|
-
}
|
|
28885
|
-
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
28886
|
-
}
|
|
28887
|
-
const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
|
|
28888
|
-
const getLineCoords = (graph, range, step) =>
|
|
28889
|
-
// Return two lines for a linear system, one for a ray or linear
|
|
28890
|
-
graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])];
|
|
28891
|
-
|
|
28892
28752
|
// Displays a stylized open eye: lint warnings are visible
|
|
28893
28753
|
const VisibleIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
|
|
28894
28754
|
width: "24",
|
|
@@ -29478,107 +29338,486 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
29478
29338
|
flexDirection: "row",
|
|
29479
29339
|
alignItems: "center"
|
|
29480
29340
|
}
|
|
29481
|
-
});
|
|
29482
|
-
|
|
29483
|
-
const truth = () => true;
|
|
29341
|
+
});
|
|
29342
|
+
|
|
29343
|
+
const truth = () => true;
|
|
29344
|
+
|
|
29345
|
+
/* A minor abstraction on top of NumberInput for ranges
|
|
29346
|
+
*
|
|
29347
|
+
*/
|
|
29348
|
+
class RangeInput extends React__namespace.Component {
|
|
29349
|
+
static propTypes = {
|
|
29350
|
+
value: PropTypes__default["default"].array.isRequired,
|
|
29351
|
+
onChange: PropTypes__default["default"].func.isRequired,
|
|
29352
|
+
placeholder: PropTypes__default["default"].array,
|
|
29353
|
+
checkValidity: PropTypes__default["default"].func
|
|
29354
|
+
};
|
|
29355
|
+
static defaultProps = {
|
|
29356
|
+
placeholder: [null, null]
|
|
29357
|
+
};
|
|
29358
|
+
render() {
|
|
29359
|
+
const value = this.props.value;
|
|
29360
|
+
const checkValidity = this.props.checkValidity || truth;
|
|
29361
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
29362
|
+
className: "range-input"
|
|
29363
|
+
}, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
|
|
29364
|
+
value: value[0],
|
|
29365
|
+
checkValidity: val => checkValidity([val, value[1]])
|
|
29366
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
29367
|
+
,
|
|
29368
|
+
onChange: this.onChange.bind(this, 0),
|
|
29369
|
+
placeholder: this.props.placeholder[0]
|
|
29370
|
+
})), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
|
|
29371
|
+
value: value[1],
|
|
29372
|
+
checkValidity: val => checkValidity([value[0], val])
|
|
29373
|
+
// eslint-disable-next-line react/jsx-no-bind
|
|
29374
|
+
,
|
|
29375
|
+
onChange: this.onChange.bind(this, 1),
|
|
29376
|
+
placeholder: this.props.placeholder[1]
|
|
29377
|
+
})));
|
|
29378
|
+
}
|
|
29379
|
+
onChange = (i, newVal) => {
|
|
29380
|
+
const value = this.props.value;
|
|
29381
|
+
if (i === 0) {
|
|
29382
|
+
this.props.onChange([newVal, value[1]]);
|
|
29383
|
+
} else {
|
|
29384
|
+
this.props.onChange([value[0], newVal]);
|
|
29385
|
+
}
|
|
29386
|
+
};
|
|
29387
|
+
}
|
|
29388
|
+
|
|
29389
|
+
var components = /*#__PURE__*/Object.freeze({
|
|
29390
|
+
__proto__: null,
|
|
29391
|
+
ButtonGroup: ButtonGroup,
|
|
29392
|
+
Graph: Graph,
|
|
29393
|
+
Graphie: Graphie,
|
|
29394
|
+
HUD: HUD,
|
|
29395
|
+
Icon: Icon,
|
|
29396
|
+
InfoTip: InfoTip,
|
|
29397
|
+
InlineIcon: InlineIcon,
|
|
29398
|
+
MathInput: MathInput,
|
|
29399
|
+
MultiButtonGroup: MultiButtonGroup,
|
|
29400
|
+
NumberInput: NumberInput,
|
|
29401
|
+
PropCheckBox: PropCheckBox,
|
|
29402
|
+
RangeInput: RangeInput,
|
|
29403
|
+
SvgImage: SvgImage,
|
|
29404
|
+
TextInput: TextInput,
|
|
29405
|
+
TextListEditor: TextListEditor
|
|
29406
|
+
});
|
|
29407
|
+
|
|
29408
|
+
/**
|
|
29409
|
+
* If a graphie URL is provided in `backgroundImage`, will return the rendered graphie background.
|
|
29410
|
+
* Otherwise, returns `null`.
|
|
29411
|
+
*/
|
|
29412
|
+
const getLegacyGrid = (box, backgroundImage) => {
|
|
29413
|
+
const {
|
|
29414
|
+
url,
|
|
29415
|
+
width,
|
|
29416
|
+
height
|
|
29417
|
+
} = backgroundImage ?? {};
|
|
29418
|
+
if (url && typeof url === "string") {
|
|
29419
|
+
const scale = box[0] / interactiveSizes$1.defaultBoxSize;
|
|
29420
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
29421
|
+
style: {
|
|
29422
|
+
position: "absolute",
|
|
29423
|
+
bottom: 0,
|
|
29424
|
+
left: 0
|
|
29425
|
+
}
|
|
29426
|
+
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
|
|
29427
|
+
let {
|
|
29428
|
+
setAssetStatus
|
|
29429
|
+
} = _ref;
|
|
29430
|
+
return /*#__PURE__*/React__namespace.createElement(SvgImage, {
|
|
29431
|
+
src: url,
|
|
29432
|
+
width: width,
|
|
29433
|
+
height: height,
|
|
29434
|
+
scale: scale,
|
|
29435
|
+
responsive: false,
|
|
29436
|
+
setAssetStatus: setAssetStatus,
|
|
29437
|
+
alt: ""
|
|
29438
|
+
});
|
|
29439
|
+
}));
|
|
29440
|
+
}
|
|
29441
|
+
return null;
|
|
29442
|
+
};
|
|
29443
|
+
|
|
29444
|
+
/** Determine if coords is type CollinearTuple[] */
|
|
29445
|
+
const isCollinearTuples = coords => Array.isArray(coords[0][0]);
|
|
29446
|
+
|
|
29447
|
+
// Generic type makes returned state match input state
|
|
29448
|
+
function interactiveGraphReducer(state, action) {
|
|
29449
|
+
const {
|
|
29450
|
+
snapStep,
|
|
29451
|
+
range
|
|
29452
|
+
} = state;
|
|
29453
|
+
if (!state.coords) {
|
|
29454
|
+
throw new Error("Graph state must have been initialized with coords");
|
|
29455
|
+
}
|
|
29456
|
+
switch (action.type) {
|
|
29457
|
+
case MOVE_CONTROL_POINT:
|
|
29458
|
+
{
|
|
29459
|
+
const newCoords = action.itemIndex !== undefined && state.coords && isCollinearTuples(state.coords) ? updateAtIndex({
|
|
29460
|
+
array: state.coords,
|
|
29461
|
+
index: action.itemIndex,
|
|
29462
|
+
update: tuple => setAtIndex({
|
|
29463
|
+
array: tuple,
|
|
29464
|
+
index: action.pointIndex,
|
|
29465
|
+
newValue: snap({
|
|
29466
|
+
snapStep,
|
|
29467
|
+
point: bound$1({
|
|
29468
|
+
snapStep,
|
|
29469
|
+
range,
|
|
29470
|
+
point: action.destination
|
|
29471
|
+
})
|
|
29472
|
+
})
|
|
29473
|
+
})
|
|
29474
|
+
}) : setAtIndex({
|
|
29475
|
+
array: state.coords,
|
|
29476
|
+
index: action.pointIndex,
|
|
29477
|
+
newValue: snap({
|
|
29478
|
+
snapStep,
|
|
29479
|
+
point: bound$1({
|
|
29480
|
+
snapStep,
|
|
29481
|
+
range,
|
|
29482
|
+
point: action.destination
|
|
29483
|
+
})
|
|
29484
|
+
})
|
|
29485
|
+
});
|
|
29486
|
+
|
|
29487
|
+
// Cannot type narrow both conditions within function parameters,
|
|
29488
|
+
// so this may seem redundant, but it's necessary for type safety.
|
|
29489
|
+
const coordsToCheck = isCollinearTuples(newCoords) && action.itemIndex !== undefined ? newCoords[action.itemIndex] : newCoords;
|
|
29490
|
+
if (!isCollinearTuples(coordsToCheck) && coordsOverlap(coordsToCheck)) {
|
|
29491
|
+
return state;
|
|
29492
|
+
}
|
|
29493
|
+
return {
|
|
29494
|
+
...state,
|
|
29495
|
+
hasBeenInteractedWith: true,
|
|
29496
|
+
coords: newCoords
|
|
29497
|
+
};
|
|
29498
|
+
}
|
|
29499
|
+
case MOVE_LINE:
|
|
29500
|
+
{
|
|
29501
|
+
if (!isCollinearTuples(state.coords)) {
|
|
29502
|
+
throw new Error("Cannot call this action unless coords is array of CollinearTuple.");
|
|
29503
|
+
}
|
|
29504
|
+
if (action.itemIndex === undefined) {
|
|
29505
|
+
throw new Error("Please provide index of line to move");
|
|
29506
|
+
}
|
|
29507
|
+
const currentLine = state.coords?.[action.itemIndex];
|
|
29508
|
+
if (!currentLine) {
|
|
29509
|
+
throw new Error("No line to move");
|
|
29510
|
+
}
|
|
29511
|
+
const change = getChange(currentLine, action.delta, {
|
|
29512
|
+
snapStep,
|
|
29513
|
+
range
|
|
29514
|
+
});
|
|
29515
|
+
const newLine = [snap({
|
|
29516
|
+
snapStep,
|
|
29517
|
+
point: mafs.vec.add(currentLine[0], change)
|
|
29518
|
+
}), snap({
|
|
29519
|
+
snapStep,
|
|
29520
|
+
point: mafs.vec.add(currentLine[1], change)
|
|
29521
|
+
})];
|
|
29522
|
+
const newCoords = setAtIndex({
|
|
29523
|
+
array: state.coords,
|
|
29524
|
+
index: action.itemIndex,
|
|
29525
|
+
newValue: newLine
|
|
29526
|
+
});
|
|
29527
|
+
return {
|
|
29528
|
+
...state,
|
|
29529
|
+
hasBeenInteractedWith: true,
|
|
29530
|
+
coords: newCoords
|
|
29531
|
+
};
|
|
29532
|
+
}
|
|
29533
|
+
case MOVE_ALL:
|
|
29534
|
+
{
|
|
29535
|
+
if (isCollinearTuples(state.coords)) {
|
|
29536
|
+
throw new Error("Cannot call this action unless coords is array of vectors.");
|
|
29537
|
+
}
|
|
29538
|
+
const change = getChange(state.coords, action.delta, {
|
|
29539
|
+
snapStep,
|
|
29540
|
+
range
|
|
29541
|
+
});
|
|
29542
|
+
const newCoords = state.coords.map(point => snap({
|
|
29543
|
+
snapStep,
|
|
29544
|
+
point: mafs.vec.add(point, change)
|
|
29545
|
+
}));
|
|
29546
|
+
return {
|
|
29547
|
+
...state,
|
|
29548
|
+
hasBeenInteractedWith: true,
|
|
29549
|
+
coords: newCoords
|
|
29550
|
+
};
|
|
29551
|
+
}
|
|
29552
|
+
default:
|
|
29553
|
+
throw new wonderStuffCore.UnreachableCaseError(action);
|
|
29554
|
+
}
|
|
29555
|
+
}
|
|
29556
|
+
const getChange = (coords, delta, constraintOpts) => {
|
|
29557
|
+
const [deltaX, deltaY] = delta;
|
|
29558
|
+
const maxMoves = coords.map(point => maxMove({
|
|
29559
|
+
...constraintOpts,
|
|
29560
|
+
point
|
|
29561
|
+
}));
|
|
29562
|
+
const minMoves = coords.map(point => minMove({
|
|
29563
|
+
...constraintOpts,
|
|
29564
|
+
point
|
|
29565
|
+
}));
|
|
29566
|
+
const maxXMove = Math.min(...maxMoves.map(move => move[0]));
|
|
29567
|
+
const maxYMove = Math.min(...maxMoves.map(move => move[1]));
|
|
29568
|
+
const minXMove = Math.max(...minMoves.map(move => move[0]));
|
|
29569
|
+
const minYMove = Math.max(...minMoves.map(move => move[1]));
|
|
29570
|
+
const dx = clamp(deltaX, minXMove, maxXMove);
|
|
29571
|
+
const dy = clamp(deltaY, minYMove, maxYMove);
|
|
29572
|
+
return [dx, dy];
|
|
29573
|
+
};
|
|
29574
|
+
function snap(_ref) {
|
|
29575
|
+
let {
|
|
29576
|
+
snapStep,
|
|
29577
|
+
point
|
|
29578
|
+
} = _ref;
|
|
29579
|
+
const [requestedX, requestedY] = point;
|
|
29580
|
+
const [snapX, snapY] = snapStep;
|
|
29581
|
+
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29582
|
+
}
|
|
29583
|
+
|
|
29584
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
29585
|
+
// bounds given in `state`.
|
|
29586
|
+
function bound$1(_ref2) {
|
|
29587
|
+
let {
|
|
29588
|
+
snapStep,
|
|
29589
|
+
range,
|
|
29590
|
+
point
|
|
29591
|
+
} = _ref2;
|
|
29592
|
+
const [requestedX, requestedY] = point;
|
|
29593
|
+
const [snapX, snapY] = snapStep;
|
|
29594
|
+
const [[minX, maxX], [minY, maxY]] = range;
|
|
29595
|
+
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
29596
|
+
}
|
|
29597
|
+
|
|
29598
|
+
// Returns the vector from the given point to the top-right corner of the graph
|
|
29599
|
+
function maxMove(_ref3) {
|
|
29600
|
+
let {
|
|
29601
|
+
snapStep,
|
|
29602
|
+
range,
|
|
29603
|
+
point
|
|
29604
|
+
} = _ref3;
|
|
29605
|
+
const topRight = bound$1({
|
|
29606
|
+
snapStep,
|
|
29607
|
+
range,
|
|
29608
|
+
point: [Infinity, Infinity]
|
|
29609
|
+
});
|
|
29610
|
+
return mafs.vec.sub(topRight, point);
|
|
29611
|
+
}
|
|
29612
|
+
|
|
29613
|
+
// Returns the vector from the given point to the bottom-left corner of the
|
|
29614
|
+
// graph
|
|
29615
|
+
function minMove(_ref4) {
|
|
29616
|
+
let {
|
|
29617
|
+
snapStep,
|
|
29618
|
+
range,
|
|
29619
|
+
point
|
|
29620
|
+
} = _ref4;
|
|
29621
|
+
const bottomLeft = bound$1({
|
|
29622
|
+
snapStep,
|
|
29623
|
+
range,
|
|
29624
|
+
point: [-Infinity, -Infinity]
|
|
29625
|
+
});
|
|
29626
|
+
return mafs.vec.sub(bottomLeft, point);
|
|
29627
|
+
}
|
|
29628
|
+
function clamp(value, min, max) {
|
|
29629
|
+
if (value < min) {
|
|
29630
|
+
return min;
|
|
29631
|
+
}
|
|
29632
|
+
if (value > max) {
|
|
29633
|
+
return max;
|
|
29634
|
+
}
|
|
29635
|
+
return value;
|
|
29636
|
+
}
|
|
29637
|
+
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && kmath.vector.equal(coord, c)));
|
|
29638
|
+
function updateAtIndex(args) {
|
|
29639
|
+
const {
|
|
29640
|
+
array = [],
|
|
29641
|
+
index,
|
|
29642
|
+
update
|
|
29643
|
+
} = args;
|
|
29644
|
+
const newValue = update(array[index]);
|
|
29645
|
+
return setAtIndex({
|
|
29646
|
+
array,
|
|
29647
|
+
index,
|
|
29648
|
+
newValue
|
|
29649
|
+
});
|
|
29650
|
+
}
|
|
29651
|
+
function setAtIndex(args) {
|
|
29652
|
+
const {
|
|
29653
|
+
array = [],
|
|
29654
|
+
index,
|
|
29655
|
+
newValue
|
|
29656
|
+
} = args;
|
|
29657
|
+
const copy = [...array];
|
|
29658
|
+
copy[index] = newValue;
|
|
29659
|
+
// restoring readonly to array
|
|
29660
|
+
return copy;
|
|
29661
|
+
}
|
|
29662
|
+
|
|
29663
|
+
function initializeGraphState(params) {
|
|
29664
|
+
const {
|
|
29665
|
+
graph,
|
|
29666
|
+
step,
|
|
29667
|
+
snapStep,
|
|
29668
|
+
range
|
|
29669
|
+
} = params;
|
|
29670
|
+
switch (graph.type) {
|
|
29671
|
+
case "segment":
|
|
29672
|
+
return {
|
|
29673
|
+
type: "segment",
|
|
29674
|
+
hasBeenInteractedWith: false,
|
|
29675
|
+
range,
|
|
29676
|
+
snapStep,
|
|
29677
|
+
coords: getDefaultSegments({
|
|
29678
|
+
graph,
|
|
29679
|
+
step,
|
|
29680
|
+
range
|
|
29681
|
+
})
|
|
29682
|
+
};
|
|
29683
|
+
case "linear":
|
|
29684
|
+
case "linear-system":
|
|
29685
|
+
case "ray":
|
|
29686
|
+
return {
|
|
29687
|
+
type: graph.type,
|
|
29688
|
+
hasBeenInteractedWith: false,
|
|
29689
|
+
range,
|
|
29690
|
+
snapStep,
|
|
29691
|
+
// Linear and ray graphs have a single tuple of points, while a
|
|
29692
|
+
// linear system has two tuples of points.
|
|
29693
|
+
coords: getLineCoords({
|
|
29694
|
+
graph,
|
|
29695
|
+
range,
|
|
29696
|
+
step
|
|
29697
|
+
})
|
|
29698
|
+
};
|
|
29699
|
+
case "polygon":
|
|
29700
|
+
return {
|
|
29701
|
+
type: "polygon",
|
|
29702
|
+
hasBeenInteractedWith: false,
|
|
29703
|
+
range,
|
|
29704
|
+
snapStep,
|
|
29705
|
+
coords: getPolygonCoords({
|
|
29706
|
+
graph,
|
|
29707
|
+
range,
|
|
29708
|
+
step
|
|
29709
|
+
})
|
|
29710
|
+
};
|
|
29711
|
+
case "angle":
|
|
29712
|
+
case "point":
|
|
29713
|
+
case "circle":
|
|
29714
|
+
case "sinusoid":
|
|
29715
|
+
case "quadratic":
|
|
29716
|
+
throw new Error("Mafs not yet implemented for graph type: " + graph.type);
|
|
29717
|
+
default:
|
|
29718
|
+
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
29719
|
+
}
|
|
29720
|
+
}
|
|
29484
29721
|
|
|
29485
|
-
|
|
29486
|
-
|
|
29487
|
-
|
|
29488
|
-
|
|
29489
|
-
|
|
29490
|
-
value: PropTypes__default["default"].array.isRequired,
|
|
29491
|
-
onChange: PropTypes__default["default"].func.isRequired,
|
|
29492
|
-
placeholder: PropTypes__default["default"].array,
|
|
29493
|
-
checkValidity: PropTypes__default["default"].func
|
|
29494
|
-
};
|
|
29495
|
-
static defaultProps = {
|
|
29496
|
-
placeholder: [null, null]
|
|
29497
|
-
};
|
|
29498
|
-
render() {
|
|
29499
|
-
const value = this.props.value;
|
|
29500
|
-
const checkValidity = this.props.checkValidity || truth;
|
|
29501
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
29502
|
-
className: "range-input"
|
|
29503
|
-
}, /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
|
|
29504
|
-
value: value[0],
|
|
29505
|
-
checkValidity: val => checkValidity([val, value[1]])
|
|
29506
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
29507
|
-
,
|
|
29508
|
-
onChange: this.onChange.bind(this, 0),
|
|
29509
|
-
placeholder: this.props.placeholder[0]
|
|
29510
|
-
})), /*#__PURE__*/React__namespace.createElement(NumberInput, _extends({}, this.props, {
|
|
29511
|
-
value: value[1],
|
|
29512
|
-
checkValidity: val => checkValidity([value[0], val])
|
|
29513
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
29514
|
-
,
|
|
29515
|
-
onChange: this.onChange.bind(this, 1),
|
|
29516
|
-
placeholder: this.props.placeholder[1]
|
|
29517
|
-
})));
|
|
29722
|
+
// TS v4 doesn't narrow return types, while v5 does.
|
|
29723
|
+
// Instead of updating to v5, using generic type to relate input and output types.
|
|
29724
|
+
function getGradableGraph(state, initialGraph) {
|
|
29725
|
+
if (!state.hasBeenInteractedWith) {
|
|
29726
|
+
return initialGraph;
|
|
29518
29727
|
}
|
|
29519
|
-
|
|
29520
|
-
|
|
29521
|
-
|
|
29522
|
-
|
|
29523
|
-
|
|
29524
|
-
|
|
29728
|
+
switch (true) {
|
|
29729
|
+
// coords: Array of CollinearTuple
|
|
29730
|
+
case state.type === "linear-system" && initialGraph.type === "linear-system":
|
|
29731
|
+
case state.type === "segment" && initialGraph.type === "segment":
|
|
29732
|
+
return {
|
|
29733
|
+
...initialGraph,
|
|
29734
|
+
coords: state.coords
|
|
29735
|
+
};
|
|
29736
|
+
// coords: CollinearTuple
|
|
29737
|
+
case state.type === "linear" && initialGraph.type === "linear":
|
|
29738
|
+
case state.type === "ray" && initialGraph.type === "ray":
|
|
29739
|
+
return {
|
|
29740
|
+
...initialGraph,
|
|
29741
|
+
coords: state.coords?.[0]
|
|
29742
|
+
};
|
|
29743
|
+
case state.type === "polygon" && initialGraph.type === "polygon":
|
|
29744
|
+
return {
|
|
29745
|
+
...initialGraph,
|
|
29746
|
+
coords: state.coords
|
|
29747
|
+
};
|
|
29748
|
+
default:
|
|
29749
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29750
|
+
}
|
|
29751
|
+
}
|
|
29752
|
+
const getDefaultSegments = _ref => {
|
|
29753
|
+
let {
|
|
29754
|
+
graph,
|
|
29755
|
+
range,
|
|
29756
|
+
step
|
|
29757
|
+
} = _ref;
|
|
29758
|
+
const ys = n => {
|
|
29759
|
+
switch (n) {
|
|
29760
|
+
case 2:
|
|
29761
|
+
return [5, -5];
|
|
29762
|
+
case 3:
|
|
29763
|
+
return [5, 0, -5];
|
|
29764
|
+
case 4:
|
|
29765
|
+
return [6, 2, -2, -6];
|
|
29766
|
+
case 5:
|
|
29767
|
+
return [6, 3, 0, -3, -6];
|
|
29768
|
+
case 6:
|
|
29769
|
+
return [5, 3, 1, -1, -3, -5];
|
|
29770
|
+
default:
|
|
29771
|
+
return [5];
|
|
29525
29772
|
}
|
|
29526
29773
|
};
|
|
29527
|
-
|
|
29774
|
+
return ys(graph.numSegments).map(y => {
|
|
29775
|
+
let endpoints = [[-5, y], [5, y]];
|
|
29776
|
+
endpoints = normalizeCoords(endpoints, range);
|
|
29777
|
+
endpoints = normalizePoints(range, step, endpoints);
|
|
29778
|
+
return endpoints;
|
|
29779
|
+
});
|
|
29780
|
+
};
|
|
29781
|
+
const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
|
|
29782
|
+
const getLineCoords = _ref2 => {
|
|
29783
|
+
let {
|
|
29784
|
+
graph,
|
|
29785
|
+
range,
|
|
29786
|
+
step
|
|
29787
|
+
} = _ref2;
|
|
29788
|
+
return (
|
|
29789
|
+
// Return two lines for a linear system, one for a ray or linear
|
|
29790
|
+
graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
|
|
29791
|
+
);
|
|
29792
|
+
};
|
|
29793
|
+
const getPolygonCoords = _ref3 => {
|
|
29794
|
+
let {
|
|
29795
|
+
graph,
|
|
29796
|
+
range,
|
|
29797
|
+
step
|
|
29798
|
+
} = _ref3;
|
|
29799
|
+
let coords = graph.coords;
|
|
29800
|
+
if (coords) {
|
|
29801
|
+
return coords;
|
|
29802
|
+
}
|
|
29803
|
+
const n = graph.numSides || 3;
|
|
29804
|
+
if (n === "unlimited") {
|
|
29805
|
+
coords = [];
|
|
29806
|
+
} else {
|
|
29807
|
+
const angle = 2 * Math.PI / n;
|
|
29808
|
+
const offset = (1 / n - 1 / 2) * Math.PI;
|
|
29528
29809
|
|
|
29529
|
-
|
|
29530
|
-
|
|
29531
|
-
|
|
29532
|
-
Graph: Graph,
|
|
29533
|
-
Graphie: Graphie,
|
|
29534
|
-
HUD: HUD,
|
|
29535
|
-
Icon: Icon,
|
|
29536
|
-
InfoTip: InfoTip,
|
|
29537
|
-
InlineIcon: InlineIcon,
|
|
29538
|
-
MathInput: MathInput,
|
|
29539
|
-
MultiButtonGroup: MultiButtonGroup,
|
|
29540
|
-
NumberInput: NumberInput,
|
|
29541
|
-
PropCheckBox: PropCheckBox,
|
|
29542
|
-
RangeInput: RangeInput,
|
|
29543
|
-
SvgImage: SvgImage,
|
|
29544
|
-
TextInput: TextInput,
|
|
29545
|
-
TextListEditor: TextListEditor
|
|
29546
|
-
});
|
|
29810
|
+
// TODO(alex): Generalize this to more than just triangles so that
|
|
29811
|
+
// all polygons have whole number side lengths if snapping to sides
|
|
29812
|
+
const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
|
|
29547
29813
|
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
* Otherwise, returns `null`.
|
|
29551
|
-
*/
|
|
29552
|
-
const getLegacyGrid = (box, backgroundImage) => {
|
|
29553
|
-
const {
|
|
29554
|
-
url,
|
|
29555
|
-
width,
|
|
29556
|
-
height
|
|
29557
|
-
} = backgroundImage ?? {};
|
|
29558
|
-
if (url && typeof url === "string") {
|
|
29559
|
-
const scale = box[0] / interactiveSizes$1.defaultBoxSize;
|
|
29560
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
29561
|
-
style: {
|
|
29562
|
-
position: "absolute",
|
|
29563
|
-
bottom: 0,
|
|
29564
|
-
left: 0
|
|
29565
|
-
}
|
|
29566
|
-
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref => {
|
|
29567
|
-
let {
|
|
29568
|
-
setAssetStatus
|
|
29569
|
-
} = _ref;
|
|
29570
|
-
return /*#__PURE__*/React__namespace.createElement(SvgImage, {
|
|
29571
|
-
src: url,
|
|
29572
|
-
width: width,
|
|
29573
|
-
height: height,
|
|
29574
|
-
scale: scale,
|
|
29575
|
-
responsive: false,
|
|
29576
|
-
setAssetStatus: setAssetStatus,
|
|
29577
|
-
alt: ""
|
|
29578
|
-
});
|
|
29579
|
-
}));
|
|
29814
|
+
// Generate coords of a regular polygon with n sides
|
|
29815
|
+
coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
|
|
29580
29816
|
}
|
|
29581
|
-
|
|
29817
|
+
coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
|
|
29818
|
+
const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
|
|
29819
|
+
coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
|
|
29820
|
+
return coords;
|
|
29582
29821
|
};
|
|
29583
29822
|
|
|
29584
29823
|
const renderGraph = props => {
|
|
@@ -29606,6 +29845,11 @@ const renderGraph = props => {
|
|
|
29606
29845
|
graphState: state,
|
|
29607
29846
|
dispatch: dispatch
|
|
29608
29847
|
});
|
|
29848
|
+
case "polygon":
|
|
29849
|
+
return /*#__PURE__*/React__namespace.createElement(PolygonGraph, {
|
|
29850
|
+
graphState: state,
|
|
29851
|
+
dispatch: dispatch
|
|
29852
|
+
});
|
|
29609
29853
|
default:
|
|
29610
29854
|
return new wonderStuffCore.UnreachableCaseError(type);
|
|
29611
29855
|
}
|
|
@@ -29619,8 +29863,8 @@ const MafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
|
29619
29863
|
}));
|
|
29620
29864
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
29621
29865
|
style: {
|
|
29622
|
-
width
|
|
29623
|
-
height
|
|
29866
|
+
width,
|
|
29867
|
+
height,
|
|
29624
29868
|
position: "relative"
|
|
29625
29869
|
}
|
|
29626
29870
|
}, legacyGrid, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
@@ -29703,7 +29947,7 @@ const makeInvalidTypeError = (functionName, graphType) => {
|
|
|
29703
29947
|
|
|
29704
29948
|
// There's no transform function in exports
|
|
29705
29949
|
|
|
29706
|
-
class
|
|
29950
|
+
class LegacyInteractiveGraph extends React__namespace.Component {
|
|
29707
29951
|
// @ts-expect-error - TS2564 - Property 'points' has no initializer and is not definitely assigned in the constructor.
|
|
29708
29952
|
|
|
29709
29953
|
// @ts-expect-error - TS2564 - Property 'shouldResetGraphie' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -29723,7 +29967,6 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
29723
29967
|
type: "linear"
|
|
29724
29968
|
}
|
|
29725
29969
|
};
|
|
29726
|
-
mafsRef = /*#__PURE__*/React__namespace.createRef();
|
|
29727
29970
|
state = {
|
|
29728
29971
|
shouldShowInstructions: _getShouldShowInstructions(this.props)
|
|
29729
29972
|
};
|
|
@@ -30834,26 +31077,13 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
30834
31077
|
graph: graph
|
|
30835
31078
|
});
|
|
30836
31079
|
};
|
|
30837
|
-
getUserInput = () =>
|
|
31080
|
+
getUserInput = () => InteractiveGraph.getUserInputFromProps(this.props);
|
|
30838
31081
|
simpleValidate = rubric => InteractiveGraph.validate(this.getUserInput(), rubric, this);
|
|
30839
31082
|
focus = $__default["default"].noop;
|
|
30840
|
-
|
|
30841
|
-
// const segment: boolean = useFeatureIsOn("mafs-segment-graph");
|
|
30842
|
-
// <Renderer apiOptions={{flags: {mafs: {segment}}}}
|
|
30843
31083
|
render() {
|
|
30844
31084
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
30845
31085
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
30846
31086
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
30847
|
-
|
|
30848
|
-
// Mafs shim
|
|
30849
|
-
if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
|
|
30850
|
-
return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, this.props, {
|
|
30851
|
-
ref: this.mafsRef,
|
|
30852
|
-
gridStep: gridStep,
|
|
30853
|
-
snapStep: snapStep,
|
|
30854
|
-
box: box
|
|
30855
|
-
}));
|
|
30856
|
-
}
|
|
30857
31087
|
let instructions;
|
|
30858
31088
|
// isClickToAddPoints() only applies to points and polygons
|
|
30859
31089
|
if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
|
|
@@ -30899,6 +31129,52 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
30899
31129
|
isMobile: isMobile
|
|
30900
31130
|
}));
|
|
30901
31131
|
}
|
|
31132
|
+
}
|
|
31133
|
+
class InteractiveGraph extends React__namespace.Component {
|
|
31134
|
+
legacyGraphRef = /*#__PURE__*/React__namespace.createRef();
|
|
31135
|
+
mafsRef = /*#__PURE__*/React__namespace.createRef();
|
|
31136
|
+
static defaultProps = {
|
|
31137
|
+
labels: ["x", "y"],
|
|
31138
|
+
range: [[-10, 10], [-10, 10]],
|
|
31139
|
+
step: [1, 1],
|
|
31140
|
+
backgroundImage: defaultBackgroundImage,
|
|
31141
|
+
markings: "graph",
|
|
31142
|
+
showTooltips: false,
|
|
31143
|
+
showProtractor: false,
|
|
31144
|
+
showRuler: false,
|
|
31145
|
+
rulerLabel: "",
|
|
31146
|
+
rulerTicks: 10,
|
|
31147
|
+
graph: {
|
|
31148
|
+
type: "linear"
|
|
31149
|
+
}
|
|
31150
|
+
};
|
|
31151
|
+
getUserInput = () => {
|
|
31152
|
+
if (this.mafsRef.current) {
|
|
31153
|
+
return this.mafsRef.current.getUserInput?.();
|
|
31154
|
+
}
|
|
31155
|
+
if (this.legacyGraphRef.current) {
|
|
31156
|
+
return this.legacyGraphRef.current.getUserInput();
|
|
31157
|
+
}
|
|
31158
|
+
throw new PerseusError("Cannot getUserInput from a graph that has never rendered", Errors.NotAllowed);
|
|
31159
|
+
};
|
|
31160
|
+
simpleValidate = rubric => InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
|
|
31161
|
+
render() {
|
|
31162
|
+
// Mafs shim
|
|
31163
|
+
if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
|
|
31164
|
+
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
31165
|
+
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
31166
|
+
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
31167
|
+
return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, this.props, {
|
|
31168
|
+
ref: this.mafsRef,
|
|
31169
|
+
gridStep: gridStep,
|
|
31170
|
+
snapStep: snapStep,
|
|
31171
|
+
box: box
|
|
31172
|
+
}));
|
|
31173
|
+
}
|
|
31174
|
+
return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
|
|
31175
|
+
ref: this.legacyGraphRef
|
|
31176
|
+
}, this.props));
|
|
31177
|
+
}
|
|
30902
31178
|
static getQuadraticCoefficients(coords) {
|
|
30903
31179
|
const p1 = coords[0];
|
|
30904
31180
|
const p2 = coords[1];
|
|
@@ -41618,7 +41894,7 @@ const init = function (options) {
|
|
|
41618
41894
|
|
|
41619
41895
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41620
41896
|
const libName = "@khanacademy/perseus";
|
|
41621
|
-
const libVersion = "20.
|
|
41897
|
+
const libVersion = "20.7.0";
|
|
41622
41898
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41623
41899
|
|
|
41624
41900
|
const apiVersion = {
|
|
@@ -42390,7 +42666,6 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
42390
42666
|
const qScore = this.questionRenderer.scoreWidgets();
|
|
42391
42667
|
const qGuess = this.questionRenderer.getUserInputForWidgets();
|
|
42392
42668
|
const state = this.questionRenderer.getSerializedState();
|
|
42393
|
-
// @ts-expect-error - TS2322 - Type 'Partial<Record<string, KEScore>>' is not assignable to type '{ [key: string]: KEScore; }'. | TS2345 - Argument of type '{ [widgetId: string]: PerseusScore; }' is not assignable to parameter of type 'Partial<Record<string, { type: "invalid"; message?: string | null | undefined; suppressAlmostThere?: boolean | null | undefined; }>>'.
|
|
42394
42669
|
return mapObject(qScore, (score, id) => {
|
|
42395
42670
|
return Util.keScoreFromPerseusScore(score, qGuess[id], state);
|
|
42396
42671
|
});
|
|
@@ -44322,6 +44597,218 @@ function generateTestPerseusItem() {
|
|
|
44322
44597
|
};
|
|
44323
44598
|
}
|
|
44324
44599
|
|
|
44600
|
+
/**
|
|
44601
|
+
* Some helpers for CEDAR logging. CEDAR convention dictates
|
|
44602
|
+
* that enum values should be upper case. Ex: for the
|
|
44603
|
+
* PerseusWidgetUsedInProblemAttempt CEDAR event, we needed
|
|
44604
|
+
* to: get the type and convert that to an enum value.
|
|
44605
|
+
*/
|
|
44606
|
+
|
|
44607
|
+
const widgetNameToEnum = {
|
|
44608
|
+
categorizer: "CATEGORIZER",
|
|
44609
|
+
"cs-program": "CS_PROGRAM",
|
|
44610
|
+
definition: "DEFINITION",
|
|
44611
|
+
"deprecated-standin": "DEPRECATED_STANDIN",
|
|
44612
|
+
dropdown: "DROPDOWN",
|
|
44613
|
+
explanation: "EXPLANATION",
|
|
44614
|
+
expression: "EXPRESSION",
|
|
44615
|
+
"graded-group-set": "GRADED_GROUP_SET",
|
|
44616
|
+
"graded-group": "GRADED_GROUP",
|
|
44617
|
+
grapher: "GRAPHER",
|
|
44618
|
+
group: "GROUP",
|
|
44619
|
+
iframe: "IFRAME",
|
|
44620
|
+
image: "IMAGE",
|
|
44621
|
+
"input-number": "INPUT_NUMBER",
|
|
44622
|
+
interaction: "INTERACTIVE",
|
|
44623
|
+
"interactive-graph": "INTERACTIVE_GRAPH",
|
|
44624
|
+
"label-image": "LABEL_IMAGE",
|
|
44625
|
+
"lights-puzzle": "LIGHTS_PUZZLE",
|
|
44626
|
+
matcher: "MATCHER",
|
|
44627
|
+
matrix: "MATRIX",
|
|
44628
|
+
measurer: "MEASURER",
|
|
44629
|
+
"molecule-renderer": "MOLECULE",
|
|
44630
|
+
"number-line": "NUMBER_LINE",
|
|
44631
|
+
"numeric-input": "NUMERIC_INPUT",
|
|
44632
|
+
orderer: "ORDERER",
|
|
44633
|
+
passage: "PASSAGE",
|
|
44634
|
+
"passage-ref": "PASSAGE_REF",
|
|
44635
|
+
"passage-ref-target": "PASSAGE_REF_TARGET",
|
|
44636
|
+
plotter: "PLOTTER",
|
|
44637
|
+
"python-program": "PYTHON_PROGRAM",
|
|
44638
|
+
"reaction-diagram": "REACTION_DIAGRAM",
|
|
44639
|
+
radio: "RADIO",
|
|
44640
|
+
sequence: "SEQUENCE",
|
|
44641
|
+
simulator: "SIMULATOR",
|
|
44642
|
+
sorter: "SORTER",
|
|
44643
|
+
table: "TABLE",
|
|
44644
|
+
"unit-input": "UNIT",
|
|
44645
|
+
video: "VIDEO"
|
|
44646
|
+
};
|
|
44647
|
+
function convertWidgetNameToEnum(name) {
|
|
44648
|
+
const widgetEnum = widgetNameToEnum[name];
|
|
44649
|
+
if (!widgetEnum) {
|
|
44650
|
+
throw new Error(`Unknown widget name: ${name}`);
|
|
44651
|
+
}
|
|
44652
|
+
return widgetEnum;
|
|
44653
|
+
}
|
|
44654
|
+
function getWidgetTypeFromWidgetId(widgetId) {
|
|
44655
|
+
const [widgetType, widgetNumber] = widgetId.split(" ");
|
|
44656
|
+
if (!widgetNumber) {
|
|
44657
|
+
throw new Error(`Incorrect format for widget ID: ${widgetId}`);
|
|
44658
|
+
}
|
|
44659
|
+
if (!widgetNameToEnum[widgetType]) {
|
|
44660
|
+
throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
|
|
44661
|
+
}
|
|
44662
|
+
return widgetType;
|
|
44663
|
+
}
|
|
44664
|
+
|
|
44665
|
+
let WidgetType = /*#__PURE__*/function (WidgetType) {
|
|
44666
|
+
WidgetType["Categorizer"] = "categorizer";
|
|
44667
|
+
WidgetType["CSProgram"] = "cs-program";
|
|
44668
|
+
WidgetType["Definition"] = "definition";
|
|
44669
|
+
WidgetType["Dropdown"] = "dropdown";
|
|
44670
|
+
WidgetType["Explanation"] = "explanation";
|
|
44671
|
+
WidgetType["Expression"] = "expression";
|
|
44672
|
+
WidgetType["GradedGroup"] = "graded-group";
|
|
44673
|
+
WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
|
|
44674
|
+
WidgetType["GradedGroupSet"] = "graded-group-set";
|
|
44675
|
+
WidgetType["Grapher"] = "grapher";
|
|
44676
|
+
WidgetType["Group"] = "group";
|
|
44677
|
+
WidgetType["Iframe"] = "iframe";
|
|
44678
|
+
WidgetType["Image"] = "image";
|
|
44679
|
+
WidgetType["InputNumber"] = "input-number";
|
|
44680
|
+
WidgetType["Interaction"] = "interaction";
|
|
44681
|
+
WidgetType["InteractiveGraph"] = "interactive-graph";
|
|
44682
|
+
WidgetType["InteractiveNumberLine"] = "interactive-number-line";
|
|
44683
|
+
WidgetType["LabelImage"] = "label-image";
|
|
44684
|
+
WidgetType["Matcher"] = "matcher";
|
|
44685
|
+
WidgetType["Matrix"] = "matrix";
|
|
44686
|
+
WidgetType["Measurer"] = "measurer";
|
|
44687
|
+
WidgetType["Molecule"] = "molecule";
|
|
44688
|
+
WidgetType["NumberLine"] = "number-line";
|
|
44689
|
+
WidgetType["NumericInput"] = "numeric-input";
|
|
44690
|
+
WidgetType["Orderer"] = "orderer";
|
|
44691
|
+
WidgetType["Passage"] = "passage";
|
|
44692
|
+
WidgetType["PassageRef"] = "passage-ref";
|
|
44693
|
+
WidgetType["PassageRefTarget"] = "passage-ref-target";
|
|
44694
|
+
WidgetType["Plotter"] = "plotter";
|
|
44695
|
+
WidgetType["Protractor"] = "protractor";
|
|
44696
|
+
WidgetType["PythonProgram"] = "python-program";
|
|
44697
|
+
WidgetType["Radio"] = "radio";
|
|
44698
|
+
WidgetType["Sorter"] = "sorter";
|
|
44699
|
+
WidgetType["Table"] = "table";
|
|
44700
|
+
WidgetType["Unit"] = "unit";
|
|
44701
|
+
WidgetType["Video"] = "video";
|
|
44702
|
+
WidgetType["VideoTranscriptLink"] = "video-transcript-link";
|
|
44703
|
+
return WidgetType;
|
|
44704
|
+
}({});
|
|
44705
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44706
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44707
|
+
|
|
44708
|
+
/**
|
|
44709
|
+
* Regex for widget placeholders in a string.
|
|
44710
|
+
*
|
|
44711
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
44712
|
+
* Second capture group is the widget type (ex. "radio)
|
|
44713
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44714
|
+
*/
|
|
44715
|
+
function getWidgetRegex() {
|
|
44716
|
+
return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
|
|
44717
|
+
}
|
|
44718
|
+
|
|
44719
|
+
/**
|
|
44720
|
+
* Add a widget placeholder using the provided widget type and instance number.
|
|
44721
|
+
* ex. addWidget(WidgetType.Radio, 1) => "[[☃ radio 1]]"
|
|
44722
|
+
*
|
|
44723
|
+
* @param {WidgetType} widgetType
|
|
44724
|
+
* @param {number} instance
|
|
44725
|
+
* @returns {string}
|
|
44726
|
+
*/
|
|
44727
|
+
function addWidget(widgetType, instance) {
|
|
44728
|
+
return `[[☃ ${widgetType} ${String(instance)}]]`;
|
|
44729
|
+
}
|
|
44730
|
+
|
|
44731
|
+
/**
|
|
44732
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44733
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44734
|
+
*
|
|
44735
|
+
* Content should contain Perseus widget placeholders,
|
|
44736
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44737
|
+
*
|
|
44738
|
+
* @param {string} content
|
|
44739
|
+
* @returns {Array<WidgetId>} widgets
|
|
44740
|
+
*/
|
|
44741
|
+
function getAllWidgetIds(content) {
|
|
44742
|
+
const widgets = [];
|
|
44743
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44744
|
+
let match = localWidgetRegex.exec(content);
|
|
44745
|
+
while (match !== null) {
|
|
44746
|
+
widgets.push(match[1]);
|
|
44747
|
+
match = localWidgetRegex.exec(content);
|
|
44748
|
+
}
|
|
44749
|
+
return widgets;
|
|
44750
|
+
}
|
|
44751
|
+
|
|
44752
|
+
/**
|
|
44753
|
+
* Check if a specific widget is an actual widget type using its name.
|
|
44754
|
+
* The widget name should include a widget type, without an instance number,
|
|
44755
|
+
* as a string. ex. 'radio'
|
|
44756
|
+
*
|
|
44757
|
+
* @param {string} widgetName
|
|
44758
|
+
* @returns {boolean}
|
|
44759
|
+
*/
|
|
44760
|
+
function isWidgetType(widgetName) {
|
|
44761
|
+
return Object.values(WidgetType).includes(widgetName);
|
|
44762
|
+
}
|
|
44763
|
+
|
|
44764
|
+
/**
|
|
44765
|
+
* Extract all widget types from a Perseus JSON content string.
|
|
44766
|
+
* This does not include the instance number and prevents duplicates.
|
|
44767
|
+
* example output: ['radio', 'categorizer']
|
|
44768
|
+
*
|
|
44769
|
+
* @param {string} content
|
|
44770
|
+
* @returns {Array<WidgetId>}
|
|
44771
|
+
*/
|
|
44772
|
+
function getAllWidgetTypes(content) {
|
|
44773
|
+
const widgetTypes = [];
|
|
44774
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44775
|
+
let match = localWidgetRegex.exec(content);
|
|
44776
|
+
while (match !== null) {
|
|
44777
|
+
const matchType = match[2];
|
|
44778
|
+
if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
|
|
44779
|
+
widgetTypes.push(matchType);
|
|
44780
|
+
}
|
|
44781
|
+
match = localWidgetRegex.exec(content);
|
|
44782
|
+
}
|
|
44783
|
+
return widgetTypes;
|
|
44784
|
+
}
|
|
44785
|
+
|
|
44786
|
+
/**
|
|
44787
|
+
* Check if a specific widget is a question widget type using its ID.
|
|
44788
|
+
* The widget ID includes the widget type and the instance number.
|
|
44789
|
+
*
|
|
44790
|
+
* @param {WidgetId} widgetId
|
|
44791
|
+
* @returns {boolean}
|
|
44792
|
+
*/
|
|
44793
|
+
function isQuestionWidgetType(widgetId) {
|
|
44794
|
+
return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
|
|
44795
|
+
}
|
|
44796
|
+
|
|
44797
|
+
/**
|
|
44798
|
+
* Extract the widget IDs of all widgets considered question widgets. Widget IDs
|
|
44799
|
+
* include widget type and instance number.
|
|
44800
|
+
* example output: ['radio 1', 'categorizer 1']
|
|
44801
|
+
*
|
|
44802
|
+
* Content should contain Perseus widget placeholders,
|
|
44803
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44804
|
+
*
|
|
44805
|
+
* @param {string} content
|
|
44806
|
+
* @returns Array<WidgetId>
|
|
44807
|
+
*/
|
|
44808
|
+
function getQuestionWidgetIds(content) {
|
|
44809
|
+
return getAllWidgetIds(content).filter(isQuestionWidgetType);
|
|
44810
|
+
}
|
|
44811
|
+
|
|
44325
44812
|
const EditorJsonify = {
|
|
44326
44813
|
serialize: function () {
|
|
44327
44814
|
// Omit props that get passed to all widgets
|
|
@@ -44363,6 +44850,7 @@ exports.PerseusError = PerseusError;
|
|
|
44363
44850
|
exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
|
|
44364
44851
|
exports.PerseusMarkdown = PerseusMarkdown;
|
|
44365
44852
|
exports.PlotterWidget = Plotter$1;
|
|
44853
|
+
exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
|
|
44366
44854
|
exports.Radio = Radio;
|
|
44367
44855
|
exports.Renderer = Renderer;
|
|
44368
44856
|
exports.ServerItemRenderer = ref;
|
|
@@ -44371,16 +44859,23 @@ exports.TableWidget = Table$1;
|
|
|
44371
44859
|
exports.Util = Util;
|
|
44372
44860
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
44373
44861
|
exports.WidgetJsonifyDeprecated = WidgetJsonifyDeprecated;
|
|
44862
|
+
exports.WidgetType = WidgetType;
|
|
44374
44863
|
exports.Widgets = widgets$1;
|
|
44864
|
+
exports.addWidget = addWidget;
|
|
44375
44865
|
exports.apiVersion = apiVersion;
|
|
44376
44866
|
exports.buildEmptyItemTreeForShape = buildEmptyItemTreeForShape;
|
|
44377
44867
|
exports.buildMapper = buildMapper;
|
|
44378
44868
|
exports.components = components;
|
|
44379
44869
|
exports.constants = constants;
|
|
44870
|
+
exports.convertWidgetNameToEnum = convertWidgetNameToEnum;
|
|
44380
44871
|
exports.displaySigFigs = displaySigFigs;
|
|
44381
44872
|
exports.generateTestPerseusItem = generateTestPerseusItem;
|
|
44382
44873
|
exports.genericPerseusItemData = genericPerseusItemData;
|
|
44874
|
+
exports.getAllWidgetIds = getAllWidgetIds;
|
|
44875
|
+
exports.getAllWidgetTypes = getAllWidgetTypes;
|
|
44383
44876
|
exports.getAnswersFromWidgets = getAnswersFromWidgets;
|
|
44877
|
+
exports.getQuestionWidgetIds = getQuestionWidgetIds;
|
|
44878
|
+
exports.getWidgetTypeFromWidgetId = getWidgetTypeFromWidgetId;
|
|
44384
44879
|
exports.globalConstants = globalConstants;
|
|
44385
44880
|
exports.globalStyles = globalStyles;
|
|
44386
44881
|
exports.icons = iconPaths;
|