@khanacademy/perseus 26.1.0 → 27.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.css +0 -10
- package/dist/es/index.js +2571 -2877
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +0 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1353 -1663
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +25 -12
- package/dist/widgets/interactive-graph.d.ts +0 -3
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +2 -1
- package/dist/widgets/interactive-graphs/reducer/initialize-graph-state.d.ts +3 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -0
- package/package.json +3 -3
- package/dist/widgets/unit.d.ts +0 -85
package/dist/index.js
CHANGED
|
@@ -19195,6 +19195,7 @@ const replaceDeprecatedWidgets = () => {
|
|
|
19195
19195
|
replaceWidget("reaction-diagram", "deprecated-standin");
|
|
19196
19196
|
replaceWidget("sequence", "deprecated-standin");
|
|
19197
19197
|
replaceWidget("simulator", "deprecated-standin");
|
|
19198
|
+
replaceWidget("unit-input", "deprecated-standin");
|
|
19198
19199
|
};
|
|
19199
19200
|
const registerEditors = editorsToRegister => {
|
|
19200
19201
|
editorsToRegister.forEach(editor => {
|
|
@@ -19228,6 +19229,7 @@ const replaceDeprecatedEditors = () => {
|
|
|
19228
19229
|
replaceEditor("reaction-diagram", "deprecated-standin");
|
|
19229
19230
|
replaceEditor("sequence", "deprecated-standin");
|
|
19230
19231
|
replaceEditor("simulator", "deprecated-standin");
|
|
19232
|
+
replaceEditor("unit-input", "deprecated-standin");
|
|
19231
19233
|
};
|
|
19232
19234
|
const getWidget = name => {
|
|
19233
19235
|
// TODO(alex): Consider referring to these as renderers to avoid
|
|
@@ -28588,7 +28590,8 @@ const defaultGraphConfig = {
|
|
|
28588
28590
|
graphDimensionsInPixels: [1, 1],
|
|
28589
28591
|
width: 0,
|
|
28590
28592
|
height: 0,
|
|
28591
|
-
labels: []
|
|
28593
|
+
labels: [],
|
|
28594
|
+
hintMode: false
|
|
28592
28595
|
};
|
|
28593
28596
|
const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
|
|
28594
28597
|
function useGraphConfig() {
|
|
@@ -30073,8 +30076,6 @@ const ItemExtras = [
|
|
|
30073
30076
|
|
|
30074
30077
|
// prettier-ignore
|
|
30075
30078
|
|
|
30076
|
-
// prettier-ignore
|
|
30077
|
-
|
|
30078
30079
|
//prettier-ignore
|
|
30079
30080
|
|
|
30080
30081
|
// A background image applied to various widgets.
|
|
@@ -30600,6 +30601,16 @@ function useDraggable(args) {
|
|
|
30600
30601
|
const isKeyboard = type.includes("key");
|
|
30601
30602
|
if (isKeyboard) {
|
|
30602
30603
|
event?.preventDefault();
|
|
30604
|
+
|
|
30605
|
+
// When a key is held down, we see multiple "keydown" events,
|
|
30606
|
+
// followed by a single "keyup" event.
|
|
30607
|
+
// For a single keypress, we only see a "keydown" event, no
|
|
30608
|
+
// "keyup".
|
|
30609
|
+
// We never want to process the keyup event as an intent to
|
|
30610
|
+
// move so we bail on further processing here.
|
|
30611
|
+
if (type === "keyup") {
|
|
30612
|
+
return;
|
|
30613
|
+
}
|
|
30603
30614
|
const {
|
|
30604
30615
|
direction: yDownDirection,
|
|
30605
30616
|
altKey,
|
|
@@ -30700,7 +30711,8 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
30700
30711
|
const {
|
|
30701
30712
|
range,
|
|
30702
30713
|
markings,
|
|
30703
|
-
showTooltips
|
|
30714
|
+
showTooltips,
|
|
30715
|
+
hintMode
|
|
30704
30716
|
} = useGraphConfig();
|
|
30705
30717
|
const {
|
|
30706
30718
|
point,
|
|
@@ -30745,7 +30757,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
30745
30757
|
cursor
|
|
30746
30758
|
},
|
|
30747
30759
|
"data-testid": "movable-point",
|
|
30748
|
-
tabIndex: tabIndex(focusBehavior)
|
|
30760
|
+
tabIndex: hintMode ? -1 : tabIndex(focusBehavior)
|
|
30749
30761
|
}, /*#__PURE__*/React__namespace.createElement("circle", {
|
|
30750
30762
|
className: "movable-point-hitbox",
|
|
30751
30763
|
r: hitboxSizePx / 2,
|
|
@@ -30833,7 +30845,8 @@ const MovableLine = props => {
|
|
|
30833
30845
|
};
|
|
30834
30846
|
function useControlPoint(point, color, onMovePoint) {
|
|
30835
30847
|
const {
|
|
30836
|
-
snapStep
|
|
30848
|
+
snapStep,
|
|
30849
|
+
hintMode
|
|
30837
30850
|
} = useGraphConfig();
|
|
30838
30851
|
const [focused, setFocused] = React.useState(false);
|
|
30839
30852
|
const keyboardHandleRef = React.useRef(null);
|
|
@@ -30855,7 +30868,7 @@ function useControlPoint(point, color, onMovePoint) {
|
|
|
30855
30868
|
const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
|
|
30856
30869
|
"data-testid": "movable-point__focusable-handle",
|
|
30857
30870
|
className: "movable-point__focusable-handle",
|
|
30858
|
-
tabIndex: 0,
|
|
30871
|
+
tabIndex: hintMode ? -1 : 0,
|
|
30859
30872
|
ref: keyboardHandleRef,
|
|
30860
30873
|
onFocus: () => setFocused(true),
|
|
30861
30874
|
onBlur: () => setFocused(false)
|
|
@@ -30888,7 +30901,8 @@ const Line$1 = props => {
|
|
|
30888
30901
|
const {
|
|
30889
30902
|
range,
|
|
30890
30903
|
graphDimensionsInPixels,
|
|
30891
|
-
snapStep
|
|
30904
|
+
snapStep,
|
|
30905
|
+
hintMode
|
|
30892
30906
|
} = useGraphConfig();
|
|
30893
30907
|
let startExtend = undefined;
|
|
30894
30908
|
let endExtend = undefined;
|
|
@@ -30910,7 +30924,7 @@ const Line$1 = props => {
|
|
|
30910
30924
|
});
|
|
30911
30925
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
|
|
30912
30926
|
ref: line,
|
|
30913
|
-
tabIndex: 0,
|
|
30927
|
+
tabIndex: hintMode ? -1 : 0,
|
|
30914
30928
|
className: "movable-line",
|
|
30915
30929
|
"data-testid": "movable-line",
|
|
30916
30930
|
style: {
|
|
@@ -31429,6 +31443,9 @@ const PolygonGraph = props => {
|
|
|
31429
31443
|
snapStep,
|
|
31430
31444
|
snapTo
|
|
31431
31445
|
} = props.graphState;
|
|
31446
|
+
const {
|
|
31447
|
+
hintMode
|
|
31448
|
+
} = useGraphConfig();
|
|
31432
31449
|
|
|
31433
31450
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
31434
31451
|
// think coords can be null.
|
|
@@ -31448,12 +31465,16 @@ const PolygonGraph = props => {
|
|
|
31448
31465
|
constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
|
|
31449
31466
|
});
|
|
31450
31467
|
const active = hovered || focused || dragging;
|
|
31468
|
+
const lastMoveTime = React__namespace.useRef(0);
|
|
31451
31469
|
const lines = getLines(points);
|
|
31452
31470
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
31453
31471
|
points: [...points],
|
|
31454
31472
|
color: "var(--movable-line-stroke-color)",
|
|
31455
31473
|
svgPolygonProps: {
|
|
31456
|
-
strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)"
|
|
31474
|
+
strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
31475
|
+
style: {
|
|
31476
|
+
fill: "transparent"
|
|
31477
|
+
}
|
|
31457
31478
|
}
|
|
31458
31479
|
}), points.map((point, i) => {
|
|
31459
31480
|
const pt1 = points.at(i - 1);
|
|
@@ -31485,10 +31506,11 @@ const PolygonGraph = props => {
|
|
|
31485
31506
|
color: "transparent",
|
|
31486
31507
|
svgPolygonProps: {
|
|
31487
31508
|
ref,
|
|
31488
|
-
tabIndex: 0,
|
|
31509
|
+
tabIndex: hintMode ? -1 : 0,
|
|
31489
31510
|
strokeWidth: TARGET_SIZE,
|
|
31490
31511
|
style: {
|
|
31491
|
-
cursor: dragging ? "grabbing" : "grab"
|
|
31512
|
+
cursor: dragging ? "grabbing" : "grab",
|
|
31513
|
+
fill: hovered ? "var(--mafs-blue)" : "transparent"
|
|
31492
31514
|
},
|
|
31493
31515
|
onFocus: () => setFocused(true),
|
|
31494
31516
|
onBlur: () => setFocused(false),
|
|
@@ -31500,7 +31522,15 @@ const PolygonGraph = props => {
|
|
|
31500
31522
|
key: "point-" + i,
|
|
31501
31523
|
snapTo: snapTo,
|
|
31502
31524
|
point: point,
|
|
31503
|
-
onMove: destination =>
|
|
31525
|
+
onMove: destination => {
|
|
31526
|
+
const now = Date.now();
|
|
31527
|
+
const targetFPS = 40;
|
|
31528
|
+
const moveThresholdTime = 1000 / targetFPS;
|
|
31529
|
+
if (now - lastMoveTime.current > moveThresholdTime) {
|
|
31530
|
+
dispatch(actions.polygon.movePoint(i, destination));
|
|
31531
|
+
lastMoveTime.current = now;
|
|
31532
|
+
}
|
|
31533
|
+
}
|
|
31504
31534
|
})));
|
|
31505
31535
|
};
|
|
31506
31536
|
function getLines(points) {
|
|
@@ -31621,7 +31651,8 @@ function MovableCircle(props) {
|
|
|
31621
31651
|
onMove
|
|
31622
31652
|
} = props;
|
|
31623
31653
|
const {
|
|
31624
|
-
snapStep
|
|
31654
|
+
snapStep,
|
|
31655
|
+
hintMode
|
|
31625
31656
|
} = useGraphConfig();
|
|
31626
31657
|
const draggableRef = React.useRef(null);
|
|
31627
31658
|
const {
|
|
@@ -31636,7 +31667,7 @@ function MovableCircle(props) {
|
|
|
31636
31667
|
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
31637
31668
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
31638
31669
|
ref: draggableRef,
|
|
31639
|
-
tabIndex: 0,
|
|
31670
|
+
tabIndex: hintMode ? -1 : 0,
|
|
31640
31671
|
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
31641
31672
|
}, /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
31642
31673
|
className: "focus-ring",
|
|
@@ -32040,7 +32071,8 @@ const MafsGraph = props => {
|
|
|
32040
32071
|
const {
|
|
32041
32072
|
state,
|
|
32042
32073
|
dispatch,
|
|
32043
|
-
labels
|
|
32074
|
+
labels,
|
|
32075
|
+
hintMode
|
|
32044
32076
|
} = props;
|
|
32045
32077
|
const [width, height] = props.box;
|
|
32046
32078
|
const tickStep = props.step;
|
|
@@ -32055,7 +32087,8 @@ const MafsGraph = props => {
|
|
|
32055
32087
|
graphDimensionsInPixels: props.box,
|
|
32056
32088
|
width,
|
|
32057
32089
|
height,
|
|
32058
|
-
labels
|
|
32090
|
+
labels,
|
|
32091
|
+
hintMode
|
|
32059
32092
|
}
|
|
32060
32093
|
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
32061
32094
|
className: "mafs-graph",
|
|
@@ -32066,7 +32099,8 @@ const MafsGraph = props => {
|
|
|
32066
32099
|
padding: "25px 25px 0 0",
|
|
32067
32100
|
boxSizing: "content-box",
|
|
32068
32101
|
marginLeft: "20px",
|
|
32069
|
-
marginBottom: "20px"
|
|
32102
|
+
marginBottom: "20px",
|
|
32103
|
+
pointerEvents: hintMode ? "none" : "auto"
|
|
32070
32104
|
}
|
|
32071
32105
|
}, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
|
|
32072
32106
|
box: props.box,
|
|
@@ -32097,7 +32131,7 @@ const MafsGraph = props => {
|
|
|
32097
32131
|
}), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
|
|
32098
32132
|
lockedFigures: props.lockedFigures,
|
|
32099
32133
|
range: state.range
|
|
32100
|
-
}),
|
|
32134
|
+
}), props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
|
|
32101
32135
|
state,
|
|
32102
32136
|
dispatch
|
|
32103
32137
|
})))));
|
|
@@ -33198,7 +33232,6 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33198
33232
|
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
33199
33233
|
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
33200
33234
|
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
33201
|
-
const startCoords = graph.startCoords ?? null;
|
|
33202
33235
|
const originalPropsRef = React.useRef(props);
|
|
33203
33236
|
const latestPropsRef = wonderBlocksCore.useLatestRef(props);
|
|
33204
33237
|
React.useEffect(() => {
|
|
@@ -33209,7 +33242,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
33209
33242
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
33210
33243
|
dispatch(reinitialize(latestPropsRef.current));
|
|
33211
33244
|
}
|
|
33212
|
-
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides,
|
|
33245
|
+
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
|
|
33213
33246
|
return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
|
|
33214
33247
|
state: state,
|
|
33215
33248
|
dispatch: dispatch
|
|
@@ -33300,9 +33333,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33300
33333
|
markings: "graph",
|
|
33301
33334
|
showTooltips: false,
|
|
33302
33335
|
showProtractor: false,
|
|
33303
|
-
showRuler: false,
|
|
33304
|
-
rulerLabel: "",
|
|
33305
|
-
rulerTicks: 10,
|
|
33306
33336
|
graph: {
|
|
33307
33337
|
type: "linear"
|
|
33308
33338
|
}
|
|
@@ -34465,9 +34495,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
34465
34495
|
markings: this.props.markings,
|
|
34466
34496
|
backgroundImage: this.props.backgroundImage,
|
|
34467
34497
|
showProtractor: this.props.showProtractor,
|
|
34468
|
-
showRuler: this.props.showRuler,
|
|
34469
|
-
rulerLabel: this.props.rulerLabel,
|
|
34470
|
-
rulerTicks: this.props.rulerTicks,
|
|
34471
34498
|
onMouseDown: onMouseDown,
|
|
34472
34499
|
onGraphieUpdated: this.setGraphie,
|
|
34473
34500
|
setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
|
|
@@ -34486,9 +34513,6 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34486
34513
|
markings: "graph",
|
|
34487
34514
|
showTooltips: false,
|
|
34488
34515
|
showProtractor: false,
|
|
34489
|
-
showRuler: false,
|
|
34490
|
-
rulerLabel: "",
|
|
34491
|
-
rulerTicks: 10,
|
|
34492
34516
|
graph: {
|
|
34493
34517
|
type: "linear"
|
|
34494
34518
|
}
|
|
@@ -44207,1231 +44231,316 @@ var Table$1 = {
|
|
|
44207
44231
|
isLintable: true
|
|
44208
44232
|
};
|
|
44209
44233
|
|
|
44210
|
-
|
|
44211
|
-
|
|
44234
|
+
/**
|
|
44235
|
+
* This is a component that adds a link to the transcript for embedded videos in articles.
|
|
44236
|
+
*/
|
|
44237
|
+
const IS_URL$1 = /^https?:\/\//;
|
|
44238
|
+
const getYoutubeId = url => {
|
|
44239
|
+
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
44240
|
+
const match = url.match(regExp);
|
|
44241
|
+
if (match && match[7].length === 11) {
|
|
44242
|
+
return match[7];
|
|
44243
|
+
}
|
|
44244
|
+
return "videoNotFound";
|
|
44212
44245
|
};
|
|
44213
|
-
|
|
44214
|
-
|
|
44215
|
-
|
|
44216
|
-
|
|
44217
|
-
|
|
44218
|
-
|
|
44219
|
-
|
|
44246
|
+
|
|
44247
|
+
// will accept a youtube link or a video's slug name from embedded video and then query accordingly.
|
|
44248
|
+
|
|
44249
|
+
/**
|
|
44250
|
+
* Video Transcript Link Component.
|
|
44251
|
+
*/
|
|
44252
|
+
const VideoTranscriptLink = props => {
|
|
44253
|
+
const {
|
|
44254
|
+
location
|
|
44255
|
+
} = props;
|
|
44256
|
+
const {
|
|
44257
|
+
useVideo
|
|
44258
|
+
} = getDependencies();
|
|
44259
|
+
const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
|
|
44260
|
+
// The result value conforms to the wonder-blocks-data `Result` type
|
|
44261
|
+
// which is used by our GraphQL framework.
|
|
44262
|
+
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
|
|
44263
|
+
const result = useVideo(id, kind);
|
|
44264
|
+
const {
|
|
44265
|
+
strings
|
|
44266
|
+
} = usePerseusI18n();
|
|
44267
|
+
switch (result.status) {
|
|
44268
|
+
case "loading":
|
|
44269
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
|
|
44270
|
+
case "success":
|
|
44271
|
+
{
|
|
44272
|
+
const video = result.data?.video;
|
|
44273
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
44274
|
+
style: styles$4.transcriptLink
|
|
44275
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Text, null, video?.title), /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
|
|
44276
|
+
size: 10
|
|
44277
|
+
}), /*#__PURE__*/React__namespace.createElement(Link__default["default"], {
|
|
44278
|
+
href: "/transcript/" + (video?.contentId || "videoNotFound"),
|
|
44279
|
+
target: "_blank",
|
|
44280
|
+
className: "visited-no-recolor"
|
|
44281
|
+
}, strings.videoTranscript));
|
|
44220
44282
|
}
|
|
44221
|
-
|
|
44283
|
+
case "error":
|
|
44284
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
44285
|
+
case "aborted":
|
|
44286
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
44287
|
+
default:
|
|
44288
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
44222
44289
|
}
|
|
44223
|
-
return obj;
|
|
44224
44290
|
};
|
|
44225
|
-
|
|
44226
|
-
|
|
44227
|
-
|
|
44291
|
+
const styles$4 = aphrodite.StyleSheet.create({
|
|
44292
|
+
transcriptLink: {
|
|
44293
|
+
flexDirection: "row",
|
|
44294
|
+
width: "100%",
|
|
44295
|
+
justifyContent: "center"
|
|
44228
44296
|
}
|
|
44229
|
-
|
|
44230
|
-
};
|
|
44297
|
+
});
|
|
44231
44298
|
|
|
44232
|
-
|
|
44233
|
-
*
|
|
44234
|
-
* lens constructor thaws, freeze delegates to type's freeze
|
|
44299
|
+
/**
|
|
44300
|
+
* This is a video widget for embedding videos in articles.
|
|
44235
44301
|
*/
|
|
44302
|
+
// Current default is 720p, based on the typical videos we upload currently
|
|
44303
|
+
const DEFAULT_WIDTH = 1280;
|
|
44304
|
+
const DEFAULT_HEIGHT = 720;
|
|
44305
|
+
const KA_EMBED = "{host}/embed_video?slug={slug}" + "&internal_video_only=1";
|
|
44306
|
+
const IS_URL = /^https?:\/\//;
|
|
44307
|
+
const IS_KA_SITE = /(khanacademy\.org|localhost)/;
|
|
44308
|
+
const IS_VIMEO = /(vimeo\.com)/;
|
|
44236
44309
|
|
|
44237
|
-
//
|
|
44238
|
-
// find the implementation to use for a given object
|
|
44239
|
-
var dispatch = function (x) {
|
|
44240
|
-
if (Array.isArray(x)) {
|
|
44241
|
-
return arr;
|
|
44242
|
-
} else if (isObject(x)) {
|
|
44243
|
-
return obj;
|
|
44244
|
-
} else if (typeof x === "string") {
|
|
44245
|
-
return str;
|
|
44246
|
-
}
|
|
44247
|
-
};
|
|
44310
|
+
// exports has no 'transform'
|
|
44248
44311
|
|
|
44249
|
-
|
|
44250
|
-
|
|
44251
|
-
|
|
44252
|
-
|
|
44312
|
+
/**
|
|
44313
|
+
* Video renderer.
|
|
44314
|
+
*/
|
|
44315
|
+
class Video extends React__namespace.Component {
|
|
44316
|
+
static contextType = PerseusI18nContext;
|
|
44317
|
+
/**
|
|
44318
|
+
* This is the widget's grading function.
|
|
44319
|
+
* Points for videos are tallied by the embedded video itself, in the case
|
|
44320
|
+
* of Khan Academy videos.
|
|
44321
|
+
*/
|
|
44322
|
+
static validate(userInput, rubric) {
|
|
44323
|
+
return {
|
|
44324
|
+
type: "points",
|
|
44325
|
+
earned: 0,
|
|
44326
|
+
total: 0,
|
|
44327
|
+
message: null
|
|
44328
|
+
};
|
|
44253
44329
|
}
|
|
44254
|
-
|
|
44255
|
-
return
|
|
44330
|
+
getUserInput = () => {
|
|
44331
|
+
return null;
|
|
44332
|
+
};
|
|
44333
|
+
simpleValidate = rubric => {
|
|
44334
|
+
return Video.validate(null, rubric);
|
|
44335
|
+
};
|
|
44336
|
+
change = (() => {
|
|
44337
|
+
var _this = this;
|
|
44338
|
+
return function () {
|
|
44339
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
44340
|
+
args[_key] = arguments[_key];
|
|
44341
|
+
}
|
|
44342
|
+
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
44343
|
+
return change.apply(_this, args);
|
|
44344
|
+
};
|
|
44345
|
+
})();
|
|
44346
|
+
render() {
|
|
44347
|
+
const {
|
|
44348
|
+
InitialRequestUrl
|
|
44349
|
+
} = getDependencies();
|
|
44350
|
+
const location = this.props.location;
|
|
44351
|
+
if (!location) {
|
|
44352
|
+
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
44353
|
+
}
|
|
44354
|
+
let url;
|
|
44355
|
+
if (IS_URL.test(location)) {
|
|
44356
|
+
url = location;
|
|
44357
|
+
if (IS_VIMEO.test(url)) {
|
|
44358
|
+
// If this is a vimeo video, we need to add the query string
|
|
44359
|
+
// parameter "dnt" so that analytics/tracking cookies aren't set.
|
|
44360
|
+
// https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameters-overview
|
|
44361
|
+
if (url.indexOf("?") === -1) {
|
|
44362
|
+
url += "?dnt=1";
|
|
44363
|
+
} else {
|
|
44364
|
+
url += "&dnt=1";
|
|
44365
|
+
}
|
|
44366
|
+
}
|
|
44367
|
+
} else {
|
|
44368
|
+
url = KA_EMBED.replace("{slug}", location);
|
|
44369
|
+
let embedHostname = "https://www.khanacademy.org";
|
|
44370
|
+
if (IS_KA_SITE.test(InitialRequestUrl.host)) {
|
|
44371
|
+
embedHostname = InitialRequestUrl.origin;
|
|
44372
|
+
}
|
|
44373
|
+
url = url.replace("{host}", embedHostname);
|
|
44374
|
+
}
|
|
44375
|
+
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(FixedToResponsive, {
|
|
44376
|
+
width: DEFAULT_WIDTH,
|
|
44377
|
+
height: DEFAULT_HEIGHT
|
|
44378
|
+
// The key is here for the benefit of the editor, to ensure that
|
|
44379
|
+
// any changes cause a re-rendering of the frame.
|
|
44380
|
+
,
|
|
44381
|
+
key: location + this.props.alignment
|
|
44382
|
+
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
44383
|
+
style: a11y.srOnly
|
|
44384
|
+
}, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
|
|
44385
|
+
// TODO(joshuan): Consider not using iframes when we're
|
|
44386
|
+
// loading this from webapp. This iframe is problematic
|
|
44387
|
+
// for screenreaders.
|
|
44388
|
+
className: "perseus-video-widget",
|
|
44389
|
+
sandbox: "allow-same-origin allow-scripts",
|
|
44390
|
+
width: DEFAULT_WIDTH,
|
|
44391
|
+
height: DEFAULT_HEIGHT,
|
|
44392
|
+
src: url,
|
|
44393
|
+
allowFullScreen: true
|
|
44394
|
+
})), /*#__PURE__*/React__namespace.createElement(VideoTranscriptLink, {
|
|
44395
|
+
location: location
|
|
44396
|
+
}));
|
|
44256
44397
|
}
|
|
44257
|
-
|
|
44258
|
-
|
|
44259
|
-
|
|
44260
|
-
|
|
44261
|
-
|
|
44262
|
-
|
|
44263
|
-
|
|
44398
|
+
}
|
|
44399
|
+
var Video$1 = {
|
|
44400
|
+
name: "video",
|
|
44401
|
+
displayName: "Video",
|
|
44402
|
+
defaultAlignment: "block",
|
|
44403
|
+
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
44404
|
+
widget: Video
|
|
44264
44405
|
};
|
|
44265
|
-
|
|
44266
|
-
|
|
44267
|
-
|
|
44406
|
+
|
|
44407
|
+
/* eslint-disable import/no-named-as-default */
|
|
44408
|
+
var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Video$1, DeprecatedStandin$1];
|
|
44409
|
+
|
|
44410
|
+
/**
|
|
44411
|
+
* This should be called by all clients, specifying whether extra widgets are
|
|
44412
|
+
* needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
|
|
44413
|
+
* call it multiple times.
|
|
44414
|
+
*
|
|
44415
|
+
* skipMathJax:
|
|
44416
|
+
* If false/undefined, MathJax will be configured, and the
|
|
44417
|
+
* promise will wait for MathJax to load (if it hasn't already).
|
|
44418
|
+
*/
|
|
44419
|
+
const init = function (options) {
|
|
44420
|
+
registerWidgets(basicWidgets);
|
|
44421
|
+
registerWidgets(extraWidgets);
|
|
44422
|
+
replaceDeprecatedWidgets();
|
|
44423
|
+
|
|
44424
|
+
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
44425
|
+
const skipMathJax = options.skipMathJax;
|
|
44426
|
+
if (skipMathJax) {
|
|
44427
|
+
// @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
|
|
44428
|
+
return Promise.resolve();
|
|
44268
44429
|
}
|
|
44269
|
-
|
|
44270
|
-
|
|
44271
|
-
|
|
44272
|
-
|
|
44273
|
-
|
|
44274
|
-
|
|
44275
|
-
|
|
44276
|
-
|
|
44277
|
-
|
|
44278
|
-
|
|
44279
|
-
|
|
44280
|
-
|
|
44281
|
-
|
|
44282
|
-
var obj = this._wrapped;
|
|
44283
|
-
for (var i = 0; i < lensArr.length; i++) {
|
|
44284
|
-
obj = dispatch(obj).get(obj, lensArr[i]);
|
|
44285
|
-
}
|
|
44286
|
-
return obj;
|
|
44287
|
-
};
|
|
44288
|
-
lens.prototype.mod = function (lensArr, f) {
|
|
44289
|
-
var obj = this._wrapped;
|
|
44290
|
-
var newObj = clone(obj);
|
|
44291
|
-
var ops = dispatch(obj);
|
|
44292
|
-
if (lensArr.length === 0) {
|
|
44293
|
-
this._wrapped = f(this._wrapped);
|
|
44294
|
-
} else if (lensArr.length === 1) {
|
|
44295
|
-
this._wrapped = ops.mod(newObj, lensArr[0], f);
|
|
44296
|
-
} else {
|
|
44297
|
-
var monocle = lensArr[0];
|
|
44298
|
-
var shortLens = lensArr.slice(1);
|
|
44299
|
-
|
|
44300
|
-
// newObj = ops.mod(obj[monocle], shortLens, f);
|
|
44301
|
-
|
|
44302
|
-
newObj[monocle] = lens(obj[monocle]).mod(shortLens, f).freeze();
|
|
44303
|
-
this._wrapped = newObj;
|
|
44304
|
-
}
|
|
44305
|
-
return this;
|
|
44306
|
-
};
|
|
44307
|
-
|
|
44308
|
-
// TODO - move to individual files
|
|
44309
|
-
lens.prototype.merge = function (lensArr, props) {
|
|
44310
|
-
this._wrapped = lens(this._wrapped).mod(lensArr, function (oldProps) {
|
|
44311
|
-
return merge(oldProps, props);
|
|
44312
|
-
}).freeze();
|
|
44313
|
-
return this;
|
|
44314
|
-
};
|
|
44315
|
-
|
|
44316
|
-
// Lens must have length >= 1 or there would be nothing to return
|
|
44317
|
-
lens.prototype.del = function (lensArr) {
|
|
44318
|
-
var obj = this._wrapped;
|
|
44319
|
-
var ops = dispatch(obj);
|
|
44320
|
-
if (lensArr.length === 1) {
|
|
44321
|
-
this._wrapped = ops.del(obj, lensArr[0]);
|
|
44322
|
-
} else {
|
|
44323
|
-
var monocle = lensArr[0];
|
|
44324
|
-
var shortLens = lensArr.slice(1);
|
|
44325
|
-
var newObj = clone(obj);
|
|
44326
|
-
newObj[monocle] = lens(obj[monocle]).del(shortLens).freeze();
|
|
44327
|
-
this._wrapped = newObj;
|
|
44328
|
-
}
|
|
44329
|
-
return this;
|
|
44330
|
-
};
|
|
44331
|
-
lens.prototype.set = function (lensArr, set) {
|
|
44332
|
-
return this.mod(lensArr, function () {
|
|
44333
|
-
return set;
|
|
44430
|
+
return new Promise((resolve, reject) => {
|
|
44431
|
+
MathJax.Hub.Config({
|
|
44432
|
+
messageStyle: "none",
|
|
44433
|
+
skipStartupTypeset: "none",
|
|
44434
|
+
"HTML-CSS": {
|
|
44435
|
+
availableFonts: ["TeX"],
|
|
44436
|
+
imageFont: null,
|
|
44437
|
+
scale: 100,
|
|
44438
|
+
showMathMenu: false
|
|
44439
|
+
}
|
|
44440
|
+
});
|
|
44441
|
+
MathJax.Hub.Configured();
|
|
44442
|
+
MathJax.Hub.Queue(resolve);
|
|
44334
44443
|
});
|
|
44335
44444
|
};
|
|
44336
44445
|
|
|
44337
|
-
//
|
|
44446
|
+
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44447
|
+
const libName = "@khanacademy/perseus";
|
|
44448
|
+
const libVersion = "27.0.0";
|
|
44449
|
+
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44338
44450
|
|
|
44339
|
-
const
|
|
44340
|
-
|
|
44341
|
-
|
|
44342
|
-
},
|
|
44343
|
-
set: function (arr, monocle, val) {
|
|
44344
|
-
var newArr = arr.splice();
|
|
44345
|
-
newArr[monocle] = val;
|
|
44346
|
-
return newArr;
|
|
44347
|
-
},
|
|
44348
|
-
mod: function (arr, monocle, f) {
|
|
44349
|
-
var newArr = arr.slice();
|
|
44350
|
-
newArr[monocle] = f(arr[monocle]);
|
|
44351
|
-
return newArr;
|
|
44352
|
-
},
|
|
44353
|
-
del: function (arr, monocle) {
|
|
44354
|
-
var newArr = arr.slice();
|
|
44355
|
-
newArr.splice(monocle, 1);
|
|
44356
|
-
return newArr;
|
|
44357
|
-
}
|
|
44451
|
+
const apiVersion = {
|
|
44452
|
+
major: 12,
|
|
44453
|
+
minor: 0
|
|
44358
44454
|
};
|
|
44359
|
-
|
|
44360
|
-
|
|
44361
|
-
|
|
44362
|
-
lens.prototype.insertAt = function(lensArr, toInsert) {
|
|
44363
|
-
var obj = this._wrapped;
|
|
44364
|
-
|
|
44365
|
-
var arrLens = lensArr.slice(0, -1);
|
|
44366
|
-
var arr = lens(obj).get(arrLens).slice(); // slice to copy
|
|
44367
|
-
|
|
44368
|
-
var arrIdx = lensArr[lensArr.length-1];
|
|
44369
|
-
arr.splice(arrIdx, 0, toInsert);
|
|
44370
|
-
return lens(obj).set(arrLens, arr);
|
|
44455
|
+
const itemDataVersion = {
|
|
44456
|
+
major: 0,
|
|
44457
|
+
minor: 1
|
|
44371
44458
|
};
|
|
44372
44459
|
|
|
44373
|
-
|
|
44374
|
-
lens.prototype.insertAfter = function(lensArr, toInsert) {
|
|
44375
|
-
var newLens = lensArr.slice();
|
|
44376
|
-
newLens[newLens.length-1] += 1;
|
|
44377
|
-
return lens(this._wrapped).insertAt(newLens, toInsert);
|
|
44378
|
-
};
|
|
44379
|
-
*/
|
|
44460
|
+
var allWidgets = [...basicWidgets, ...extraWidgets];
|
|
44380
44461
|
|
|
44381
|
-
|
|
44462
|
+
registerWidgets(allWidgets);
|
|
44463
|
+
const ItemVersion = getVersionVector();
|
|
44464
|
+
ItemVersion["::renderer::"] = itemDataVersion;
|
|
44382
44465
|
|
|
44383
|
-
|
|
44384
|
-
|
|
44385
|
-
|
|
44386
|
-
|
|
44387
|
-
|
|
44388
|
-
|
|
44389
|
-
|
|
44390
|
-
|
|
44391
|
-
}
|
|
44392
|
-
|
|
44393
|
-
|
|
44394
|
-
newObj[monocle] = f(obj[monocle]);
|
|
44395
|
-
return newObj;
|
|
44396
|
-
},
|
|
44397
|
-
del: function (obj, monocle) {
|
|
44398
|
-
var newObj = clone(obj);
|
|
44399
|
-
delete newObj[monocle];
|
|
44400
|
-
return newObj;
|
|
44466
|
+
class ArticleRenderer extends React__namespace.Component {
|
|
44467
|
+
static contextType = PerseusI18nContext;
|
|
44468
|
+
_currentFocus;
|
|
44469
|
+
sectionRenderers = [];
|
|
44470
|
+
static defaultProps = {
|
|
44471
|
+
apiOptions: ApiOptions.defaults,
|
|
44472
|
+
useNewStyles: false,
|
|
44473
|
+
linterContext: PerseusLinter__namespace.linterContextDefault
|
|
44474
|
+
};
|
|
44475
|
+
constructor(props) {
|
|
44476
|
+
super(props);
|
|
44401
44477
|
}
|
|
44402
|
-
|
|
44403
|
-
|
|
44404
|
-
// STRING FUNCTIONS
|
|
44405
|
-
|
|
44406
|
-
const str = {
|
|
44407
|
-
get: function (arr, monocle) {
|
|
44408
|
-
return arr[monocle];
|
|
44409
|
-
},
|
|
44410
|
-
set: function (arr, monocle, val) {
|
|
44411
|
-
var newArr = arr.splice();
|
|
44412
|
-
newArr[monocle] = val;
|
|
44413
|
-
return newArr;
|
|
44414
|
-
},
|
|
44415
|
-
mod: function (arr, monocle, f) {
|
|
44416
|
-
var newArr = arr.splice();
|
|
44417
|
-
newArr[monocle] = f(arr[monocle]);
|
|
44418
|
-
return newArr;
|
|
44419
|
-
},
|
|
44420
|
-
del: function (arr, monocle) {
|
|
44421
|
-
var newArr = arr.slice();
|
|
44422
|
-
newArr.splice(monocle);
|
|
44423
|
-
return newArr;
|
|
44478
|
+
componentDidMount() {
|
|
44479
|
+
this._currentFocus = null;
|
|
44424
44480
|
}
|
|
44425
|
-
|
|
44426
|
-
|
|
44427
|
-
|
|
44428
|
-
|
|
44429
|
-
|
|
44430
|
-
|
|
44431
|
-
|
|
44432
|
-
|
|
44433
|
-
|
|
44434
|
-
|
|
44435
|
-
|
|
44436
|
-
|
|
44437
|
-
|
|
44438
|
-
|
|
44439
|
-
|
|
44440
|
-
|
|
44441
|
-
|
|
44442
|
-
|
|
44481
|
+
shouldComponentUpdate(nextProps) {
|
|
44482
|
+
return nextProps !== this.props;
|
|
44483
|
+
}
|
|
44484
|
+
_handleFocusChange = (newFocusPath, oldFocusPath) => {
|
|
44485
|
+
// TODO(charlie): DRY this up--some of this logic is repeated in
|
|
44486
|
+
// ItemRenderer.
|
|
44487
|
+
if (newFocusPath) {
|
|
44488
|
+
this._setCurrentFocus(newFocusPath);
|
|
44489
|
+
} else {
|
|
44490
|
+
this._onRendererBlur(oldFocusPath);
|
|
44491
|
+
}
|
|
44492
|
+
};
|
|
44493
|
+
_setCurrentFocus = newFocusPath => {
|
|
44494
|
+
const {
|
|
44495
|
+
keypadElement,
|
|
44496
|
+
apiOptions
|
|
44497
|
+
} = this.props;
|
|
44498
|
+
const {
|
|
44499
|
+
isMobile
|
|
44500
|
+
} = apiOptions;
|
|
44501
|
+
const prevFocusPath = this._currentFocus;
|
|
44502
|
+
this._currentFocus = newFocusPath;
|
|
44443
44503
|
|
|
44444
|
-
|
|
44445
|
-
|
|
44446
|
-
|
|
44447
|
-
|
|
44448
|
-
|
|
44449
|
-
|
|
44450
|
-
|
|
44451
|
-
|
|
44452
|
-
|
|
44453
|
-
|
|
44454
|
-
|
|
44455
|
-
|
|
44456
|
-
|
|
44457
|
-
|
|
44458
|
-
|
|
44459
|
-
|
|
44460
|
-
|
|
44461
|
-
|
|
44504
|
+
// Use the section prefix to extract the relevant Renderer's input
|
|
44505
|
+
// paths, so as to check whether the focused path represents an
|
|
44506
|
+
// input.
|
|
44507
|
+
let didFocusInput = false;
|
|
44508
|
+
let focusedInput;
|
|
44509
|
+
if (this._currentFocus) {
|
|
44510
|
+
const [sectionIndex, ...focusPath] = this._currentFocus;
|
|
44511
|
+
const inputPaths = this.sectionRenderers[sectionIndex].getInputPaths();
|
|
44512
|
+
didFocusInput = inputPaths.some(inputPath => {
|
|
44513
|
+
return Util.inputPathsEqual(inputPath, focusPath);
|
|
44514
|
+
});
|
|
44515
|
+
focusedInput = this.sectionRenderers[sectionIndex].getDOMNodeForPath(focusPath);
|
|
44516
|
+
}
|
|
44517
|
+
const {
|
|
44518
|
+
onFocusChange
|
|
44519
|
+
} = this.props.apiOptions;
|
|
44520
|
+
if (onFocusChange) {
|
|
44521
|
+
// Wait for the keypad to mount before getting the height
|
|
44522
|
+
setTimeout(() => {
|
|
44523
|
+
const keypadDomNode = keypadElement?.getDOMNode();
|
|
44524
|
+
const keypadHeight = keypadDomNode && didFocusInput ? keypadDomNode.getBoundingClientRect().height : 0;
|
|
44525
|
+
onFocusChange(this._currentFocus, prevFocusPath, keypadHeight, didFocusInput ? focusedInput : null);
|
|
44526
|
+
}, 0);
|
|
44527
|
+
}
|
|
44528
|
+
if (keypadElement && isMobile) {
|
|
44529
|
+
if (didFocusInput) {
|
|
44530
|
+
keypadElement.activate();
|
|
44531
|
+
} else {
|
|
44532
|
+
keypadElement.dismiss();
|
|
44533
|
+
}
|
|
44534
|
+
}
|
|
44535
|
+
};
|
|
44536
|
+
_onRendererBlur = blurPath => {
|
|
44537
|
+
const blurringFocusPath = this._currentFocus;
|
|
44462
44538
|
|
|
44463
|
-
|
|
44464
|
-
|
|
44465
|
-
|
|
44466
|
-
|
|
44467
|
-
|
|
44468
|
-
* used in multiplication.
|
|
44469
|
-
*
|
|
44470
|
-
* @return the number of significant figures.
|
|
44471
|
-
*/
|
|
44472
|
-
this.sigFigs = this.mantissa.length;
|
|
44473
|
-
|
|
44474
|
-
/**
|
|
44475
|
-
* Get the least significant decimal this object uses.
|
|
44476
|
-
* This is useful to know if a number is being used
|
|
44477
|
-
* in addition.
|
|
44478
|
-
* 400 - 2 the hundreds place (10^2) is the least significant decimal.
|
|
44479
|
-
* 75 - 0 the ones place (10^0) is the least significant decimal.
|
|
44480
|
-
* .543 - -3 the 1/1000's place (10^-3) is the least significant decimal.
|
|
44481
|
-
*
|
|
44482
|
-
* @return an integer representing the least significant decimal place.
|
|
44483
|
-
*/
|
|
44484
|
-
this.sigDecs = this.order - this.mantissa.length;
|
|
44485
|
-
}
|
|
44486
|
-
}
|
|
44487
|
-
|
|
44488
|
-
/**
|
|
44489
|
-
* Format a floating point for display using the specified
|
|
44490
|
-
* number of significant figures and least significant decimal.
|
|
44491
|
-
* Scientific notation may used by this method if this
|
|
44492
|
-
* object is very small, very large, has many significant
|
|
44493
|
-
* figures, or the number of significant figures would be
|
|
44494
|
-
* ambiguous in the output if scientific notation were not
|
|
44495
|
-
* used.
|
|
44496
|
-
*
|
|
44497
|
-
* @param f A floating point number that should be displayed
|
|
44498
|
-
* @param sigFigs desired number of significant figures (integer).
|
|
44499
|
-
* @param sigDecs the least significant decimal place (integer).
|
|
44500
|
-
* @param scientific true iff scientific notation should always be used.
|
|
44501
|
-
* @return a string of this object formatted correctly.
|
|
44502
|
-
*/
|
|
44503
|
-
function displaySigFigs(f, sigFigs, sigDecs, scientific) {
|
|
44504
|
-
const s = "" + f;
|
|
44505
|
-
let order = parseOrder(s);
|
|
44506
|
-
let mantissa = parseMantissa(s);
|
|
44507
|
-
let positive = parseSign(s);
|
|
44508
|
-
let add;
|
|
44509
|
-
let decAdd;
|
|
44510
|
-
let sigAdd;
|
|
44511
|
-
let zeroScientific = false;
|
|
44512
|
-
if (f == 0 || mantissa == "" || mantissa == "0") {
|
|
44513
|
-
mantissa = "";
|
|
44514
|
-
for (let i = 0; i < sigFigs; i++) {
|
|
44515
|
-
mantissa += "0";
|
|
44516
|
-
}
|
|
44517
|
-
order = sigFigs + sigDecs;
|
|
44518
|
-
if (sigDecs < 0 && -sigDecs >= sigFigs) {
|
|
44519
|
-
zeroScientific = true;
|
|
44520
|
-
}
|
|
44521
|
-
} else {
|
|
44522
|
-
decAdd = order - mantissa.length - sigDecs;
|
|
44523
|
-
sigAdd = sigFigs - mantissa.length;
|
|
44524
|
-
add = Math.min(sigAdd, decAdd);
|
|
44525
|
-
if (add < 0) {
|
|
44526
|
-
let rounded = round(mantissa, -add);
|
|
44527
|
-
if (rounded.length > mantissa.length + add) {
|
|
44528
|
-
order++;
|
|
44529
|
-
if (decAdd > sigAdd) {
|
|
44530
|
-
rounded = round(rounded, 1);
|
|
44531
|
-
}
|
|
44532
|
-
}
|
|
44533
|
-
mantissa = rounded;
|
|
44534
|
-
} else if (add > 0) {
|
|
44535
|
-
for (let i = 0; i < add; i++) {
|
|
44536
|
-
mantissa += "0";
|
|
44537
|
-
}
|
|
44538
|
-
}
|
|
44539
|
-
if (mantissa == "" || mantissa == "0") {
|
|
44540
|
-
mantissa = "0";
|
|
44541
|
-
positive = true;
|
|
44542
|
-
order = 1 + sigDecs;
|
|
44543
|
-
if (order != 0) {
|
|
44544
|
-
zeroScientific = true;
|
|
44545
|
-
}
|
|
44546
|
-
}
|
|
44547
|
-
}
|
|
44548
|
-
const useScientific = scientific || mantissa.length > 20 || order > 4 || order < -2 || order - mantissa.length > 0 && trailingZeros(mantissa) > 0 || zeroScientific;
|
|
44549
|
-
let returnVal = "";
|
|
44550
|
-
if (!positive) {
|
|
44551
|
-
returnVal += "-";
|
|
44552
|
-
}
|
|
44553
|
-
if (useScientific) {
|
|
44554
|
-
returnVal += mantissa.charAt(0);
|
|
44555
|
-
if (mantissa.length > 1) {
|
|
44556
|
-
returnVal += "." + mantissa.substring(1, mantissa.length);
|
|
44557
|
-
}
|
|
44558
|
-
if (order - 1 != 0) {
|
|
44559
|
-
returnVal += " x 10^" + (order - 1);
|
|
44560
|
-
}
|
|
44561
|
-
} else {
|
|
44562
|
-
let wholePart = "";
|
|
44563
|
-
let fractPart = "";
|
|
44564
|
-
let needDot = true;
|
|
44565
|
-
if (order > 0) {
|
|
44566
|
-
if (mantissa.length > order) {
|
|
44567
|
-
wholePart = mantissa.substring(0, order);
|
|
44568
|
-
fractPart = mantissa.substring(order, mantissa.length);
|
|
44569
|
-
} else {
|
|
44570
|
-
wholePart = mantissa;
|
|
44571
|
-
needDot = trailingZeros(mantissa) != 0;
|
|
44572
|
-
for (let i = 0; i < order - mantissa.length; i++) {
|
|
44573
|
-
wholePart += "0";
|
|
44574
|
-
}
|
|
44575
|
-
}
|
|
44576
|
-
} else {
|
|
44577
|
-
for (let i = 0; i < -order; i++) {
|
|
44578
|
-
fractPart += "0";
|
|
44579
|
-
}
|
|
44580
|
-
fractPart += mantissa;
|
|
44581
|
-
}
|
|
44582
|
-
returnVal += (wholePart == "" ? "0" : wholePart) + (needDot ? "." : "") + fractPart;
|
|
44583
|
-
}
|
|
44584
|
-
return returnVal;
|
|
44585
|
-
}
|
|
44586
|
-
|
|
44587
|
-
/**
|
|
44588
|
-
* Count the significant trailing zeros on this object.
|
|
44589
|
-
*
|
|
44590
|
-
* @return the number of trailing zeros
|
|
44591
|
-
*/
|
|
44592
|
-
function trailingZeros(mantissa) {
|
|
44593
|
-
let zeros = 0;
|
|
44594
|
-
for (let i = mantissa.length - 1; i >= 0; i--) {
|
|
44595
|
-
const c = mantissa.charAt(i);
|
|
44596
|
-
if (c == "0") {
|
|
44597
|
-
zeros++;
|
|
44598
|
-
} else {
|
|
44599
|
-
return zeros;
|
|
44600
|
-
}
|
|
44601
|
-
}
|
|
44602
|
-
return zeros;
|
|
44603
|
-
}
|
|
44604
|
-
|
|
44605
|
-
/**
|
|
44606
|
-
* Parse a string representation of a floating point
|
|
44607
|
-
* and pull out the sign.
|
|
44608
|
-
*
|
|
44609
|
-
* @param s the string representation of a floating point.
|
|
44610
|
-
* @return true iff this is a positive number
|
|
44611
|
-
*/
|
|
44612
|
-
function parseSign(s) {
|
|
44613
|
-
let beginning = true;
|
|
44614
|
-
let seenDot = false;
|
|
44615
|
-
let seenSomething = false;
|
|
44616
|
-
let zeros = "";
|
|
44617
|
-
let all = "";
|
|
44618
|
-
let pos = true;
|
|
44619
|
-
for (let i = 0; i < s.length; i++) {
|
|
44620
|
-
const c = s.charAt(i);
|
|
44621
|
-
if (c >= "1" && c <= "9") {
|
|
44622
|
-
all += zeros + c;
|
|
44623
|
-
zeros = "";
|
|
44624
|
-
seenSomething = true;
|
|
44625
|
-
beginning = false;
|
|
44626
|
-
} else if (c == "0") {
|
|
44627
|
-
if (seenDot) {
|
|
44628
|
-
if (seenSomething) {
|
|
44629
|
-
all += zeros + c;
|
|
44630
|
-
zeros = "";
|
|
44631
|
-
}
|
|
44632
|
-
} else {
|
|
44633
|
-
if (seenSomething) {
|
|
44634
|
-
zeros += c;
|
|
44635
|
-
}
|
|
44636
|
-
}
|
|
44637
|
-
beginning = false;
|
|
44638
|
-
} else if (!seenDot && c == ".") {
|
|
44639
|
-
all += zeros;
|
|
44640
|
-
zeros = "";
|
|
44641
|
-
seenDot = true;
|
|
44642
|
-
beginning = false;
|
|
44643
|
-
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
44644
|
-
parseInt(s.substring(i + 1, s.length));
|
|
44645
|
-
i = s.length;
|
|
44646
|
-
} else if (beginning && (c == "+" || c == "-")) {
|
|
44647
|
-
if (c == "-") {
|
|
44648
|
-
pos = !pos;
|
|
44649
|
-
}
|
|
44650
|
-
}
|
|
44651
|
-
}
|
|
44652
|
-
if (all == "") {
|
|
44653
|
-
return true;
|
|
44654
|
-
}
|
|
44655
|
-
return pos;
|
|
44656
|
-
}
|
|
44657
|
-
|
|
44658
|
-
/**
|
|
44659
|
-
* Parse a string representation of a floating point
|
|
44660
|
-
* and pull out the mantissa.
|
|
44661
|
-
*
|
|
44662
|
-
* @param s the string representation of a floating point.
|
|
44663
|
-
* @return the mantissa of this number.
|
|
44664
|
-
*/
|
|
44665
|
-
function parseMantissa(s) {
|
|
44666
|
-
let seenDot = false;
|
|
44667
|
-
let seenSomething = false;
|
|
44668
|
-
let zeros = "";
|
|
44669
|
-
let leadZeros = "";
|
|
44670
|
-
let all = "";
|
|
44671
|
-
for (let i = 0; i < s.length; i++) {
|
|
44672
|
-
const c = s.charAt(i);
|
|
44673
|
-
if (c >= "1" && c <= "9") {
|
|
44674
|
-
all += zeros + c;
|
|
44675
|
-
zeros = "";
|
|
44676
|
-
seenSomething = true;
|
|
44677
|
-
} else if (c == "0") {
|
|
44678
|
-
if (seenDot) {
|
|
44679
|
-
if (seenSomething) {
|
|
44680
|
-
all += zeros + c;
|
|
44681
|
-
zeros = "";
|
|
44682
|
-
} else {
|
|
44683
|
-
leadZeros += c;
|
|
44684
|
-
}
|
|
44685
|
-
} else {
|
|
44686
|
-
if (seenSomething) {
|
|
44687
|
-
leadZeros += c;
|
|
44688
|
-
zeros += c;
|
|
44689
|
-
} else {
|
|
44690
|
-
leadZeros += c;
|
|
44691
|
-
}
|
|
44692
|
-
}
|
|
44693
|
-
} else if (!seenDot && c == ".") {
|
|
44694
|
-
all += zeros;
|
|
44695
|
-
zeros = "";
|
|
44696
|
-
seenDot = true;
|
|
44697
|
-
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
44698
|
-
parseInt(s.substring(i + 1, s.length));
|
|
44699
|
-
i = s.length;
|
|
44700
|
-
} else ;
|
|
44701
|
-
}
|
|
44702
|
-
if (all == "") {
|
|
44703
|
-
return leadZeros;
|
|
44704
|
-
}
|
|
44705
|
-
return all;
|
|
44706
|
-
}
|
|
44707
|
-
|
|
44708
|
-
/**
|
|
44709
|
-
* Parse a string representation of a floating point
|
|
44710
|
-
* and pull out the exponent.
|
|
44711
|
-
*
|
|
44712
|
-
* @param s the string representation of a floating point.
|
|
44713
|
-
* @return (integer) the number after the e.
|
|
44714
|
-
*/
|
|
44715
|
-
function parseOrder(s) {
|
|
44716
|
-
let seenDot = false;
|
|
44717
|
-
let seenSomething = false;
|
|
44718
|
-
let zeros = "";
|
|
44719
|
-
let all = "";
|
|
44720
|
-
let decPlaces = 0;
|
|
44721
|
-
let totalDecs = 0;
|
|
44722
|
-
for (let i = 0; i < s.length; i++) {
|
|
44723
|
-
const c = s.charAt(i);
|
|
44724
|
-
if (c >= "1" && c <= "9") {
|
|
44725
|
-
all += zeros + c;
|
|
44726
|
-
zeros = "";
|
|
44727
|
-
seenSomething = true;
|
|
44728
|
-
if (!seenDot) {
|
|
44729
|
-
totalDecs++;
|
|
44730
|
-
decPlaces++;
|
|
44731
|
-
}
|
|
44732
|
-
} else if (c == "0") {
|
|
44733
|
-
if (seenDot) {
|
|
44734
|
-
if (seenSomething) {
|
|
44735
|
-
all += zeros + c;
|
|
44736
|
-
zeros = "";
|
|
44737
|
-
} else {
|
|
44738
|
-
decPlaces--;
|
|
44739
|
-
}
|
|
44740
|
-
} else {
|
|
44741
|
-
totalDecs++;
|
|
44742
|
-
if (seenSomething) {
|
|
44743
|
-
decPlaces++;
|
|
44744
|
-
zeros += c;
|
|
44745
|
-
}
|
|
44746
|
-
}
|
|
44747
|
-
} else if (!seenDot && c == ".") {
|
|
44748
|
-
all += zeros;
|
|
44749
|
-
zeros = "";
|
|
44750
|
-
seenDot = true;
|
|
44751
|
-
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
44752
|
-
const raised = parseInt(s.substring(i + 1, s.length));
|
|
44753
|
-
decPlaces += raised;
|
|
44754
|
-
totalDecs += raised;
|
|
44755
|
-
i = s.length;
|
|
44756
|
-
} else ;
|
|
44757
|
-
}
|
|
44758
|
-
if (all == "") {
|
|
44759
|
-
return totalDecs;
|
|
44760
|
-
}
|
|
44761
|
-
return decPlaces;
|
|
44762
|
-
}
|
|
44763
|
-
|
|
44764
|
-
/**
|
|
44765
|
-
* Remove the specified number of digits from string by
|
|
44766
|
-
* rounding. Proper rounding rules for scientific purposes
|
|
44767
|
-
* are followed.
|
|
44768
|
-
* This method may cause an extra significant figure
|
|
44769
|
-
* to be added to the number. For example, if 999999
|
|
44770
|
-
* were rounded, A one would carry over and become
|
|
44771
|
-
* a significant figure. Those who call this method
|
|
44772
|
-
* should check for this and call round again if needed.
|
|
44773
|
-
*
|
|
44774
|
-
* @param mantissa A string representing an whole number of arbitrary length.
|
|
44775
|
-
* @param digits A number of digits to remove
|
|
44776
|
-
* @return A string represted the rounded version of mantissa
|
|
44777
|
-
*/
|
|
44778
|
-
function round(mantissa, digits) {
|
|
44779
|
-
const last = mantissa.length - digits - 1;
|
|
44780
|
-
if (last < 0) {
|
|
44781
|
-
return "";
|
|
44782
|
-
}
|
|
44783
|
-
if (last >= mantissa.length - 1) {
|
|
44784
|
-
return mantissa;
|
|
44785
|
-
}
|
|
44786
|
-
const nextToLast = mantissa.charAt(last + 1);
|
|
44787
|
-
const lastChar = mantissa.charAt(last);
|
|
44788
|
-
let roundUp = false;
|
|
44789
|
-
if (nextToLast > "5") {
|
|
44790
|
-
roundUp = true;
|
|
44791
|
-
} else if (nextToLast == "5") {
|
|
44792
|
-
for (let j = last + 2; j < mantissa.length; j++) {
|
|
44793
|
-
if (mantissa.charAt(j) != "0") {
|
|
44794
|
-
roundUp = true;
|
|
44795
|
-
}
|
|
44796
|
-
}
|
|
44797
|
-
// @ts-expect-error - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
|
|
44798
|
-
if (lastChar % 2 == 1) {
|
|
44799
|
-
roundUp = true;
|
|
44800
|
-
}
|
|
44801
|
-
}
|
|
44802
|
-
let result = "";
|
|
44803
|
-
for (let i = last; i >= 0; i--) {
|
|
44804
|
-
const c = mantissa.charAt(i);
|
|
44805
|
-
if (roundUp) {
|
|
44806
|
-
let nextChar;
|
|
44807
|
-
if (c == "9") {
|
|
44808
|
-
nextChar = "0";
|
|
44809
|
-
} else {
|
|
44810
|
-
switch (c) {
|
|
44811
|
-
case "0":
|
|
44812
|
-
nextChar = "1";
|
|
44813
|
-
break;
|
|
44814
|
-
case "1":
|
|
44815
|
-
nextChar = "2";
|
|
44816
|
-
break;
|
|
44817
|
-
case "2":
|
|
44818
|
-
nextChar = "3";
|
|
44819
|
-
break;
|
|
44820
|
-
case "3":
|
|
44821
|
-
nextChar = "4";
|
|
44822
|
-
break;
|
|
44823
|
-
case "4":
|
|
44824
|
-
nextChar = "5";
|
|
44825
|
-
break;
|
|
44826
|
-
case "5":
|
|
44827
|
-
nextChar = "6";
|
|
44828
|
-
break;
|
|
44829
|
-
case "6":
|
|
44830
|
-
nextChar = "7";
|
|
44831
|
-
break;
|
|
44832
|
-
case "7":
|
|
44833
|
-
nextChar = "8";
|
|
44834
|
-
break;
|
|
44835
|
-
case "8":
|
|
44836
|
-
nextChar = "9";
|
|
44837
|
-
break;
|
|
44838
|
-
}
|
|
44839
|
-
roundUp = false;
|
|
44840
|
-
}
|
|
44841
|
-
result = nextChar + result;
|
|
44842
|
-
} else {
|
|
44843
|
-
result = c + result;
|
|
44844
|
-
}
|
|
44845
|
-
}
|
|
44846
|
-
if (roundUp) {
|
|
44847
|
-
result = "1" + result;
|
|
44848
|
-
}
|
|
44849
|
-
return result;
|
|
44850
|
-
}
|
|
44851
|
-
|
|
44852
|
-
// TODO(joel): teach KAS how to accept an answer only if it's expressed in
|
|
44853
|
-
const ALL = "all";
|
|
44854
|
-
const countSigfigs = function (value) {
|
|
44855
|
-
return new SignificantFigures(value).sigFigs;
|
|
44856
|
-
};
|
|
44857
|
-
/* I just wrote this, but it's old by analogy to `OldExpression`, in that it's
|
|
44858
|
-
* the version that non-mathquill platforms get stuck with. Constructed with an
|
|
44859
|
-
* <input>, a parser, popsicle sticks, and glue.
|
|
44860
|
-
*
|
|
44861
|
-
* In the same way as OldExpression, this parses continuously as you type, then
|
|
44862
|
-
* shows and hides an error buddy. The error message is only shown after a
|
|
44863
|
-
* rolling two second delay, but hidden immediately on further typing.
|
|
44864
|
-
*/
|
|
44865
|
-
class OldUnitInput extends React__namespace.Component {
|
|
44866
|
-
static contextType = PerseusI18nContext;
|
|
44867
|
-
_errorTimeout;
|
|
44868
|
-
static defaultProps = {
|
|
44869
|
-
apiOptions: ApiOptions.defaults,
|
|
44870
|
-
value: ""
|
|
44871
|
-
};
|
|
44872
|
-
static validate(userInput, rubric, strings) {
|
|
44873
|
-
const answer = KAS__namespace.unitParse(rubric.value).expr;
|
|
44874
|
-
const guess = KAS__namespace.unitParse(userInput);
|
|
44875
|
-
if (!guess.parsed) {
|
|
44876
|
-
return {
|
|
44877
|
-
type: "invalid",
|
|
44878
|
-
message: strings.dontUnderstandUnits
|
|
44879
|
-
};
|
|
44880
|
-
}
|
|
44881
|
-
|
|
44882
|
-
// Note: we check sigfigs, then numerical correctness, then units, so
|
|
44883
|
-
// the most significant things come last, that way the user will see
|
|
44884
|
-
// the most important message.
|
|
44885
|
-
let message = null;
|
|
44886
|
-
|
|
44887
|
-
// did the user specify the right number of sigfigs?
|
|
44888
|
-
// TODO(joel) - add a grading mode where the wrong number of sigfigs
|
|
44889
|
-
// isn't marked wrong
|
|
44890
|
-
const sigfigs = rubric.sigfigs;
|
|
44891
|
-
const sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
|
|
44892
|
-
if (!sigfigsCorrect) {
|
|
44893
|
-
message = strings.checkSigFigs;
|
|
44894
|
-
}
|
|
44895
|
-
|
|
44896
|
-
// now we need to check that the answer is correct to the precision we
|
|
44897
|
-
// require.
|
|
44898
|
-
let numericallyCorrect;
|
|
44899
|
-
try {
|
|
44900
|
-
const x = new KAS__namespace.Var("x");
|
|
44901
|
-
const equality = new KAS__namespace.Eq(answer.simplify(), "=", new KAS__namespace.Mul(x, guess.expr.simplify()));
|
|
44902
|
-
const conversion = equality.solveLinearEquationForVariable(x);
|
|
44903
|
-
|
|
44904
|
-
// Make sure the conversion factor between the user's input answer
|
|
44905
|
-
// and the canonical answer is 1, to sigfig places.
|
|
44906
|
-
// TODO(joel) is this sound?
|
|
44907
|
-
numericallyCorrect = Number(conversion.eval()).toPrecision(sigfigs) === Number(1).toPrecision(sigfigs);
|
|
44908
|
-
} catch (e) {
|
|
44909
|
-
numericallyCorrect = false;
|
|
44910
|
-
}
|
|
44911
|
-
if (!numericallyCorrect) {
|
|
44912
|
-
message = strings.answerNumericallyIncorrect;
|
|
44913
|
-
}
|
|
44914
|
-
let kasCorrect;
|
|
44915
|
-
const guessUnit = primUnits(guess.expr.simplify());
|
|
44916
|
-
const answerUnit = primUnits(answer.simplify());
|
|
44917
|
-
if (rubric.accepting === ALL) {
|
|
44918
|
-
// We're accepting all units - KAS does the hard work of figuring
|
|
44919
|
-
// out if the user's unit is equivalent to the author's unit.
|
|
44920
|
-
kasCorrect = KAS__namespace.compare(guessUnit, answerUnit).equal;
|
|
44921
|
-
} else {
|
|
44922
|
-
// Are any of the accepted units the same as what the user entered?
|
|
44923
|
-
kasCorrect = ___default["default"](rubric.acceptingUnits).any(unit => {
|
|
44924
|
-
const thisAnswerUnit = primUnits(KAS__namespace.unitParse(unit).unit.simplify());
|
|
44925
|
-
return KAS__namespace.compare(thisAnswerUnit, guessUnit
|
|
44926
|
-
// TODO(joel) - make this work as intended.
|
|
44927
|
-
// { form: true }
|
|
44928
|
-
).equal;
|
|
44929
|
-
});
|
|
44930
|
-
}
|
|
44931
|
-
if (!kasCorrect) {
|
|
44932
|
-
message = strings.checkUnits;
|
|
44933
|
-
}
|
|
44934
|
-
const correct = kasCorrect && numericallyCorrect && sigfigsCorrect;
|
|
44935
|
-
return {
|
|
44936
|
-
type: "points",
|
|
44937
|
-
earned: correct ? 1 : 0,
|
|
44938
|
-
total: 1,
|
|
44939
|
-
message
|
|
44940
|
-
};
|
|
44941
|
-
}
|
|
44942
|
-
componentDidUpdate() {
|
|
44943
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
44944
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
44945
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
44946
|
-
clearTimeout(this._errorTimeout);
|
|
44947
|
-
if (KAS__namespace.unitParse(this.props.value).parsed) {
|
|
44948
|
-
this._hideError();
|
|
44949
|
-
} else {
|
|
44950
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
44951
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
44952
|
-
// @ts-expect-error - TS2322 - Type 'Timeout' is not assignable to type 'number'.
|
|
44953
|
-
this._errorTimeout = setTimeout(this._showError, 2000);
|
|
44954
|
-
}
|
|
44955
|
-
}
|
|
44956
|
-
componentWillUnmount() {
|
|
44957
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
44958
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
44959
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
44960
|
-
clearTimeout(this._errorTimeout);
|
|
44961
|
-
}
|
|
44962
|
-
_showError = () => {
|
|
44963
|
-
if (this.props.value === "") {
|
|
44964
|
-
return;
|
|
44965
|
-
}
|
|
44966
|
-
|
|
44967
|
-
// eslint-disable-next-line react/no-string-refs
|
|
44968
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
44969
|
-
const $error = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.error));
|
|
44970
|
-
// @ts-expect-error - TS2339 - Property 'is' does not exist on type 'JQueryStatic'.
|
|
44971
|
-
if (!$error.is(":visible")) {
|
|
44972
|
-
$error
|
|
44973
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
44974
|
-
.css({
|
|
44975
|
-
top: 50,
|
|
44976
|
-
opacity: 0.1
|
|
44977
|
-
}).show().animate({
|
|
44978
|
-
top: 0,
|
|
44979
|
-
opacity: 1.0
|
|
44980
|
-
}, 300);
|
|
44981
|
-
}
|
|
44982
|
-
};
|
|
44983
|
-
_hideError = () => {
|
|
44984
|
-
// eslint-disable-next-line react/no-string-refs
|
|
44985
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
44986
|
-
const $error = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.error));
|
|
44987
|
-
// @ts-expect-error - TS2339 - Property 'is' does not exist on type 'JQueryStatic'.
|
|
44988
|
-
if ($error.is(":visible")) {
|
|
44989
|
-
// @ts-expect-error - TS2339 - Property 'animate' does not exist on type 'JQueryStatic'.
|
|
44990
|
-
$error.animate({
|
|
44991
|
-
top: 50,
|
|
44992
|
-
opacity: 0.1
|
|
44993
|
-
}, 300, function () {
|
|
44994
|
-
// eslint-disable-next-line @babel/no-invalid-this
|
|
44995
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
44996
|
-
// eslint-disable-next-line @babel/no-invalid-this
|
|
44997
|
-
$__default["default"](this).hide();
|
|
44998
|
-
});
|
|
44999
|
-
}
|
|
45000
|
-
};
|
|
45001
|
-
change = (() => {
|
|
45002
|
-
var _this = this;
|
|
45003
|
-
return function () {
|
|
45004
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
45005
|
-
args[_key] = arguments[_key];
|
|
45006
|
-
}
|
|
45007
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly any[]' is not assignable to parameter of type 'any[]'.
|
|
45008
|
-
return change.apply(_this, args);
|
|
45009
|
-
};
|
|
45010
|
-
})();
|
|
45011
|
-
handleBlur = () => {
|
|
45012
|
-
this.props.onBlur([]);
|
|
45013
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
|
|
45014
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
45015
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
45016
|
-
clearTimeout(this._errorTimeout);
|
|
45017
|
-
if (!KAS__namespace.unitParse(this.props.value).parsed) {
|
|
45018
|
-
this._showError();
|
|
45019
|
-
}
|
|
45020
|
-
};
|
|
45021
|
-
handleChange = event => {
|
|
45022
|
-
this._hideError();
|
|
45023
|
-
this.props.onChange({
|
|
45024
|
-
value: event.target.value
|
|
45025
|
-
});
|
|
45026
|
-
};
|
|
45027
|
-
simpleValidate = (rubric, onInputError) => {
|
|
45028
|
-
return OldUnitInput.validate(this.getUserInput(), rubric, this.context.strings);
|
|
45029
|
-
};
|
|
45030
|
-
getUserInput = () => {
|
|
45031
|
-
return this.props.value;
|
|
45032
|
-
};
|
|
45033
|
-
|
|
45034
|
-
// begin mobile stuff
|
|
45035
|
-
|
|
45036
|
-
getInputPaths = () => {
|
|
45037
|
-
// The widget itself is an input, so we return a single empty list to
|
|
45038
|
-
// indicate this.
|
|
45039
|
-
return [[]];
|
|
45040
|
-
};
|
|
45041
|
-
focusInputPath = inputPath => {
|
|
45042
|
-
// eslint-disable-next-line react/no-string-refs
|
|
45043
|
-
const input = ReactDOM__default["default"].findDOMNode(this.refs.input);
|
|
45044
|
-
if (input) {
|
|
45045
|
-
/**
|
|
45046
|
-
* TODO(somewhatabstract, JIRA-XXXX):
|
|
45047
|
-
* Change to using a ref callback so that focus() can be
|
|
45048
|
-
* accessed.
|
|
45049
|
-
*/
|
|
45050
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
|
|
45051
|
-
input.focus();
|
|
45052
|
-
}
|
|
45053
|
-
};
|
|
45054
|
-
handleFocus = () => {
|
|
45055
|
-
this.props.onFocus([]);
|
|
45056
|
-
};
|
|
45057
|
-
blurInputPath = inputPath => {
|
|
45058
|
-
// eslint-disable-next-line react/no-string-refs
|
|
45059
|
-
const input = ReactDOM__default["default"].findDOMNode(this.refs.input);
|
|
45060
|
-
if (input) {
|
|
45061
|
-
/**
|
|
45062
|
-
* TODO(somewhatabstract, JIRA-XXXX):
|
|
45063
|
-
* Change to using a ref callback so that focus() can be
|
|
45064
|
-
* accessed.
|
|
45065
|
-
*/
|
|
45066
|
-
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'Element | Text'.
|
|
45067
|
-
input.blur();
|
|
45068
|
-
}
|
|
45069
|
-
};
|
|
45070
|
-
setInputValue = (path, newValue, cb) => {
|
|
45071
|
-
this.props.onChange({
|
|
45072
|
-
value: newValue
|
|
45073
|
-
}, cb);
|
|
45074
|
-
};
|
|
45075
|
-
getDOMNodeForPath = () => {
|
|
45076
|
-
// eslint-disable-next-line react/no-string-refs
|
|
45077
|
-
return ReactDOM__default["default"].findDOMNode(this.refs.input);
|
|
45078
|
-
};
|
|
45079
|
-
getGrammarTypeForPath = inputPath => {
|
|
45080
|
-
return "unit";
|
|
45081
|
-
};
|
|
45082
|
-
|
|
45083
|
-
// end mobile stuff
|
|
45084
|
-
|
|
45085
|
-
// TODO(joel) think about showing the error buddy
|
|
45086
|
-
render() {
|
|
45087
|
-
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
45088
|
-
className: "old-unit-input"
|
|
45089
|
-
}, /*#__PURE__*/React__namespace.createElement("input", {
|
|
45090
|
-
onChange: this.handleChange,
|
|
45091
|
-
ref: "input" // eslint-disable-line react/no-string-refs
|
|
45092
|
-
,
|
|
45093
|
-
className: ClassNames.INTERACTIVE,
|
|
45094
|
-
value: this.props.value,
|
|
45095
|
-
onFocus: this.handleFocus,
|
|
45096
|
-
onBlur: this.handleBlur
|
|
45097
|
-
}), /*#__PURE__*/React__namespace.createElement("div", {
|
|
45098
|
-
ref: "error",
|
|
45099
|
-
className: "error",
|
|
45100
|
-
style: {
|
|
45101
|
-
display: "none"
|
|
45102
|
-
}
|
|
45103
|
-
}, this.context.strings.dontUnderstand));
|
|
45104
|
-
}
|
|
45105
|
-
}
|
|
45106
|
-
|
|
45107
|
-
// Extract the primitive units from a unit expression. This first simplifies
|
|
45108
|
-
// `expr` to a `Mul` like "5 kg m / s^2" then removes the first term.
|
|
45109
|
-
const primUnits = function (expr) {
|
|
45110
|
-
return expr.simplify().asMul().partition()[1].flatten().simplify();
|
|
45111
|
-
};
|
|
45112
|
-
var Unit = {
|
|
45113
|
-
name: "unit-input",
|
|
45114
|
-
displayName: "Unit input",
|
|
45115
|
-
defaultAlignment: "inline-block",
|
|
45116
|
-
widget: OldUnitInput,
|
|
45117
|
-
transform: x => lens(x).del(["value"]).freeze(),
|
|
45118
|
-
version: {
|
|
45119
|
-
major: 0,
|
|
45120
|
-
minor: 1
|
|
45121
|
-
},
|
|
45122
|
-
hidden: true
|
|
45123
|
-
};
|
|
45124
|
-
|
|
45125
|
-
/**
|
|
45126
|
-
* This is a component that adds a link to the transcript for embedded videos in articles.
|
|
45127
|
-
*/
|
|
45128
|
-
const IS_URL$1 = /^https?:\/\//;
|
|
45129
|
-
const getYoutubeId = url => {
|
|
45130
|
-
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
45131
|
-
const match = url.match(regExp);
|
|
45132
|
-
if (match && match[7].length === 11) {
|
|
45133
|
-
return match[7];
|
|
45134
|
-
}
|
|
45135
|
-
return "videoNotFound";
|
|
45136
|
-
};
|
|
45137
|
-
|
|
45138
|
-
// will accept a youtube link or a video's slug name from embedded video and then query accordingly.
|
|
45139
|
-
|
|
45140
|
-
/**
|
|
45141
|
-
* Video Transcript Link Component.
|
|
45142
|
-
*/
|
|
45143
|
-
const VideoTranscriptLink = props => {
|
|
45144
|
-
const {
|
|
45145
|
-
location
|
|
45146
|
-
} = props;
|
|
45147
|
-
const {
|
|
45148
|
-
useVideo
|
|
45149
|
-
} = getDependencies();
|
|
45150
|
-
const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
|
|
45151
|
-
// The result value conforms to the wonder-blocks-data `Result` type
|
|
45152
|
-
// which is used by our GraphQL framework.
|
|
45153
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
|
|
45154
|
-
const result = useVideo(id, kind);
|
|
45155
|
-
const {
|
|
45156
|
-
strings
|
|
45157
|
-
} = usePerseusI18n();
|
|
45158
|
-
switch (result.status) {
|
|
45159
|
-
case "loading":
|
|
45160
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
|
|
45161
|
-
case "success":
|
|
45162
|
-
{
|
|
45163
|
-
const video = result.data?.video;
|
|
45164
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
45165
|
-
style: styles$4.transcriptLink
|
|
45166
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Text, null, video?.title), /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
|
|
45167
|
-
size: 10
|
|
45168
|
-
}), /*#__PURE__*/React__namespace.createElement(Link__default["default"], {
|
|
45169
|
-
href: "/transcript/" + (video?.contentId || "videoNotFound"),
|
|
45170
|
-
target: "_blank",
|
|
45171
|
-
className: "visited-no-recolor"
|
|
45172
|
-
}, strings.videoTranscript));
|
|
45173
|
-
}
|
|
45174
|
-
case "error":
|
|
45175
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
45176
|
-
case "aborted":
|
|
45177
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
45178
|
-
default:
|
|
45179
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
|
|
45180
|
-
}
|
|
45181
|
-
};
|
|
45182
|
-
const styles$4 = aphrodite.StyleSheet.create({
|
|
45183
|
-
transcriptLink: {
|
|
45184
|
-
flexDirection: "row",
|
|
45185
|
-
width: "100%",
|
|
45186
|
-
justifyContent: "center"
|
|
45187
|
-
}
|
|
45188
|
-
});
|
|
45189
|
-
|
|
45190
|
-
/**
|
|
45191
|
-
* This is a video widget for embedding videos in articles.
|
|
45192
|
-
*/
|
|
45193
|
-
// Current default is 720p, based on the typical videos we upload currently
|
|
45194
|
-
const DEFAULT_WIDTH = 1280;
|
|
45195
|
-
const DEFAULT_HEIGHT = 720;
|
|
45196
|
-
const KA_EMBED = "{host}/embed_video?slug={slug}" + "&internal_video_only=1";
|
|
45197
|
-
const IS_URL = /^https?:\/\//;
|
|
45198
|
-
const IS_KA_SITE = /(khanacademy\.org|localhost)/;
|
|
45199
|
-
const IS_VIMEO = /(vimeo\.com)/;
|
|
45200
|
-
|
|
45201
|
-
// exports has no 'transform'
|
|
45202
|
-
|
|
45203
|
-
/**
|
|
45204
|
-
* Video renderer.
|
|
45205
|
-
*/
|
|
45206
|
-
class Video extends React__namespace.Component {
|
|
45207
|
-
static contextType = PerseusI18nContext;
|
|
45208
|
-
/**
|
|
45209
|
-
* This is the widget's grading function.
|
|
45210
|
-
* Points for videos are tallied by the embedded video itself, in the case
|
|
45211
|
-
* of Khan Academy videos.
|
|
45212
|
-
*/
|
|
45213
|
-
static validate(userInput, rubric) {
|
|
45214
|
-
return {
|
|
45215
|
-
type: "points",
|
|
45216
|
-
earned: 0,
|
|
45217
|
-
total: 0,
|
|
45218
|
-
message: null
|
|
45219
|
-
};
|
|
45220
|
-
}
|
|
45221
|
-
getUserInput = () => {
|
|
45222
|
-
return null;
|
|
45223
|
-
};
|
|
45224
|
-
simpleValidate = rubric => {
|
|
45225
|
-
return Video.validate(null, rubric);
|
|
45226
|
-
};
|
|
45227
|
-
change = (() => {
|
|
45228
|
-
var _this = this;
|
|
45229
|
-
return function () {
|
|
45230
|
-
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
45231
|
-
args[_key] = arguments[_key];
|
|
45232
|
-
}
|
|
45233
|
-
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
|
|
45234
|
-
return change.apply(_this, args);
|
|
45235
|
-
};
|
|
45236
|
-
})();
|
|
45237
|
-
render() {
|
|
45238
|
-
const {
|
|
45239
|
-
InitialRequestUrl
|
|
45240
|
-
} = getDependencies();
|
|
45241
|
-
const location = this.props.location;
|
|
45242
|
-
if (!location) {
|
|
45243
|
-
return /*#__PURE__*/React__namespace.createElement("div", null);
|
|
45244
|
-
}
|
|
45245
|
-
let url;
|
|
45246
|
-
if (IS_URL.test(location)) {
|
|
45247
|
-
url = location;
|
|
45248
|
-
if (IS_VIMEO.test(url)) {
|
|
45249
|
-
// If this is a vimeo video, we need to add the query string
|
|
45250
|
-
// parameter "dnt" so that analytics/tracking cookies aren't set.
|
|
45251
|
-
// https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameters-overview
|
|
45252
|
-
if (url.indexOf("?") === -1) {
|
|
45253
|
-
url += "?dnt=1";
|
|
45254
|
-
} else {
|
|
45255
|
-
url += "&dnt=1";
|
|
45256
|
-
}
|
|
45257
|
-
}
|
|
45258
|
-
} else {
|
|
45259
|
-
url = KA_EMBED.replace("{slug}", location);
|
|
45260
|
-
let embedHostname = "https://www.khanacademy.org";
|
|
45261
|
-
if (IS_KA_SITE.test(InitialRequestUrl.host)) {
|
|
45262
|
-
embedHostname = InitialRequestUrl.origin;
|
|
45263
|
-
}
|
|
45264
|
-
url = url.replace("{host}", embedHostname);
|
|
45265
|
-
}
|
|
45266
|
-
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(FixedToResponsive, {
|
|
45267
|
-
width: DEFAULT_WIDTH,
|
|
45268
|
-
height: DEFAULT_HEIGHT
|
|
45269
|
-
// The key is here for the benefit of the editor, to ensure that
|
|
45270
|
-
// any changes cause a re-rendering of the frame.
|
|
45271
|
-
,
|
|
45272
|
-
key: location + this.props.alignment
|
|
45273
|
-
}, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
45274
|
-
style: a11y.srOnly
|
|
45275
|
-
}, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
|
|
45276
|
-
// TODO(joshuan): Consider not using iframes when we're
|
|
45277
|
-
// loading this from webapp. This iframe is problematic
|
|
45278
|
-
// for screenreaders.
|
|
45279
|
-
className: "perseus-video-widget",
|
|
45280
|
-
sandbox: "allow-same-origin allow-scripts",
|
|
45281
|
-
width: DEFAULT_WIDTH,
|
|
45282
|
-
height: DEFAULT_HEIGHT,
|
|
45283
|
-
src: url,
|
|
45284
|
-
allowFullScreen: true
|
|
45285
|
-
})), /*#__PURE__*/React__namespace.createElement(VideoTranscriptLink, {
|
|
45286
|
-
location: location
|
|
45287
|
-
}));
|
|
45288
|
-
}
|
|
45289
|
-
}
|
|
45290
|
-
var Video$1 = {
|
|
45291
|
-
name: "video",
|
|
45292
|
-
displayName: "Video",
|
|
45293
|
-
defaultAlignment: "block",
|
|
45294
|
-
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
45295
|
-
widget: Video
|
|
45296
|
-
};
|
|
45297
|
-
|
|
45298
|
-
/* eslint-disable import/no-named-as-default */
|
|
45299
|
-
var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Unit, Video$1, DeprecatedStandin$1];
|
|
45300
|
-
|
|
45301
|
-
/**
|
|
45302
|
-
* This should be called by all clients, specifying whether extra widgets are
|
|
45303
|
-
* needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
|
|
45304
|
-
* call it multiple times.
|
|
45305
|
-
*
|
|
45306
|
-
* skipMathJax:
|
|
45307
|
-
* If false/undefined, MathJax will be configured, and the
|
|
45308
|
-
* promise will wait for MathJax to load (if it hasn't already).
|
|
45309
|
-
*/
|
|
45310
|
-
const init = function (options) {
|
|
45311
|
-
registerWidgets(basicWidgets);
|
|
45312
|
-
registerWidgets(extraWidgets);
|
|
45313
|
-
replaceDeprecatedWidgets();
|
|
45314
|
-
|
|
45315
|
-
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
45316
|
-
const skipMathJax = options.skipMathJax;
|
|
45317
|
-
if (skipMathJax) {
|
|
45318
|
-
// @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
|
|
45319
|
-
return Promise.resolve();
|
|
45320
|
-
}
|
|
45321
|
-
return new Promise((resolve, reject) => {
|
|
45322
|
-
MathJax.Hub.Config({
|
|
45323
|
-
messageStyle: "none",
|
|
45324
|
-
skipStartupTypeset: "none",
|
|
45325
|
-
"HTML-CSS": {
|
|
45326
|
-
availableFonts: ["TeX"],
|
|
45327
|
-
imageFont: null,
|
|
45328
|
-
scale: 100,
|
|
45329
|
-
showMathMenu: false
|
|
45330
|
-
}
|
|
45331
|
-
});
|
|
45332
|
-
MathJax.Hub.Configured();
|
|
45333
|
-
MathJax.Hub.Queue(resolve);
|
|
45334
|
-
});
|
|
45335
|
-
};
|
|
45336
|
-
|
|
45337
|
-
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45338
|
-
const libName = "@khanacademy/perseus";
|
|
45339
|
-
const libVersion = "26.1.0";
|
|
45340
|
-
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45341
|
-
|
|
45342
|
-
const apiVersion = {
|
|
45343
|
-
major: 12,
|
|
45344
|
-
minor: 0
|
|
45345
|
-
};
|
|
45346
|
-
const itemDataVersion = {
|
|
45347
|
-
major: 0,
|
|
45348
|
-
minor: 1
|
|
45349
|
-
};
|
|
45350
|
-
|
|
45351
|
-
var allWidgets = [...basicWidgets, ...extraWidgets];
|
|
45352
|
-
|
|
45353
|
-
registerWidgets(allWidgets);
|
|
45354
|
-
const ItemVersion = getVersionVector();
|
|
45355
|
-
ItemVersion["::renderer::"] = itemDataVersion;
|
|
45356
|
-
|
|
45357
|
-
class ArticleRenderer extends React__namespace.Component {
|
|
45358
|
-
static contextType = PerseusI18nContext;
|
|
45359
|
-
_currentFocus;
|
|
45360
|
-
sectionRenderers = [];
|
|
45361
|
-
static defaultProps = {
|
|
45362
|
-
apiOptions: ApiOptions.defaults,
|
|
45363
|
-
useNewStyles: false,
|
|
45364
|
-
linterContext: PerseusLinter__namespace.linterContextDefault
|
|
45365
|
-
};
|
|
45366
|
-
constructor(props) {
|
|
45367
|
-
super(props);
|
|
45368
|
-
}
|
|
45369
|
-
componentDidMount() {
|
|
45370
|
-
this._currentFocus = null;
|
|
45371
|
-
}
|
|
45372
|
-
shouldComponentUpdate(nextProps) {
|
|
45373
|
-
return nextProps !== this.props;
|
|
45374
|
-
}
|
|
45375
|
-
_handleFocusChange = (newFocusPath, oldFocusPath) => {
|
|
45376
|
-
// TODO(charlie): DRY this up--some of this logic is repeated in
|
|
45377
|
-
// ItemRenderer.
|
|
45378
|
-
if (newFocusPath) {
|
|
45379
|
-
this._setCurrentFocus(newFocusPath);
|
|
45380
|
-
} else {
|
|
45381
|
-
this._onRendererBlur(oldFocusPath);
|
|
45382
|
-
}
|
|
45383
|
-
};
|
|
45384
|
-
_setCurrentFocus = newFocusPath => {
|
|
45385
|
-
const {
|
|
45386
|
-
keypadElement,
|
|
45387
|
-
apiOptions
|
|
45388
|
-
} = this.props;
|
|
45389
|
-
const {
|
|
45390
|
-
isMobile
|
|
45391
|
-
} = apiOptions;
|
|
45392
|
-
const prevFocusPath = this._currentFocus;
|
|
45393
|
-
this._currentFocus = newFocusPath;
|
|
45394
|
-
|
|
45395
|
-
// Use the section prefix to extract the relevant Renderer's input
|
|
45396
|
-
// paths, so as to check whether the focused path represents an
|
|
45397
|
-
// input.
|
|
45398
|
-
let didFocusInput = false;
|
|
45399
|
-
let focusedInput;
|
|
45400
|
-
if (this._currentFocus) {
|
|
45401
|
-
const [sectionIndex, ...focusPath] = this._currentFocus;
|
|
45402
|
-
const inputPaths = this.sectionRenderers[sectionIndex].getInputPaths();
|
|
45403
|
-
didFocusInput = inputPaths.some(inputPath => {
|
|
45404
|
-
return Util.inputPathsEqual(inputPath, focusPath);
|
|
45405
|
-
});
|
|
45406
|
-
focusedInput = this.sectionRenderers[sectionIndex].getDOMNodeForPath(focusPath);
|
|
45407
|
-
}
|
|
45408
|
-
const {
|
|
45409
|
-
onFocusChange
|
|
45410
|
-
} = this.props.apiOptions;
|
|
45411
|
-
if (onFocusChange) {
|
|
45412
|
-
// Wait for the keypad to mount before getting the height
|
|
45413
|
-
setTimeout(() => {
|
|
45414
|
-
const keypadDomNode = keypadElement?.getDOMNode();
|
|
45415
|
-
const keypadHeight = keypadDomNode && didFocusInput ? keypadDomNode.getBoundingClientRect().height : 0;
|
|
45416
|
-
onFocusChange(this._currentFocus, prevFocusPath, keypadHeight, didFocusInput ? focusedInput : null);
|
|
45417
|
-
}, 0);
|
|
45418
|
-
}
|
|
45419
|
-
if (keypadElement && isMobile) {
|
|
45420
|
-
if (didFocusInput) {
|
|
45421
|
-
keypadElement.activate();
|
|
45422
|
-
} else {
|
|
45423
|
-
keypadElement.dismiss();
|
|
45424
|
-
}
|
|
45425
|
-
}
|
|
45426
|
-
};
|
|
45427
|
-
_onRendererBlur = blurPath => {
|
|
45428
|
-
const blurringFocusPath = this._currentFocus;
|
|
45429
|
-
|
|
45430
|
-
// Failsafe: abort if ID is different, because focus probably happened
|
|
45431
|
-
// before blur.
|
|
45432
|
-
if (!Util.inputPathsEqual(blurPath, blurringFocusPath)) {
|
|
45433
|
-
return;
|
|
45434
|
-
}
|
|
44539
|
+
// Failsafe: abort if ID is different, because focus probably happened
|
|
44540
|
+
// before blur.
|
|
44541
|
+
if (!Util.inputPathsEqual(blurPath, blurringFocusPath)) {
|
|
44542
|
+
return;
|
|
44543
|
+
}
|
|
45435
44544
|
|
|
45436
44545
|
// Wait until after any new focus events fire this tick before declaring
|
|
45437
44546
|
// that nothing is focused, since if there were a focus change across
|
|
@@ -46869,31 +45978,248 @@ function inferItemTreeShape(node) {
|
|
|
46869
45978
|
if (typeof node === "object" && node.__type === "hint") {
|
|
46870
45979
|
return shapes.hint;
|
|
46871
45980
|
}
|
|
46872
|
-
if (typeof node === "object") {
|
|
46873
|
-
const valueShapes = {};
|
|
46874
|
-
Object.keys(node).forEach(key => {
|
|
46875
|
-
valueShapes[key] = inferItemTreeShape(node[key]);
|
|
46876
|
-
});
|
|
46877
|
-
return shapes.shape(valueShapes);
|
|
45981
|
+
if (typeof node === "object") {
|
|
45982
|
+
const valueShapes = {};
|
|
45983
|
+
Object.keys(node).forEach(key => {
|
|
45984
|
+
valueShapes[key] = inferItemTreeShape(node[key]);
|
|
45985
|
+
});
|
|
45986
|
+
return shapes.shape(valueShapes);
|
|
45987
|
+
}
|
|
45988
|
+
throw new perseusCore.PerseusError(`unexpected multi-item node ${JSON.stringify(node)}`, perseusCore.Errors.InvalidInput);
|
|
45989
|
+
}
|
|
45990
|
+
|
|
45991
|
+
/**
|
|
45992
|
+
* Convert the given Item to an ItemTree, by unwrapping the `_multi` key.
|
|
45993
|
+
*/
|
|
45994
|
+
function itemToTree(item) {
|
|
45995
|
+
return item._multi;
|
|
45996
|
+
}
|
|
45997
|
+
|
|
45998
|
+
/**
|
|
45999
|
+
* Convert the given ItemTree to an Item, by wrapping it in the `_multi` key.
|
|
46000
|
+
*/
|
|
46001
|
+
function treeToItem(node) {
|
|
46002
|
+
return {
|
|
46003
|
+
_multi: node
|
|
46004
|
+
};
|
|
46005
|
+
}
|
|
46006
|
+
|
|
46007
|
+
var isObject = function (obj) {
|
|
46008
|
+
return obj === Object(obj);
|
|
46009
|
+
};
|
|
46010
|
+
var merge = function () {
|
|
46011
|
+
var obj = {};
|
|
46012
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
46013
|
+
var source = arguments[i];
|
|
46014
|
+
if (source) {
|
|
46015
|
+
for (var prop in source) {
|
|
46016
|
+
obj[prop] = source[prop];
|
|
46017
|
+
}
|
|
46018
|
+
}
|
|
46019
|
+
}
|
|
46020
|
+
return obj;
|
|
46021
|
+
};
|
|
46022
|
+
var clone = function (obj) {
|
|
46023
|
+
if (!isObject(obj)) {
|
|
46024
|
+
return obj;
|
|
46025
|
+
}
|
|
46026
|
+
return Array.isArray(obj) ? obj.slice() : merge(obj);
|
|
46027
|
+
};
|
|
46028
|
+
|
|
46029
|
+
/* TODO batch *all* mutations
|
|
46030
|
+
* idea: freeze / thaw implementations for all types
|
|
46031
|
+
* lens constructor thaws, freeze delegates to type's freeze
|
|
46032
|
+
*/
|
|
46033
|
+
|
|
46034
|
+
// equivalents, without requiring it
|
|
46035
|
+
// find the implementation to use for a given object
|
|
46036
|
+
var dispatch = function (x) {
|
|
46037
|
+
if (Array.isArray(x)) {
|
|
46038
|
+
return arr;
|
|
46039
|
+
} else if (isObject(x)) {
|
|
46040
|
+
return obj;
|
|
46041
|
+
} else if (typeof x === "string") {
|
|
46042
|
+
return str;
|
|
46043
|
+
}
|
|
46044
|
+
};
|
|
46045
|
+
|
|
46046
|
+
// This is underscore with a different name
|
|
46047
|
+
var lens = function (obj) {
|
|
46048
|
+
if (obj instanceof lens) {
|
|
46049
|
+
return obj;
|
|
46050
|
+
}
|
|
46051
|
+
if (!(this instanceof lens)) {
|
|
46052
|
+
return new lens(obj);
|
|
46053
|
+
}
|
|
46054
|
+
var ops = dispatch(obj);
|
|
46055
|
+
this._wrapped = ops.thaw ? ops.thaw(obj) : obj;
|
|
46056
|
+
};
|
|
46057
|
+
lens.prototype.freeze = function () {
|
|
46058
|
+
var obj = this._wrapped;
|
|
46059
|
+
var ops = dispatch(obj);
|
|
46060
|
+
return ops.freeze ? ops.freeze(obj) : obj;
|
|
46061
|
+
};
|
|
46062
|
+
lens.prototype.zoom = function (lensArr) {
|
|
46063
|
+
if (this._zoomStack === undefined) {
|
|
46064
|
+
this._zoomStack = [];
|
|
46065
|
+
}
|
|
46066
|
+
this._zoomStack.push({
|
|
46067
|
+
zoom: lensArr,
|
|
46068
|
+
wrapped: this._wrapped
|
|
46069
|
+
});
|
|
46070
|
+
this._wrapped = lens(this._wrapped).get(lensArr);
|
|
46071
|
+
return this;
|
|
46072
|
+
};
|
|
46073
|
+
lens.prototype.deZoom = function () {
|
|
46074
|
+
var frame = this._zoomStack.pop();
|
|
46075
|
+
this._wrapped = lens(frame.wrapped).set(frame.zoom, this._wrapped).freeze();
|
|
46076
|
+
return this;
|
|
46077
|
+
};
|
|
46078
|
+
lens.prototype.get = function (lensArr) {
|
|
46079
|
+
var obj = this._wrapped;
|
|
46080
|
+
for (var i = 0; i < lensArr.length; i++) {
|
|
46081
|
+
obj = dispatch(obj).get(obj, lensArr[i]);
|
|
46082
|
+
}
|
|
46083
|
+
return obj;
|
|
46084
|
+
};
|
|
46085
|
+
lens.prototype.mod = function (lensArr, f) {
|
|
46086
|
+
var obj = this._wrapped;
|
|
46087
|
+
var newObj = clone(obj);
|
|
46088
|
+
var ops = dispatch(obj);
|
|
46089
|
+
if (lensArr.length === 0) {
|
|
46090
|
+
this._wrapped = f(this._wrapped);
|
|
46091
|
+
} else if (lensArr.length === 1) {
|
|
46092
|
+
this._wrapped = ops.mod(newObj, lensArr[0], f);
|
|
46093
|
+
} else {
|
|
46094
|
+
var monocle = lensArr[0];
|
|
46095
|
+
var shortLens = lensArr.slice(1);
|
|
46096
|
+
|
|
46097
|
+
// newObj = ops.mod(obj[monocle], shortLens, f);
|
|
46098
|
+
|
|
46099
|
+
newObj[monocle] = lens(obj[monocle]).mod(shortLens, f).freeze();
|
|
46100
|
+
this._wrapped = newObj;
|
|
46101
|
+
}
|
|
46102
|
+
return this;
|
|
46103
|
+
};
|
|
46104
|
+
|
|
46105
|
+
// TODO - move to individual files
|
|
46106
|
+
lens.prototype.merge = function (lensArr, props) {
|
|
46107
|
+
this._wrapped = lens(this._wrapped).mod(lensArr, function (oldProps) {
|
|
46108
|
+
return merge(oldProps, props);
|
|
46109
|
+
}).freeze();
|
|
46110
|
+
return this;
|
|
46111
|
+
};
|
|
46112
|
+
|
|
46113
|
+
// Lens must have length >= 1 or there would be nothing to return
|
|
46114
|
+
lens.prototype.del = function (lensArr) {
|
|
46115
|
+
var obj = this._wrapped;
|
|
46116
|
+
var ops = dispatch(obj);
|
|
46117
|
+
if (lensArr.length === 1) {
|
|
46118
|
+
this._wrapped = ops.del(obj, lensArr[0]);
|
|
46119
|
+
} else {
|
|
46120
|
+
var monocle = lensArr[0];
|
|
46121
|
+
var shortLens = lensArr.slice(1);
|
|
46122
|
+
var newObj = clone(obj);
|
|
46123
|
+
newObj[monocle] = lens(obj[monocle]).del(shortLens).freeze();
|
|
46124
|
+
this._wrapped = newObj;
|
|
46125
|
+
}
|
|
46126
|
+
return this;
|
|
46127
|
+
};
|
|
46128
|
+
lens.prototype.set = function (lensArr, set) {
|
|
46129
|
+
return this.mod(lensArr, function () {
|
|
46130
|
+
return set;
|
|
46131
|
+
});
|
|
46132
|
+
};
|
|
46133
|
+
|
|
46134
|
+
// ARRAY FUNCTIONS
|
|
46135
|
+
|
|
46136
|
+
const arr = {
|
|
46137
|
+
get: function (arr, monocle) {
|
|
46138
|
+
return arr[monocle];
|
|
46139
|
+
},
|
|
46140
|
+
set: function (arr, monocle, val) {
|
|
46141
|
+
var newArr = arr.splice();
|
|
46142
|
+
newArr[monocle] = val;
|
|
46143
|
+
return newArr;
|
|
46144
|
+
},
|
|
46145
|
+
mod: function (arr, monocle, f) {
|
|
46146
|
+
var newArr = arr.slice();
|
|
46147
|
+
newArr[monocle] = f(arr[monocle]);
|
|
46148
|
+
return newArr;
|
|
46149
|
+
},
|
|
46150
|
+
del: function (arr, monocle) {
|
|
46151
|
+
var newArr = arr.slice();
|
|
46152
|
+
newArr.splice(monocle, 1);
|
|
46153
|
+
return newArr;
|
|
46154
|
+
}
|
|
46155
|
+
};
|
|
46156
|
+
|
|
46157
|
+
/*
|
|
46158
|
+
// Lens must point to a member of an array. We'll insert into that array.
|
|
46159
|
+
lens.prototype.insertAt = function(lensArr, toInsert) {
|
|
46160
|
+
var obj = this._wrapped;
|
|
46161
|
+
|
|
46162
|
+
var arrLens = lensArr.slice(0, -1);
|
|
46163
|
+
var arr = lens(obj).get(arrLens).slice(); // slice to copy
|
|
46164
|
+
|
|
46165
|
+
var arrIdx = lensArr[lensArr.length-1];
|
|
46166
|
+
arr.splice(arrIdx, 0, toInsert);
|
|
46167
|
+
return lens(obj).set(arrLens, arr);
|
|
46168
|
+
};
|
|
46169
|
+
|
|
46170
|
+
lens.prototype.insertBefore = lens.prototype.insertAt;
|
|
46171
|
+
lens.prototype.insertAfter = function(lensArr, toInsert) {
|
|
46172
|
+
var newLens = lensArr.slice();
|
|
46173
|
+
newLens[newLens.length-1] += 1;
|
|
46174
|
+
return lens(this._wrapped).insertAt(newLens, toInsert);
|
|
46175
|
+
};
|
|
46176
|
+
*/
|
|
46177
|
+
|
|
46178
|
+
// OBJECT FUNCTIONS
|
|
46179
|
+
|
|
46180
|
+
const obj = {
|
|
46181
|
+
get: function (obj, monocle) {
|
|
46182
|
+
return obj[monocle];
|
|
46183
|
+
},
|
|
46184
|
+
set: function (obj, monocle, val) {
|
|
46185
|
+
var newObj = clone(obj);
|
|
46186
|
+
newObj[monocle] = val;
|
|
46187
|
+
return newObj;
|
|
46188
|
+
},
|
|
46189
|
+
mod: function (obj, monocle, f) {
|
|
46190
|
+
var newObj = clone(obj);
|
|
46191
|
+
newObj[monocle] = f(obj[monocle]);
|
|
46192
|
+
return newObj;
|
|
46193
|
+
},
|
|
46194
|
+
del: function (obj, monocle) {
|
|
46195
|
+
var newObj = clone(obj);
|
|
46196
|
+
delete newObj[monocle];
|
|
46197
|
+
return newObj;
|
|
46878
46198
|
}
|
|
46879
|
-
|
|
46880
|
-
}
|
|
46199
|
+
};
|
|
46881
46200
|
|
|
46882
|
-
|
|
46883
|
-
* Convert the given Item to an ItemTree, by unwrapping the `_multi` key.
|
|
46884
|
-
*/
|
|
46885
|
-
function itemToTree(item) {
|
|
46886
|
-
return item._multi;
|
|
46887
|
-
}
|
|
46201
|
+
// STRING FUNCTIONS
|
|
46888
46202
|
|
|
46889
|
-
|
|
46890
|
-
|
|
46891
|
-
|
|
46892
|
-
|
|
46893
|
-
|
|
46894
|
-
|
|
46895
|
-
|
|
46896
|
-
|
|
46203
|
+
const str = {
|
|
46204
|
+
get: function (arr, monocle) {
|
|
46205
|
+
return arr[monocle];
|
|
46206
|
+
},
|
|
46207
|
+
set: function (arr, monocle, val) {
|
|
46208
|
+
var newArr = arr.splice();
|
|
46209
|
+
newArr[monocle] = val;
|
|
46210
|
+
return newArr;
|
|
46211
|
+
},
|
|
46212
|
+
mod: function (arr, monocle, f) {
|
|
46213
|
+
var newArr = arr.splice();
|
|
46214
|
+
newArr[monocle] = f(arr[monocle]);
|
|
46215
|
+
return newArr;
|
|
46216
|
+
},
|
|
46217
|
+
del: function (arr, monocle) {
|
|
46218
|
+
var newArr = arr.slice();
|
|
46219
|
+
newArr.splice(monocle);
|
|
46220
|
+
return newArr;
|
|
46221
|
+
}
|
|
46222
|
+
};
|
|
46897
46223
|
|
|
46898
46224
|
// TODO(mdr)
|
|
46899
46225
|
// TODO(mdr)
|
|
@@ -47035,467 +46361,833 @@ class MultiRenderer extends React__namespace.Component {
|
|
|
47035
46361
|
}
|
|
47036
46362
|
|
|
47037
46363
|
/**
|
|
47038
|
-
* Construct a Renderer for the given HintNode, and keep track of the hint
|
|
47039
|
-
* itself for future use, too.
|
|
46364
|
+
* Construct a Renderer for the given HintNode, and keep track of the hint
|
|
46365
|
+
* itself for future use, too.
|
|
46366
|
+
*/
|
|
46367
|
+
_makeHintRendererData(hint) {
|
|
46368
|
+
// TODO(mdr): Once HintsRenderer supports inter-widget communication,
|
|
46369
|
+
// give it a ref. Until then, leave the ref null forever, to avoid
|
|
46370
|
+
// confusing the findWidgets functions.
|
|
46371
|
+
//
|
|
46372
|
+
// NOTE(davidflanagan): As a partial step toward inter-widget
|
|
46373
|
+
// communication we're going to pass a findExternalWidgets function
|
|
46374
|
+
// (using a dummy data object). This allows passage-ref widgets in
|
|
46375
|
+
// hints to use findWidget() to find the passage widgets they reference.
|
|
46376
|
+
// Note that this is one-way only, however. It does not allow
|
|
46377
|
+
// widgets in the question to find widgets in the hints, for example.
|
|
46378
|
+
const findExternalWidgets = criterion => this._findWidgets({}, criterion);
|
|
46379
|
+
return {
|
|
46380
|
+
hint,
|
|
46381
|
+
findExternalWidgets,
|
|
46382
|
+
// _annotateRendererArray() needs this
|
|
46383
|
+
ref: null,
|
|
46384
|
+
makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
|
|
46385
|
+
findExternalWidgets: findExternalWidgets,
|
|
46386
|
+
hints: [hint]
|
|
46387
|
+
}))
|
|
46388
|
+
};
|
|
46389
|
+
}
|
|
46390
|
+
|
|
46391
|
+
/**
|
|
46392
|
+
* Construct a tree of interconnected RendererDatas, corresponding to the
|
|
46393
|
+
* given item. Called in `_tryMakeRendererState`, in order to store this
|
|
46394
|
+
* tree in the component state.
|
|
46395
|
+
*/
|
|
46396
|
+
_makeRendererDataTree(item, shape) {
|
|
46397
|
+
const itemTree = itemToTree(item);
|
|
46398
|
+
return this.rendererDataTreeMapper.mapTree(itemTree, shape);
|
|
46399
|
+
}
|
|
46400
|
+
|
|
46401
|
+
/**
|
|
46402
|
+
* Return all widgets that meet the given criterion, from all Renderers
|
|
46403
|
+
* except the Renderer that triggered this call.
|
|
46404
|
+
*
|
|
46405
|
+
* This function is provided to each Renderer's `findExternalWidgets` prop,
|
|
46406
|
+
* which enables widgets in different Renderers to discover each other and
|
|
46407
|
+
* communicate.
|
|
46408
|
+
*/
|
|
46409
|
+
_findWidgets(callingData, filterCriterion) {
|
|
46410
|
+
const results = [];
|
|
46411
|
+
this._mapRenderers(data => {
|
|
46412
|
+
if (callingData !== data && data.ref) {
|
|
46413
|
+
results.push(...data.ref.findInternalWidgets(filterCriterion));
|
|
46414
|
+
}
|
|
46415
|
+
});
|
|
46416
|
+
return results;
|
|
46417
|
+
}
|
|
46418
|
+
|
|
46419
|
+
/**
|
|
46420
|
+
* Copy the renderer tree, apply the given transformation to the leaf nodes
|
|
46421
|
+
* and the optional given transformation to the array nodes, and return the
|
|
46422
|
+
* result.
|
|
46423
|
+
*
|
|
46424
|
+
* Used to provide structured data to the call site (the Renderer tree on
|
|
46425
|
+
* `render`, the Score tree on `getScores`, etc.), and to traverse the
|
|
46426
|
+
* renderer tree even when we disregard the output (like in
|
|
46427
|
+
* `_findWidgets`).
|
|
46428
|
+
*/
|
|
46429
|
+
_mapRenderers(
|
|
46430
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
46431
|
+
leafMapper
|
|
46432
|
+
// @ts-expect-error - TS2315 - Type 'Tree' is not generic.
|
|
46433
|
+
) {
|
|
46434
|
+
const {
|
|
46435
|
+
rendererDataTree
|
|
46436
|
+
} = this.state;
|
|
46437
|
+
if (!rendererDataTree) {
|
|
46438
|
+
return null;
|
|
46439
|
+
}
|
|
46440
|
+
const mapper = buildMapper().setContentMapper(leafMapper).setHintMapper(leafMapper);
|
|
46441
|
+
return mapper.mapTree(rendererDataTree, this.props.shape);
|
|
46442
|
+
}
|
|
46443
|
+
_scoreFromRef(ref) {
|
|
46444
|
+
if (!ref) {
|
|
46445
|
+
return null;
|
|
46446
|
+
}
|
|
46447
|
+
const [guess, score] = ref.guessAndScore();
|
|
46448
|
+
let state;
|
|
46449
|
+
if (ref.getSerializedState) {
|
|
46450
|
+
state = ref.getSerializedState();
|
|
46451
|
+
}
|
|
46452
|
+
return Util.keScoreFromPerseusScore(score, guess, state);
|
|
46453
|
+
}
|
|
46454
|
+
|
|
46455
|
+
/**
|
|
46456
|
+
* Return a tree in the shape of the multi-item, with scores at each of
|
|
46457
|
+
* the content nodes and `null` at the other leaf nodes.
|
|
46458
|
+
*/
|
|
46459
|
+
getScores() {
|
|
46460
|
+
return this._mapRenderers(data => this._scoreFromRef(data.ref));
|
|
46461
|
+
}
|
|
46462
|
+
|
|
46463
|
+
/**
|
|
46464
|
+
* Return a single composite score for all rendered content nodes.
|
|
46465
|
+
* The `guess` is a tree in the shape of the multi-item, with an individual
|
|
46466
|
+
* guess at each content node and `null` at the other leaf nodes.
|
|
46467
|
+
*/
|
|
46468
|
+
score() {
|
|
46469
|
+
const scores = [];
|
|
46470
|
+
const state = [];
|
|
46471
|
+
const guess = this._mapRenderers(data => {
|
|
46472
|
+
if (!data.ref) {
|
|
46473
|
+
return null;
|
|
46474
|
+
}
|
|
46475
|
+
if (data.ref.getSerializedState) {
|
|
46476
|
+
state.push(data.ref.getSerializedState());
|
|
46477
|
+
}
|
|
46478
|
+
scores.push(data.ref.score());
|
|
46479
|
+
return data.ref?.getUserInput();
|
|
46480
|
+
});
|
|
46481
|
+
const combinedScore = scores.reduce(Util.combineScores);
|
|
46482
|
+
return Util.keScoreFromPerseusScore(combinedScore, guess, state);
|
|
46483
|
+
}
|
|
46484
|
+
|
|
46485
|
+
/**
|
|
46486
|
+
* Return a tree in the shape of the multi-item, with serialized state at
|
|
46487
|
+
* each of the content nodes and `null` at the other leaf nodes.
|
|
46488
|
+
*
|
|
46489
|
+
* If the lastSerializedState argument is supplied, this function will fill
|
|
46490
|
+
* in the state of not-currently-rendered content and hint nodes with the
|
|
46491
|
+
* values from the previous serialized state. If no lastSerializedState is
|
|
46492
|
+
* supplied, `null` will be returned for not-currently-rendered content and
|
|
46493
|
+
* hint nodes.
|
|
46494
|
+
*/
|
|
46495
|
+
_getSerializedState(lastSerializedState) {
|
|
46496
|
+
return this._mapRenderers((data, _, path) => {
|
|
46497
|
+
if (data.ref) {
|
|
46498
|
+
return data.ref.getSerializedState();
|
|
46499
|
+
}
|
|
46500
|
+
if (lastSerializedState) {
|
|
46501
|
+
return lens(lastSerializedState).get(path);
|
|
46502
|
+
}
|
|
46503
|
+
return null;
|
|
46504
|
+
});
|
|
46505
|
+
}
|
|
46506
|
+
|
|
46507
|
+
/**
|
|
46508
|
+
* Given a tree in the shape of the multi-item, with serialized state at
|
|
46509
|
+
* each of the content nodes, restore each state to the corresponding
|
|
46510
|
+
* renderer if currently mounted.
|
|
46511
|
+
*/
|
|
46512
|
+
restoreSerializedState(serializedState, callback) {
|
|
46513
|
+
// We want to call our async callback only once all of the childrens'
|
|
46514
|
+
// callbacks have run. We add one to this counter before we call out to
|
|
46515
|
+
// each renderer and decrement it when it runs our callback.
|
|
46516
|
+
let numCallbacks = 0;
|
|
46517
|
+
const countCallback = () => {
|
|
46518
|
+
numCallbacks--;
|
|
46519
|
+
if (callback && numCallbacks === 0) {
|
|
46520
|
+
callback();
|
|
46521
|
+
}
|
|
46522
|
+
};
|
|
46523
|
+
this._mapRenderers((data, _, path) => {
|
|
46524
|
+
if (!data.ref) {
|
|
46525
|
+
return;
|
|
46526
|
+
}
|
|
46527
|
+
const state = lens(serializedState).get(path);
|
|
46528
|
+
if (!state) {
|
|
46529
|
+
return;
|
|
46530
|
+
}
|
|
46531
|
+
numCallbacks++;
|
|
46532
|
+
data.ref?.restoreSerializedState(state, countCallback);
|
|
46533
|
+
});
|
|
46534
|
+
}
|
|
46535
|
+
|
|
46536
|
+
/**
|
|
46537
|
+
* Given an array of renderers, if it happens to be an array of *hint*
|
|
46538
|
+
* renderers, then attach a `firstN` method to the array, which allows the
|
|
46539
|
+
* layout to render the hints together in one HintsRenderer.
|
|
47040
46540
|
*/
|
|
47041
|
-
|
|
47042
|
-
|
|
47043
|
-
|
|
47044
|
-
|
|
47045
|
-
|
|
47046
|
-
|
|
47047
|
-
|
|
47048
|
-
|
|
47049
|
-
|
|
47050
|
-
|
|
47051
|
-
|
|
47052
|
-
|
|
47053
|
-
return
|
|
47054
|
-
hint,
|
|
47055
|
-
findExternalWidgets,
|
|
47056
|
-
// _annotateRendererArray() needs this
|
|
47057
|
-
ref: null,
|
|
47058
|
-
makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
|
|
47059
|
-
findExternalWidgets: findExternalWidgets,
|
|
47060
|
-
hints: [hint]
|
|
47061
|
-
}))
|
|
47062
|
-
};
|
|
46541
|
+
_annotateRendererArray(renderers, rendererDatas, shape) {
|
|
46542
|
+
if (shape.elementShape.type === "hint") {
|
|
46543
|
+
// The shape says that these are HintRendererDatas, even though
|
|
46544
|
+
// it's not provable at compile time, so perform a cast.
|
|
46545
|
+
const hintRendererDatas = rendererDatas;
|
|
46546
|
+
renderers = [...renderers];
|
|
46547
|
+
renderers.firstN = n => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
|
|
46548
|
+
findExternalWidgets: hintRendererDatas[0] ? hintRendererDatas[0].findExternalWidgets : undefined,
|
|
46549
|
+
hints: hintRendererDatas.map(d => d.hint),
|
|
46550
|
+
hintsVisible: n
|
|
46551
|
+
}));
|
|
46552
|
+
}
|
|
46553
|
+
return renderers;
|
|
47063
46554
|
}
|
|
47064
46555
|
|
|
47065
46556
|
/**
|
|
47066
|
-
*
|
|
47067
|
-
*
|
|
47068
|
-
*
|
|
46557
|
+
* Return a tree in the shape of the multi-item, with a Renderer at each
|
|
46558
|
+
* content node and a HintRenderer at each hint node.
|
|
46559
|
+
*
|
|
46560
|
+
* This is generated by running each of the `makeRenderer` functions at the
|
|
46561
|
+
* leaf nodes.
|
|
47069
46562
|
*/
|
|
47070
|
-
|
|
47071
|
-
|
|
47072
|
-
|
|
46563
|
+
_getRenderers() {
|
|
46564
|
+
return this.getRenderersMapper.mapTree(this.state.rendererDataTree, this.props.shape);
|
|
46565
|
+
}
|
|
46566
|
+
render() {
|
|
46567
|
+
if (this.state.renderError) {
|
|
46568
|
+
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
46569
|
+
className: aphrodite.css(styles.error)
|
|
46570
|
+
}, this.context.strings.errorRendering({
|
|
46571
|
+
error: String(this.state.renderError)
|
|
46572
|
+
}));
|
|
46573
|
+
}
|
|
46574
|
+
|
|
46575
|
+
// Pass the renderer tree to the `children` function, which will
|
|
46576
|
+
// determine the actual content of this component.
|
|
46577
|
+
return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
|
|
46578
|
+
value: this.props.dependencies
|
|
46579
|
+
}, this.props.children({
|
|
46580
|
+
renderers: this._getRenderers()
|
|
46581
|
+
}));
|
|
46582
|
+
}
|
|
46583
|
+
}
|
|
46584
|
+
const styles = aphrodite.StyleSheet.create({
|
|
46585
|
+
error: {
|
|
46586
|
+
color: "red"
|
|
46587
|
+
}
|
|
46588
|
+
});
|
|
46589
|
+
|
|
46590
|
+
/**
|
|
46591
|
+
* Utility functions to build React PropTypes for multi-items and shapes.
|
|
46592
|
+
*
|
|
46593
|
+
* If you're writing new components, though, consider using the Item and Shape
|
|
46594
|
+
* types instead.
|
|
46595
|
+
*/
|
|
46596
|
+
|
|
46597
|
+
/**
|
|
46598
|
+
* Return a PropType that accepts Items of the given shape, and rejects other
|
|
46599
|
+
* objects.
|
|
46600
|
+
*
|
|
46601
|
+
* Usage: `propTypes: {item: buildPropTypeForShape(myShape)}`
|
|
46602
|
+
*/
|
|
46603
|
+
function buildPropTypeForShape(shape) {
|
|
46604
|
+
return PropTypes__default["default"].oneOfType([PropTypes__default["default"].shape({
|
|
46605
|
+
_multi: buildTreePropTypeForShape(shape)
|
|
46606
|
+
}), PropTypes__default["default"].oneOf([null, undefined])]);
|
|
46607
|
+
}
|
|
46608
|
+
|
|
46609
|
+
/**
|
|
46610
|
+
* Return a PropType that accepts ItemTrees of the given shape, and rejects
|
|
46611
|
+
* other objects.
|
|
46612
|
+
*/
|
|
46613
|
+
function buildTreePropTypeForShape(shape) {
|
|
46614
|
+
if (shape.type === "content") {
|
|
46615
|
+
return PropTypes__default["default"].shape({
|
|
46616
|
+
// TODO(mdr): Remove #LegacyContentNode support.
|
|
46617
|
+
__type: PropTypes__default["default"].oneOf(["content", "item"]).isRequired,
|
|
46618
|
+
content: PropTypes__default["default"].string,
|
|
46619
|
+
images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
|
|
46620
|
+
widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any)
|
|
46621
|
+
});
|
|
46622
|
+
}
|
|
46623
|
+
if (shape.type === "hint") {
|
|
46624
|
+
return PropTypes__default["default"].shape({
|
|
46625
|
+
__type: PropTypes__default["default"].oneOf(["hint"]).isRequired,
|
|
46626
|
+
content: PropTypes__default["default"].string,
|
|
46627
|
+
images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
|
|
46628
|
+
widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
|
|
46629
|
+
replace: PropTypes__default["default"].bool
|
|
46630
|
+
});
|
|
46631
|
+
}
|
|
46632
|
+
if (shape.type === "tags") {
|
|
46633
|
+
return PropTypes__default["default"].arrayOf(PropTypes__default["default"].string.isRequired);
|
|
46634
|
+
}
|
|
46635
|
+
if (shape.type === "array") {
|
|
46636
|
+
const elementPropType = buildTreePropTypeForShape(shape.elementShape);
|
|
46637
|
+
return PropTypes__default["default"].arrayOf(elementPropType.isRequired);
|
|
46638
|
+
}
|
|
46639
|
+
if (shape.type === "object") {
|
|
46640
|
+
const valueShapes = shape.shape;
|
|
46641
|
+
const propTypeShape = {};
|
|
46642
|
+
Object.keys(valueShapes).forEach(key => {
|
|
46643
|
+
propTypeShape[key] = buildTreePropTypeForShape(valueShapes[key]).isRequired;
|
|
46644
|
+
});
|
|
46645
|
+
return PropTypes__default["default"].shape(propTypeShape);
|
|
46646
|
+
}
|
|
46647
|
+
throw new perseusCore.PerseusError(
|
|
46648
|
+
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
46649
|
+
`unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
|
|
46650
|
+
}
|
|
46651
|
+
|
|
46652
|
+
/**
|
|
46653
|
+
* This library provides support for Perseus multi-items: structured Perseus
|
|
46654
|
+
* content that content creators can easily create, and that applications can
|
|
46655
|
+
* easily render into different parts of the layout.
|
|
46656
|
+
*
|
|
46657
|
+
* For more details about application and motivation, see:
|
|
46658
|
+
* https://sites.google.com/a/khanacademy.org/forge/for-developers/perseus-items-and-multi-items
|
|
46659
|
+
*
|
|
46660
|
+
* This file primarily exposes the `MultiRenderer` component, which performs
|
|
46661
|
+
* multi-rendering. To multi-render a question, pass in the content of the item
|
|
46662
|
+
* to the `MultiRenderer` component as a props. Then, pass in a function which
|
|
46663
|
+
* takes an object of renderers (in the same structure as the content), and
|
|
46664
|
+
* return a render tree. The `MultiRenderer` component will allow you to
|
|
46665
|
+
* combine scores, serialized state, etc. without having to manually call on
|
|
46666
|
+
* each of the functions. It also handles inter-widgets requests between the
|
|
46667
|
+
* different renderers.
|
|
46668
|
+
* For more details, see `multi-items/multi-renderer.jsx`.
|
|
46669
|
+
*
|
|
46670
|
+
* Example:
|
|
46671
|
+
*
|
|
46672
|
+
* item = {_multi: {
|
|
46673
|
+
* left: <content data>,
|
|
46674
|
+
* right: [<content data>, <content data>],
|
|
46675
|
+
* }}
|
|
46676
|
+
* shape = shapes.shape({
|
|
46677
|
+
* left: shapes.content,
|
|
46678
|
+
* right: shapes.arrayOf(shapes.content),
|
|
46679
|
+
* })
|
|
46680
|
+
*
|
|
46681
|
+
* <MultiRenderer item={item} shape={shape}>
|
|
46682
|
+
* {({renderers}) =>
|
|
46683
|
+
* <div>
|
|
46684
|
+
* <div id="left">{renderers.left}</div>
|
|
46685
|
+
* <ul id="right">
|
|
46686
|
+
* {renderers.right.map(r => <li>{r}</li>)}
|
|
46687
|
+
* </ul>
|
|
46688
|
+
* </div>
|
|
46689
|
+
* }
|
|
46690
|
+
* </MultiRenderer>
|
|
46691
|
+
*
|
|
46692
|
+
* This file also exposes `shapes`, which helps you construct a runtime type
|
|
46693
|
+
* declaration for your particular class of multi-item. This can then be used
|
|
46694
|
+
* to create a MultirendererEditor for your multi-item shape, and to validate
|
|
46695
|
+
* that a multi-item conforms to the shape via `buildPropTypeForShape`.
|
|
46696
|
+
* For more details, see `multi-items/shapes.js`.
|
|
46697
|
+
*
|
|
46698
|
+
* This file also exposes some utility functions for working with generic
|
|
46699
|
+
* multi-items, like `findContentNodesInItem`, `findHintNodesInItem`,
|
|
46700
|
+
* `inferItemShape`, and `buildEmptyItemForShape`.
|
|
46701
|
+
* For more details, see `multi-items/items.js`.
|
|
46702
|
+
*/
|
|
46703
|
+
var MultiItems = {
|
|
46704
|
+
// Tools for rendering your multi-items
|
|
46705
|
+
MultiRenderer,
|
|
46706
|
+
// Tools for declaring your multi-item shapes
|
|
46707
|
+
shapes,
|
|
46708
|
+
buildPropTypeForShape,
|
|
46709
|
+
// Tools for generically manipulating multi-items
|
|
46710
|
+
buildEmptyItemForShape,
|
|
46711
|
+
findContentNodesInItem: findContentNodesInItem$2,
|
|
46712
|
+
findHintNodesInItem,
|
|
46713
|
+
inferItemShape: inferItemShape$2
|
|
46714
|
+
};
|
|
46715
|
+
|
|
46716
|
+
/**
|
|
46717
|
+
* Calculates whether a perseus item is renderable by a specific
|
|
46718
|
+
* perseus-item-data version.
|
|
46719
|
+
*
|
|
46720
|
+
* This is done inside of the perseus repo so that it can traverse
|
|
46721
|
+
* widget-specific data that might need to do a sub-traversal.
|
|
46722
|
+
* This supports widgets that contain renderers, such as the
|
|
46723
|
+
* group or sequence widgets.
|
|
46724
|
+
*/
|
|
46725
|
+
const {
|
|
46726
|
+
findContentNodesInItem: findContentNodesInItem$1,
|
|
46727
|
+
inferItemShape: inferItemShape$1
|
|
46728
|
+
} = MultiItems;
|
|
46729
|
+
const isUpgradedWidgetInfoRenderableBy = function (widgetInfo, widgetRendererVersion) {
|
|
46730
|
+
if (widgetRendererVersion == null) {
|
|
46731
|
+
// If the widget does not exist in this version, this will
|
|
46732
|
+
// be null, and that version of perseus cannot render the
|
|
46733
|
+
// widget (it doesn't even know the widget exists!)
|
|
46734
|
+
return false;
|
|
46735
|
+
}
|
|
46736
|
+
const widgetVersion = widgetInfo.version || {
|
|
46737
|
+
major: 0,
|
|
46738
|
+
minor: 0
|
|
46739
|
+
};
|
|
46740
|
+
if (widgetRendererVersion.major > widgetVersion.major) {
|
|
46741
|
+
return true;
|
|
46742
|
+
}
|
|
46743
|
+
if (widgetRendererVersion.major < widgetVersion.major) {
|
|
46744
|
+
return false;
|
|
46745
|
+
}
|
|
46746
|
+
// If the major versions are the same, the minor version acts
|
|
46747
|
+
// like a tie-breaker.
|
|
46748
|
+
// For example, input-number 3.2 can render an input-number
|
|
46749
|
+
// 2.4, 3.0, or 3.2, but not an input number 3.3 or 4.0.
|
|
46750
|
+
return widgetRendererVersion.minor >= widgetVersion.minor;
|
|
46751
|
+
};
|
|
46752
|
+
const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion) {
|
|
46753
|
+
// Empty/non-existant widgets are always safe to render
|
|
46754
|
+
if (widgetInfo == null || widgetInfo.type == null) {
|
|
46755
|
+
return true;
|
|
47073
46756
|
}
|
|
47074
46757
|
|
|
47075
|
-
|
|
47076
|
-
|
|
47077
|
-
|
|
47078
|
-
|
|
47079
|
-
|
|
47080
|
-
|
|
47081
|
-
|
|
47082
|
-
|
|
47083
|
-
|
|
47084
|
-
|
|
47085
|
-
|
|
47086
|
-
|
|
47087
|
-
|
|
46758
|
+
// NOTE: This doesn't modify the widget info if the widget info
|
|
46759
|
+
// is at a later version than is supported.
|
|
46760
|
+
const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
46761
|
+
return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
|
|
46762
|
+
};
|
|
46763
|
+
const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
|
|
46764
|
+
let isRenderable = true;
|
|
46765
|
+
traverse(rendererOptions, null, function (widgetInfo) {
|
|
46766
|
+
isRenderable = isRenderable && isRawWidgetInfoRenderableBy(widgetInfo, rendererContentVersion);
|
|
46767
|
+
});
|
|
46768
|
+
return isRenderable;
|
|
46769
|
+
};
|
|
46770
|
+
const isItemRenderableByVersion = function (itemData, rendererContentVersion) {
|
|
46771
|
+
if (itemData == null || rendererContentVersion == null) {
|
|
46772
|
+
throw new perseusCore.PerseusError("missing parameter to Perseus.isRenderable.item", perseusCore.Errors.InvalidInput);
|
|
46773
|
+
}
|
|
46774
|
+
if (itemData._multi) {
|
|
46775
|
+
const shape = inferItemShape$1(itemData);
|
|
46776
|
+
let isRenderable = true;
|
|
46777
|
+
findContentNodesInItem$1(itemData, shape, node => {
|
|
46778
|
+
const nodeIsRenderable = isRendererContentRenderableBy(node, rendererContentVersion);
|
|
46779
|
+
if (!nodeIsRenderable) {
|
|
46780
|
+
isRenderable = false;
|
|
47088
46781
|
}
|
|
47089
46782
|
});
|
|
47090
|
-
return
|
|
46783
|
+
return isRenderable;
|
|
47091
46784
|
}
|
|
46785
|
+
return isRendererContentRenderableBy(itemData.question, rendererContentVersion);
|
|
46786
|
+
};
|
|
47092
46787
|
|
|
47093
|
-
|
|
47094
|
-
|
|
47095
|
-
|
|
47096
|
-
|
|
47097
|
-
|
|
47098
|
-
|
|
47099
|
-
|
|
47100
|
-
|
|
47101
|
-
|
|
47102
|
-
|
|
47103
|
-
|
|
47104
|
-
|
|
47105
|
-
|
|
47106
|
-
|
|
47107
|
-
|
|
47108
|
-
const {
|
|
47109
|
-
rendererDataTree
|
|
47110
|
-
} = this.state;
|
|
47111
|
-
if (!rendererDataTree) {
|
|
47112
|
-
return null;
|
|
47113
|
-
}
|
|
47114
|
-
const mapper = buildMapper().setContentMapper(leafMapper).setHintMapper(leafMapper);
|
|
47115
|
-
return mapper.mapTree(rendererDataTree, this.props.shape);
|
|
47116
|
-
}
|
|
47117
|
-
_scoreFromRef(ref) {
|
|
47118
|
-
if (!ref) {
|
|
47119
|
-
return null;
|
|
47120
|
-
}
|
|
47121
|
-
const [guess, score] = ref.guessAndScore();
|
|
47122
|
-
let state;
|
|
47123
|
-
if (ref.getSerializedState) {
|
|
47124
|
-
state = ref.getSerializedState();
|
|
46788
|
+
/**
|
|
46789
|
+
* Identifies whether or not a given perseus item requires the use of a mouse
|
|
46790
|
+
* or screen, based on the widgets it contains.
|
|
46791
|
+
*/
|
|
46792
|
+
const {
|
|
46793
|
+
findContentNodesInItem,
|
|
46794
|
+
inferItemShape
|
|
46795
|
+
} = MultiItems;
|
|
46796
|
+
|
|
46797
|
+
// Iterate over a single Perseus renderer, mutating `widgets` by appending
|
|
46798
|
+
// violating widget types discovered in this item.
|
|
46799
|
+
function traverseRenderer(itemData, widgets) {
|
|
46800
|
+
traverse(itemData, null, function (info) {
|
|
46801
|
+
if (info.type && !isAccessible(info)) {
|
|
46802
|
+
widgets.push(info.type);
|
|
47125
46803
|
}
|
|
47126
|
-
|
|
47127
|
-
|
|
46804
|
+
});
|
|
46805
|
+
}
|
|
47128
46806
|
|
|
47129
|
-
|
|
47130
|
-
|
|
47131
|
-
|
|
47132
|
-
|
|
47133
|
-
|
|
47134
|
-
|
|
46807
|
+
// Returns a list of widgets that cause a given perseus item to require
|
|
46808
|
+
// the use of a screen or mouse.
|
|
46809
|
+
//
|
|
46810
|
+
// For now we'll just check the `accessible` field on each of the widgets
|
|
46811
|
+
// in the item data, but in the future we may specify accessibility on
|
|
46812
|
+
// each widget with higher granularity.
|
|
46813
|
+
function violatingWidgets(itemData) {
|
|
46814
|
+
// TODO(jordan): Hints as well
|
|
46815
|
+
const widgets = [];
|
|
46816
|
+
if (itemData._multi) {
|
|
46817
|
+
const shape = inferItemShape(itemData);
|
|
46818
|
+
findContentNodesInItem(itemData, shape, content => traverseRenderer(content, widgets));
|
|
46819
|
+
} else {
|
|
46820
|
+
traverseRenderer(itemData.question, widgets);
|
|
47135
46821
|
}
|
|
47136
46822
|
|
|
47137
|
-
|
|
47138
|
-
|
|
47139
|
-
|
|
47140
|
-
* guess at each content node and `null` at the other leaf nodes.
|
|
47141
|
-
*/
|
|
47142
|
-
score() {
|
|
47143
|
-
const scores = [];
|
|
47144
|
-
const state = [];
|
|
47145
|
-
const guess = this._mapRenderers(data => {
|
|
47146
|
-
if (!data.ref) {
|
|
47147
|
-
return null;
|
|
47148
|
-
}
|
|
47149
|
-
if (data.ref.getSerializedState) {
|
|
47150
|
-
state.push(data.ref.getSerializedState());
|
|
47151
|
-
}
|
|
47152
|
-
scores.push(data.ref.score());
|
|
47153
|
-
return data.ref?.getUserInput();
|
|
47154
|
-
});
|
|
47155
|
-
const combinedScore = scores.reduce(Util.combineScores);
|
|
47156
|
-
return Util.keScoreFromPerseusScore(combinedScore, guess, state);
|
|
47157
|
-
}
|
|
46823
|
+
// Uniquify the list of widgets (by type)
|
|
46824
|
+
return ___default["default"].uniq(widgets);
|
|
46825
|
+
}
|
|
47158
46826
|
|
|
47159
|
-
|
|
47160
|
-
* Return a tree in the shape of the multi-item, with serialized state at
|
|
47161
|
-
* each of the content nodes and `null` at the other leaf nodes.
|
|
47162
|
-
*
|
|
47163
|
-
* If the lastSerializedState argument is supplied, this function will fill
|
|
47164
|
-
* in the state of not-currently-rendered content and hint nodes with the
|
|
47165
|
-
* values from the previous serialized state. If no lastSerializedState is
|
|
47166
|
-
* supplied, `null` will be returned for not-currently-rendered content and
|
|
47167
|
-
* hint nodes.
|
|
47168
|
-
*/
|
|
47169
|
-
_getSerializedState(lastSerializedState) {
|
|
47170
|
-
return this._mapRenderers((data, _, path) => {
|
|
47171
|
-
if (data.ref) {
|
|
47172
|
-
return data.ref.getSerializedState();
|
|
47173
|
-
}
|
|
47174
|
-
if (lastSerializedState) {
|
|
47175
|
-
return lens(lastSerializedState).get(path);
|
|
47176
|
-
}
|
|
47177
|
-
return null;
|
|
47178
|
-
});
|
|
47179
|
-
}
|
|
46827
|
+
/* eslint-disable eqeqeq, @typescript-eslint/no-unused-vars */
|
|
47180
46828
|
|
|
47181
|
-
|
|
47182
|
-
|
|
47183
|
-
|
|
47184
|
-
|
|
47185
|
-
|
|
47186
|
-
|
|
47187
|
-
|
|
47188
|
-
|
|
47189
|
-
|
|
47190
|
-
|
|
47191
|
-
|
|
47192
|
-
|
|
47193
|
-
|
|
47194
|
-
|
|
46829
|
+
/**
|
|
46830
|
+
* Format a floating point for display using the specified
|
|
46831
|
+
* number of significant figures and least significant decimal.
|
|
46832
|
+
* Scientific notation may used by this method if this
|
|
46833
|
+
* object is very small, very large, has many significant
|
|
46834
|
+
* figures, or the number of significant figures would be
|
|
46835
|
+
* ambiguous in the output if scientific notation were not
|
|
46836
|
+
* used.
|
|
46837
|
+
*
|
|
46838
|
+
* @param f A floating point number that should be displayed
|
|
46839
|
+
* @param sigFigs desired number of significant figures (integer).
|
|
46840
|
+
* @param sigDecs the least significant decimal place (integer).
|
|
46841
|
+
* @param scientific true iff scientific notation should always be used.
|
|
46842
|
+
* @return a string of this object formatted correctly.
|
|
46843
|
+
*/
|
|
46844
|
+
function displaySigFigs(f, sigFigs, sigDecs, scientific) {
|
|
46845
|
+
const s = "" + f;
|
|
46846
|
+
let order = parseOrder(s);
|
|
46847
|
+
let mantissa = parseMantissa(s);
|
|
46848
|
+
let positive = parseSign(s);
|
|
46849
|
+
let add;
|
|
46850
|
+
let decAdd;
|
|
46851
|
+
let sigAdd;
|
|
46852
|
+
let zeroScientific = false;
|
|
46853
|
+
if (f == 0 || mantissa == "" || mantissa == "0") {
|
|
46854
|
+
mantissa = "";
|
|
46855
|
+
for (let i = 0; i < sigFigs; i++) {
|
|
46856
|
+
mantissa += "0";
|
|
46857
|
+
}
|
|
46858
|
+
order = sigFigs + sigDecs;
|
|
46859
|
+
if (sigDecs < 0 && -sigDecs >= sigFigs) {
|
|
46860
|
+
zeroScientific = true;
|
|
46861
|
+
}
|
|
46862
|
+
} else {
|
|
46863
|
+
decAdd = order - mantissa.length - sigDecs;
|
|
46864
|
+
sigAdd = sigFigs - mantissa.length;
|
|
46865
|
+
add = Math.min(sigAdd, decAdd);
|
|
46866
|
+
if (add < 0) {
|
|
46867
|
+
let rounded = round(mantissa, -add);
|
|
46868
|
+
if (rounded.length > mantissa.length + add) {
|
|
46869
|
+
order++;
|
|
46870
|
+
if (decAdd > sigAdd) {
|
|
46871
|
+
rounded = round(rounded, 1);
|
|
46872
|
+
}
|
|
47195
46873
|
}
|
|
47196
|
-
|
|
47197
|
-
|
|
47198
|
-
|
|
47199
|
-
|
|
46874
|
+
mantissa = rounded;
|
|
46875
|
+
} else if (add > 0) {
|
|
46876
|
+
for (let i = 0; i < add; i++) {
|
|
46877
|
+
mantissa += "0";
|
|
47200
46878
|
}
|
|
47201
|
-
|
|
47202
|
-
|
|
47203
|
-
|
|
46879
|
+
}
|
|
46880
|
+
if (mantissa == "" || mantissa == "0") {
|
|
46881
|
+
mantissa = "0";
|
|
46882
|
+
positive = true;
|
|
46883
|
+
order = 1 + sigDecs;
|
|
46884
|
+
if (order != 0) {
|
|
46885
|
+
zeroScientific = true;
|
|
47204
46886
|
}
|
|
47205
|
-
numCallbacks++;
|
|
47206
|
-
data.ref?.restoreSerializedState(state, countCallback);
|
|
47207
|
-
});
|
|
47208
|
-
}
|
|
47209
|
-
|
|
47210
|
-
/**
|
|
47211
|
-
* Given an array of renderers, if it happens to be an array of *hint*
|
|
47212
|
-
* renderers, then attach a `firstN` method to the array, which allows the
|
|
47213
|
-
* layout to render the hints together in one HintsRenderer.
|
|
47214
|
-
*/
|
|
47215
|
-
_annotateRendererArray(renderers, rendererDatas, shape) {
|
|
47216
|
-
if (shape.elementShape.type === "hint") {
|
|
47217
|
-
// The shape says that these are HintRendererDatas, even though
|
|
47218
|
-
// it's not provable at compile time, so perform a cast.
|
|
47219
|
-
const hintRendererDatas = rendererDatas;
|
|
47220
|
-
renderers = [...renderers];
|
|
47221
|
-
renderers.firstN = n => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
|
|
47222
|
-
findExternalWidgets: hintRendererDatas[0] ? hintRendererDatas[0].findExternalWidgets : undefined,
|
|
47223
|
-
hints: hintRendererDatas.map(d => d.hint),
|
|
47224
|
-
hintsVisible: n
|
|
47225
|
-
}));
|
|
47226
46887
|
}
|
|
47227
|
-
return renderers;
|
|
47228
46888
|
}
|
|
47229
|
-
|
|
47230
|
-
|
|
47231
|
-
|
|
47232
|
-
|
|
47233
|
-
*
|
|
47234
|
-
* This is generated by running each of the `makeRenderer` functions at the
|
|
47235
|
-
* leaf nodes.
|
|
47236
|
-
*/
|
|
47237
|
-
_getRenderers() {
|
|
47238
|
-
return this.getRenderersMapper.mapTree(this.state.rendererDataTree, this.props.shape);
|
|
46889
|
+
const useScientific = scientific || mantissa.length > 20 || order > 4 || order < -2 || order - mantissa.length > 0 && trailingZeros(mantissa) > 0 || zeroScientific;
|
|
46890
|
+
let returnVal = "";
|
|
46891
|
+
if (!positive) {
|
|
46892
|
+
returnVal += "-";
|
|
47239
46893
|
}
|
|
47240
|
-
|
|
47241
|
-
|
|
47242
|
-
|
|
47243
|
-
|
|
47244
|
-
}, this.context.strings.errorRendering({
|
|
47245
|
-
error: String(this.state.renderError)
|
|
47246
|
-
}));
|
|
46894
|
+
if (useScientific) {
|
|
46895
|
+
returnVal += mantissa.charAt(0);
|
|
46896
|
+
if (mantissa.length > 1) {
|
|
46897
|
+
returnVal += "." + mantissa.substring(1, mantissa.length);
|
|
47247
46898
|
}
|
|
47248
|
-
|
|
47249
|
-
|
|
47250
|
-
|
|
47251
|
-
|
|
47252
|
-
|
|
47253
|
-
|
|
47254
|
-
|
|
47255
|
-
|
|
46899
|
+
if (order - 1 != 0) {
|
|
46900
|
+
returnVal += " x 10^" + (order - 1);
|
|
46901
|
+
}
|
|
46902
|
+
} else {
|
|
46903
|
+
let wholePart = "";
|
|
46904
|
+
let fractPart = "";
|
|
46905
|
+
let needDot = true;
|
|
46906
|
+
if (order > 0) {
|
|
46907
|
+
if (mantissa.length > order) {
|
|
46908
|
+
wholePart = mantissa.substring(0, order);
|
|
46909
|
+
fractPart = mantissa.substring(order, mantissa.length);
|
|
46910
|
+
} else {
|
|
46911
|
+
wholePart = mantissa;
|
|
46912
|
+
needDot = trailingZeros(mantissa) != 0;
|
|
46913
|
+
for (let i = 0; i < order - mantissa.length; i++) {
|
|
46914
|
+
wholePart += "0";
|
|
46915
|
+
}
|
|
46916
|
+
}
|
|
46917
|
+
} else {
|
|
46918
|
+
for (let i = 0; i < -order; i++) {
|
|
46919
|
+
fractPart += "0";
|
|
46920
|
+
}
|
|
46921
|
+
fractPart += mantissa;
|
|
46922
|
+
}
|
|
46923
|
+
returnVal += (wholePart == "" ? "0" : wholePart) + (needDot ? "." : "") + fractPart;
|
|
47256
46924
|
}
|
|
46925
|
+
return returnVal;
|
|
47257
46926
|
}
|
|
47258
|
-
const styles = aphrodite.StyleSheet.create({
|
|
47259
|
-
error: {
|
|
47260
|
-
color: "red"
|
|
47261
|
-
}
|
|
47262
|
-
});
|
|
47263
46927
|
|
|
47264
46928
|
/**
|
|
47265
|
-
*
|
|
46929
|
+
* Count the significant trailing zeros on this object.
|
|
47266
46930
|
*
|
|
47267
|
-
*
|
|
47268
|
-
* types instead.
|
|
46931
|
+
* @return the number of trailing zeros
|
|
47269
46932
|
*/
|
|
46933
|
+
function trailingZeros(mantissa) {
|
|
46934
|
+
let zeros = 0;
|
|
46935
|
+
for (let i = mantissa.length - 1; i >= 0; i--) {
|
|
46936
|
+
const c = mantissa.charAt(i);
|
|
46937
|
+
if (c == "0") {
|
|
46938
|
+
zeros++;
|
|
46939
|
+
} else {
|
|
46940
|
+
return zeros;
|
|
46941
|
+
}
|
|
46942
|
+
}
|
|
46943
|
+
return zeros;
|
|
46944
|
+
}
|
|
47270
46945
|
|
|
47271
46946
|
/**
|
|
47272
|
-
*
|
|
47273
|
-
*
|
|
46947
|
+
* Parse a string representation of a floating point
|
|
46948
|
+
* and pull out the sign.
|
|
47274
46949
|
*
|
|
47275
|
-
*
|
|
46950
|
+
* @param s the string representation of a floating point.
|
|
46951
|
+
* @return true iff this is a positive number
|
|
47276
46952
|
*/
|
|
47277
|
-
function
|
|
47278
|
-
|
|
47279
|
-
|
|
47280
|
-
|
|
46953
|
+
function parseSign(s) {
|
|
46954
|
+
let beginning = true;
|
|
46955
|
+
let seenDot = false;
|
|
46956
|
+
let seenSomething = false;
|
|
46957
|
+
let zeros = "";
|
|
46958
|
+
let all = "";
|
|
46959
|
+
let pos = true;
|
|
46960
|
+
for (let i = 0; i < s.length; i++) {
|
|
46961
|
+
const c = s.charAt(i);
|
|
46962
|
+
if (c >= "1" && c <= "9") {
|
|
46963
|
+
all += zeros + c;
|
|
46964
|
+
zeros = "";
|
|
46965
|
+
seenSomething = true;
|
|
46966
|
+
beginning = false;
|
|
46967
|
+
} else if (c == "0") {
|
|
46968
|
+
if (seenDot) {
|
|
46969
|
+
if (seenSomething) {
|
|
46970
|
+
all += zeros + c;
|
|
46971
|
+
zeros = "";
|
|
46972
|
+
}
|
|
46973
|
+
} else {
|
|
46974
|
+
if (seenSomething) {
|
|
46975
|
+
zeros += c;
|
|
46976
|
+
}
|
|
46977
|
+
}
|
|
46978
|
+
beginning = false;
|
|
46979
|
+
} else if (!seenDot && c == ".") {
|
|
46980
|
+
all += zeros;
|
|
46981
|
+
zeros = "";
|
|
46982
|
+
seenDot = true;
|
|
46983
|
+
beginning = false;
|
|
46984
|
+
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
46985
|
+
parseInt(s.substring(i + 1, s.length));
|
|
46986
|
+
i = s.length;
|
|
46987
|
+
} else if (beginning && (c == "+" || c == "-")) {
|
|
46988
|
+
if (c == "-") {
|
|
46989
|
+
pos = !pos;
|
|
46990
|
+
}
|
|
46991
|
+
}
|
|
46992
|
+
}
|
|
46993
|
+
if (all == "") {
|
|
46994
|
+
return true;
|
|
46995
|
+
}
|
|
46996
|
+
return pos;
|
|
47281
46997
|
}
|
|
47282
46998
|
|
|
47283
46999
|
/**
|
|
47284
|
-
*
|
|
47285
|
-
*
|
|
47000
|
+
* Parse a string representation of a floating point
|
|
47001
|
+
* and pull out the mantissa.
|
|
47002
|
+
*
|
|
47003
|
+
* @param s the string representation of a floating point.
|
|
47004
|
+
* @return the mantissa of this number.
|
|
47286
47005
|
*/
|
|
47287
|
-
function
|
|
47288
|
-
|
|
47289
|
-
|
|
47290
|
-
|
|
47291
|
-
|
|
47292
|
-
|
|
47293
|
-
|
|
47294
|
-
|
|
47295
|
-
|
|
47296
|
-
|
|
47297
|
-
|
|
47298
|
-
|
|
47299
|
-
|
|
47300
|
-
|
|
47301
|
-
|
|
47302
|
-
|
|
47303
|
-
|
|
47304
|
-
|
|
47006
|
+
function parseMantissa(s) {
|
|
47007
|
+
let seenDot = false;
|
|
47008
|
+
let seenSomething = false;
|
|
47009
|
+
let zeros = "";
|
|
47010
|
+
let leadZeros = "";
|
|
47011
|
+
let all = "";
|
|
47012
|
+
for (let i = 0; i < s.length; i++) {
|
|
47013
|
+
const c = s.charAt(i);
|
|
47014
|
+
if (c >= "1" && c <= "9") {
|
|
47015
|
+
all += zeros + c;
|
|
47016
|
+
zeros = "";
|
|
47017
|
+
seenSomething = true;
|
|
47018
|
+
} else if (c == "0") {
|
|
47019
|
+
if (seenDot) {
|
|
47020
|
+
if (seenSomething) {
|
|
47021
|
+
all += zeros + c;
|
|
47022
|
+
zeros = "";
|
|
47023
|
+
} else {
|
|
47024
|
+
leadZeros += c;
|
|
47025
|
+
}
|
|
47026
|
+
} else {
|
|
47027
|
+
if (seenSomething) {
|
|
47028
|
+
leadZeros += c;
|
|
47029
|
+
zeros += c;
|
|
47030
|
+
} else {
|
|
47031
|
+
leadZeros += c;
|
|
47032
|
+
}
|
|
47033
|
+
}
|
|
47034
|
+
} else if (!seenDot && c == ".") {
|
|
47035
|
+
all += zeros;
|
|
47036
|
+
zeros = "";
|
|
47037
|
+
seenDot = true;
|
|
47038
|
+
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
47039
|
+
parseInt(s.substring(i + 1, s.length));
|
|
47040
|
+
i = s.length;
|
|
47041
|
+
} else ;
|
|
47305
47042
|
}
|
|
47306
|
-
if (
|
|
47307
|
-
return
|
|
47043
|
+
if (all == "") {
|
|
47044
|
+
return leadZeros;
|
|
47308
47045
|
}
|
|
47309
|
-
|
|
47310
|
-
|
|
47311
|
-
|
|
47046
|
+
return all;
|
|
47047
|
+
}
|
|
47048
|
+
|
|
47049
|
+
/**
|
|
47050
|
+
* Parse a string representation of a floating point
|
|
47051
|
+
* and pull out the exponent.
|
|
47052
|
+
*
|
|
47053
|
+
* @param s the string representation of a floating point.
|
|
47054
|
+
* @return (integer) the number after the e.
|
|
47055
|
+
*/
|
|
47056
|
+
function parseOrder(s) {
|
|
47057
|
+
let seenDot = false;
|
|
47058
|
+
let seenSomething = false;
|
|
47059
|
+
let zeros = "";
|
|
47060
|
+
let all = "";
|
|
47061
|
+
let decPlaces = 0;
|
|
47062
|
+
let totalDecs = 0;
|
|
47063
|
+
for (let i = 0; i < s.length; i++) {
|
|
47064
|
+
const c = s.charAt(i);
|
|
47065
|
+
if (c >= "1" && c <= "9") {
|
|
47066
|
+
all += zeros + c;
|
|
47067
|
+
zeros = "";
|
|
47068
|
+
seenSomething = true;
|
|
47069
|
+
if (!seenDot) {
|
|
47070
|
+
totalDecs++;
|
|
47071
|
+
decPlaces++;
|
|
47072
|
+
}
|
|
47073
|
+
} else if (c == "0") {
|
|
47074
|
+
if (seenDot) {
|
|
47075
|
+
if (seenSomething) {
|
|
47076
|
+
all += zeros + c;
|
|
47077
|
+
zeros = "";
|
|
47078
|
+
} else {
|
|
47079
|
+
decPlaces--;
|
|
47080
|
+
}
|
|
47081
|
+
} else {
|
|
47082
|
+
totalDecs++;
|
|
47083
|
+
if (seenSomething) {
|
|
47084
|
+
decPlaces++;
|
|
47085
|
+
zeros += c;
|
|
47086
|
+
}
|
|
47087
|
+
}
|
|
47088
|
+
} else if (!seenDot && c == ".") {
|
|
47089
|
+
all += zeros;
|
|
47090
|
+
zeros = "";
|
|
47091
|
+
seenDot = true;
|
|
47092
|
+
} else if (c == "e" || c == "E" && i + 1 < s.length) {
|
|
47093
|
+
const raised = parseInt(s.substring(i + 1, s.length));
|
|
47094
|
+
decPlaces += raised;
|
|
47095
|
+
totalDecs += raised;
|
|
47096
|
+
i = s.length;
|
|
47097
|
+
} else ;
|
|
47312
47098
|
}
|
|
47313
|
-
if (
|
|
47314
|
-
|
|
47315
|
-
const propTypeShape = {};
|
|
47316
|
-
Object.keys(valueShapes).forEach(key => {
|
|
47317
|
-
propTypeShape[key] = buildTreePropTypeForShape(valueShapes[key]).isRequired;
|
|
47318
|
-
});
|
|
47319
|
-
return PropTypes__default["default"].shape(propTypeShape);
|
|
47099
|
+
if (all == "") {
|
|
47100
|
+
return totalDecs;
|
|
47320
47101
|
}
|
|
47321
|
-
|
|
47322
|
-
// @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
|
|
47323
|
-
`unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
|
|
47102
|
+
return decPlaces;
|
|
47324
47103
|
}
|
|
47325
47104
|
|
|
47326
47105
|
/**
|
|
47327
|
-
*
|
|
47328
|
-
*
|
|
47329
|
-
*
|
|
47330
|
-
*
|
|
47331
|
-
*
|
|
47332
|
-
*
|
|
47333
|
-
*
|
|
47334
|
-
*
|
|
47335
|
-
* multi-rendering. To multi-render a question, pass in the content of the item
|
|
47336
|
-
* to the `MultiRenderer` component as a props. Then, pass in a function which
|
|
47337
|
-
* takes an object of renderers (in the same structure as the content), and
|
|
47338
|
-
* return a render tree. The `MultiRenderer` component will allow you to
|
|
47339
|
-
* combine scores, serialized state, etc. without having to manually call on
|
|
47340
|
-
* each of the functions. It also handles inter-widgets requests between the
|
|
47341
|
-
* different renderers.
|
|
47342
|
-
* For more details, see `multi-items/multi-renderer.jsx`.
|
|
47343
|
-
*
|
|
47344
|
-
* Example:
|
|
47345
|
-
*
|
|
47346
|
-
* item = {_multi: {
|
|
47347
|
-
* left: <content data>,
|
|
47348
|
-
* right: [<content data>, <content data>],
|
|
47349
|
-
* }}
|
|
47350
|
-
* shape = shapes.shape({
|
|
47351
|
-
* left: shapes.content,
|
|
47352
|
-
* right: shapes.arrayOf(shapes.content),
|
|
47353
|
-
* })
|
|
47354
|
-
*
|
|
47355
|
-
* <MultiRenderer item={item} shape={shape}>
|
|
47356
|
-
* {({renderers}) =>
|
|
47357
|
-
* <div>
|
|
47358
|
-
* <div id="left">{renderers.left}</div>
|
|
47359
|
-
* <ul id="right">
|
|
47360
|
-
* {renderers.right.map(r => <li>{r}</li>)}
|
|
47361
|
-
* </ul>
|
|
47362
|
-
* </div>
|
|
47363
|
-
* }
|
|
47364
|
-
* </MultiRenderer>
|
|
47365
|
-
*
|
|
47366
|
-
* This file also exposes `shapes`, which helps you construct a runtime type
|
|
47367
|
-
* declaration for your particular class of multi-item. This can then be used
|
|
47368
|
-
* to create a MultirendererEditor for your multi-item shape, and to validate
|
|
47369
|
-
* that a multi-item conforms to the shape via `buildPropTypeForShape`.
|
|
47370
|
-
* For more details, see `multi-items/shapes.js`.
|
|
47371
|
-
*
|
|
47372
|
-
* This file also exposes some utility functions for working with generic
|
|
47373
|
-
* multi-items, like `findContentNodesInItem`, `findHintNodesInItem`,
|
|
47374
|
-
* `inferItemShape`, and `buildEmptyItemForShape`.
|
|
47375
|
-
* For more details, see `multi-items/items.js`.
|
|
47376
|
-
*/
|
|
47377
|
-
var MultiItems = {
|
|
47378
|
-
// Tools for rendering your multi-items
|
|
47379
|
-
MultiRenderer,
|
|
47380
|
-
// Tools for declaring your multi-item shapes
|
|
47381
|
-
shapes,
|
|
47382
|
-
buildPropTypeForShape,
|
|
47383
|
-
// Tools for generically manipulating multi-items
|
|
47384
|
-
buildEmptyItemForShape,
|
|
47385
|
-
findContentNodesInItem: findContentNodesInItem$2,
|
|
47386
|
-
findHintNodesInItem,
|
|
47387
|
-
inferItemShape: inferItemShape$2
|
|
47388
|
-
};
|
|
47389
|
-
|
|
47390
|
-
/**
|
|
47391
|
-
* Calculates whether a perseus item is renderable by a specific
|
|
47392
|
-
* perseus-item-data version.
|
|
47106
|
+
* Remove the specified number of digits from string by
|
|
47107
|
+
* rounding. Proper rounding rules for scientific purposes
|
|
47108
|
+
* are followed.
|
|
47109
|
+
* This method may cause an extra significant figure
|
|
47110
|
+
* to be added to the number. For example, if 999999
|
|
47111
|
+
* were rounded, A one would carry over and become
|
|
47112
|
+
* a significant figure. Those who call this method
|
|
47113
|
+
* should check for this and call round again if needed.
|
|
47393
47114
|
*
|
|
47394
|
-
*
|
|
47395
|
-
*
|
|
47396
|
-
*
|
|
47397
|
-
* group or sequence widgets.
|
|
47115
|
+
* @param mantissa A string representing an whole number of arbitrary length.
|
|
47116
|
+
* @param digits A number of digits to remove
|
|
47117
|
+
* @return A string represted the rounded version of mantissa
|
|
47398
47118
|
*/
|
|
47399
|
-
|
|
47400
|
-
|
|
47401
|
-
|
|
47402
|
-
|
|
47403
|
-
const isUpgradedWidgetInfoRenderableBy = function (widgetInfo, widgetRendererVersion) {
|
|
47404
|
-
if (widgetRendererVersion == null) {
|
|
47405
|
-
// If the widget does not exist in this version, this will
|
|
47406
|
-
// be null, and that version of perseus cannot render the
|
|
47407
|
-
// widget (it doesn't even know the widget exists!)
|
|
47408
|
-
return false;
|
|
47409
|
-
}
|
|
47410
|
-
const widgetVersion = widgetInfo.version || {
|
|
47411
|
-
major: 0,
|
|
47412
|
-
minor: 0
|
|
47413
|
-
};
|
|
47414
|
-
if (widgetRendererVersion.major > widgetVersion.major) {
|
|
47415
|
-
return true;
|
|
47416
|
-
}
|
|
47417
|
-
if (widgetRendererVersion.major < widgetVersion.major) {
|
|
47418
|
-
return false;
|
|
47419
|
-
}
|
|
47420
|
-
// If the major versions are the same, the minor version acts
|
|
47421
|
-
// like a tie-breaker.
|
|
47422
|
-
// For example, input-number 3.2 can render an input-number
|
|
47423
|
-
// 2.4, 3.0, or 3.2, but not an input number 3.3 or 4.0.
|
|
47424
|
-
return widgetRendererVersion.minor >= widgetVersion.minor;
|
|
47425
|
-
};
|
|
47426
|
-
const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion) {
|
|
47427
|
-
// Empty/non-existant widgets are always safe to render
|
|
47428
|
-
if (widgetInfo == null || widgetInfo.type == null) {
|
|
47429
|
-
return true;
|
|
47119
|
+
function round(mantissa, digits) {
|
|
47120
|
+
const last = mantissa.length - digits - 1;
|
|
47121
|
+
if (last < 0) {
|
|
47122
|
+
return "";
|
|
47430
47123
|
}
|
|
47431
|
-
|
|
47432
|
-
|
|
47433
|
-
// is at a later version than is supported.
|
|
47434
|
-
const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
47435
|
-
return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
|
|
47436
|
-
};
|
|
47437
|
-
const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
|
|
47438
|
-
let isRenderable = true;
|
|
47439
|
-
traverse(rendererOptions, null, function (widgetInfo) {
|
|
47440
|
-
isRenderable = isRenderable && isRawWidgetInfoRenderableBy(widgetInfo, rendererContentVersion);
|
|
47441
|
-
});
|
|
47442
|
-
return isRenderable;
|
|
47443
|
-
};
|
|
47444
|
-
const isItemRenderableByVersion = function (itemData, rendererContentVersion) {
|
|
47445
|
-
if (itemData == null || rendererContentVersion == null) {
|
|
47446
|
-
throw new perseusCore.PerseusError("missing parameter to Perseus.isRenderable.item", perseusCore.Errors.InvalidInput);
|
|
47124
|
+
if (last >= mantissa.length - 1) {
|
|
47125
|
+
return mantissa;
|
|
47447
47126
|
}
|
|
47448
|
-
|
|
47449
|
-
|
|
47450
|
-
|
|
47451
|
-
|
|
47452
|
-
|
|
47453
|
-
|
|
47454
|
-
|
|
47127
|
+
const nextToLast = mantissa.charAt(last + 1);
|
|
47128
|
+
const lastChar = mantissa.charAt(last);
|
|
47129
|
+
let roundUp = false;
|
|
47130
|
+
if (nextToLast > "5") {
|
|
47131
|
+
roundUp = true;
|
|
47132
|
+
} else if (nextToLast == "5") {
|
|
47133
|
+
for (let j = last + 2; j < mantissa.length; j++) {
|
|
47134
|
+
if (mantissa.charAt(j) != "0") {
|
|
47135
|
+
roundUp = true;
|
|
47455
47136
|
}
|
|
47456
|
-
}
|
|
47457
|
-
|
|
47137
|
+
}
|
|
47138
|
+
// @ts-expect-error - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
|
|
47139
|
+
if (lastChar % 2 == 1) {
|
|
47140
|
+
roundUp = true;
|
|
47141
|
+
}
|
|
47458
47142
|
}
|
|
47459
|
-
|
|
47460
|
-
|
|
47461
|
-
|
|
47462
|
-
|
|
47463
|
-
|
|
47464
|
-
|
|
47465
|
-
|
|
47466
|
-
|
|
47467
|
-
|
|
47468
|
-
|
|
47469
|
-
|
|
47470
|
-
|
|
47471
|
-
|
|
47472
|
-
|
|
47473
|
-
|
|
47474
|
-
|
|
47475
|
-
|
|
47476
|
-
|
|
47143
|
+
let result = "";
|
|
47144
|
+
for (let i = last; i >= 0; i--) {
|
|
47145
|
+
const c = mantissa.charAt(i);
|
|
47146
|
+
if (roundUp) {
|
|
47147
|
+
let nextChar;
|
|
47148
|
+
if (c == "9") {
|
|
47149
|
+
nextChar = "0";
|
|
47150
|
+
} else {
|
|
47151
|
+
switch (c) {
|
|
47152
|
+
case "0":
|
|
47153
|
+
nextChar = "1";
|
|
47154
|
+
break;
|
|
47155
|
+
case "1":
|
|
47156
|
+
nextChar = "2";
|
|
47157
|
+
break;
|
|
47158
|
+
case "2":
|
|
47159
|
+
nextChar = "3";
|
|
47160
|
+
break;
|
|
47161
|
+
case "3":
|
|
47162
|
+
nextChar = "4";
|
|
47163
|
+
break;
|
|
47164
|
+
case "4":
|
|
47165
|
+
nextChar = "5";
|
|
47166
|
+
break;
|
|
47167
|
+
case "5":
|
|
47168
|
+
nextChar = "6";
|
|
47169
|
+
break;
|
|
47170
|
+
case "6":
|
|
47171
|
+
nextChar = "7";
|
|
47172
|
+
break;
|
|
47173
|
+
case "7":
|
|
47174
|
+
nextChar = "8";
|
|
47175
|
+
break;
|
|
47176
|
+
case "8":
|
|
47177
|
+
nextChar = "9";
|
|
47178
|
+
break;
|
|
47179
|
+
}
|
|
47180
|
+
roundUp = false;
|
|
47181
|
+
}
|
|
47182
|
+
result = nextChar + result;
|
|
47183
|
+
} else {
|
|
47184
|
+
result = c + result;
|
|
47477
47185
|
}
|
|
47478
|
-
});
|
|
47479
|
-
}
|
|
47480
|
-
|
|
47481
|
-
// Returns a list of widgets that cause a given perseus item to require
|
|
47482
|
-
// the use of a screen or mouse.
|
|
47483
|
-
//
|
|
47484
|
-
// For now we'll just check the `accessible` field on each of the widgets
|
|
47485
|
-
// in the item data, but in the future we may specify accessibility on
|
|
47486
|
-
// each widget with higher granularity.
|
|
47487
|
-
function violatingWidgets(itemData) {
|
|
47488
|
-
// TODO(jordan): Hints as well
|
|
47489
|
-
const widgets = [];
|
|
47490
|
-
if (itemData._multi) {
|
|
47491
|
-
const shape = inferItemShape(itemData);
|
|
47492
|
-
findContentNodesInItem(itemData, shape, content => traverseRenderer(content, widgets));
|
|
47493
|
-
} else {
|
|
47494
|
-
traverseRenderer(itemData.question, widgets);
|
|
47495
47186
|
}
|
|
47496
|
-
|
|
47497
|
-
|
|
47498
|
-
|
|
47187
|
+
if (roundUp) {
|
|
47188
|
+
result = "1" + result;
|
|
47189
|
+
}
|
|
47190
|
+
return result;
|
|
47499
47191
|
}
|
|
47500
47192
|
|
|
47501
47193
|
/**
|
|
@@ -48219,9 +47911,6 @@ function generateTestInteractiveGraphWidget() {
|
|
|
48219
47911
|
markings: "graph",
|
|
48220
47912
|
labels: ["x", "y"],
|
|
48221
47913
|
showProtractor: false,
|
|
48222
|
-
showRuler: false,
|
|
48223
|
-
rulerLabel: "mm",
|
|
48224
|
-
rulerTicks: 2,
|
|
48225
47914
|
range: [[-10, 10], [-10, 10]],
|
|
48226
47915
|
graph: {
|
|
48227
47916
|
type: "angle"
|
|
@@ -48333,7 +48022,6 @@ const widgetNameToEnum = {
|
|
|
48333
48022
|
simulator: "SIMULATOR",
|
|
48334
48023
|
sorter: "SORTER",
|
|
48335
48024
|
table: "TABLE",
|
|
48336
|
-
"unit-input": "UNIT",
|
|
48337
48025
|
video: "VIDEO"
|
|
48338
48026
|
};
|
|
48339
48027
|
function convertWidgetNameToEnum(name) {
|
|
@@ -48415,6 +48103,8 @@ exports.getAnswerFromUserInput = getAnswerFromUserInput;
|
|
|
48415
48103
|
exports.getAnswersFromWidgets = getAnswersFromWidgets;
|
|
48416
48104
|
exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
|
|
48417
48105
|
exports.getLineCoords = getLineCoords;
|
|
48106
|
+
exports.getLinearSystemCoords = getLinearSystemCoords;
|
|
48107
|
+
exports.getSegmentCoords = getSegmentCoords;
|
|
48418
48108
|
exports.getValidWidgetIds = getValidWidgetIds;
|
|
48419
48109
|
exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
|
|
48420
48110
|
exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
|