@khanacademy/perseus 20.8.0 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +663 -378
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +674 -373
- package/dist/index.js.map +1 -1
- package/dist/util/extract-perseus-data.d.ts +6 -7
- package/dist/util/snowman-utils.d.ts +4 -77
- package/dist/util/test-utils.d.ts +37 -2
- package/dist/util/widget-enum-utils.d.ts +1 -3
- package/dist/widget-type-utils.d.ts +64 -0
- package/dist/widgets/interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/components/angle.d.ts +23 -0
- package/dist/widgets/interactive-graphs/graphs/components/css-transform-wrapper.d.ts +7 -0
- package/dist/widgets/interactive-graphs/graphs/components/movable-point.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +6 -0
- package/dist/widgets/interactive-graphs/graphs/use-transform.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +9 -0
- package/dist/widgets/interactive-graphs/legacy-grid.d.ts +6 -1
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +3 -0
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +1 -1
- package/package.json +2 -6
package/dist/es/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as KAS from '@khanacademy/kas';
|
|
|
2
2
|
import { keyTranslator, CursorContext, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
3
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
4
4
|
import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
|
|
5
|
-
import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$
|
|
5
|
+
import { View, RenderStateRoot, UniqueIDProvider, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
6
6
|
import * as i18n from '@khanacademy/wonder-blocks-i18n';
|
|
7
7
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
8
8
|
import classNames from 'classnames';
|
|
@@ -10,9 +10,8 @@ import * as React from 'react';
|
|
|
10
10
|
import React__default, { useState, useEffect, useRef } from 'react';
|
|
11
11
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
12
12
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
13
|
-
import Color, { fade } from '@khanacademy/wonder-blocks-color';
|
|
14
13
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
15
|
-
import
|
|
14
|
+
import { color, spacing } from '@khanacademy/wonder-blocks-tokens';
|
|
16
15
|
import { StyleSheet, css } from 'aphrodite';
|
|
17
16
|
import $ from 'jquery';
|
|
18
17
|
import PropTypes from 'prop-types';
|
|
@@ -31,8 +30,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
31
30
|
import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
|
|
32
31
|
import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
|
|
33
32
|
import debounce$2 from 'lodash.debounce';
|
|
34
|
-
import { Point as Point$2,
|
|
35
|
-
import { color } from '@khanacademy/wonder-blocks-tokens';
|
|
33
|
+
import { Point as Point$2, useTransformContext, vec, useMovable, Vector, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
|
|
36
34
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
37
35
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
38
36
|
import { LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -331,16 +329,16 @@ const MathInputIcon = ({
|
|
|
331
329
|
focused,
|
|
332
330
|
active
|
|
333
331
|
}) => {
|
|
334
|
-
let
|
|
332
|
+
let fillColor;
|
|
335
333
|
switch (true) {
|
|
336
334
|
case focused || active:
|
|
337
|
-
|
|
335
|
+
fillColor = color.white;
|
|
338
336
|
break;
|
|
339
337
|
case hovered:
|
|
340
|
-
|
|
338
|
+
fillColor = color.blue;
|
|
341
339
|
break;
|
|
342
340
|
default:
|
|
343
|
-
|
|
341
|
+
fillColor = color.offBlack;
|
|
344
342
|
break;
|
|
345
343
|
}
|
|
346
344
|
const dynamicClass = active || focused ? styles$D.iconActive : styles$D.iconInactive;
|
|
@@ -350,7 +348,7 @@ const MathInputIcon = ({
|
|
|
350
348
|
xmlns: "http://www.w3.org/2000/svg",
|
|
351
349
|
width: "16",
|
|
352
350
|
height: "16",
|
|
353
|
-
fill:
|
|
351
|
+
fill: fillColor,
|
|
354
352
|
viewBox: "0 0 256 256"
|
|
355
353
|
}, /*#__PURE__*/React.createElement("path", {
|
|
356
354
|
d: "M112,72a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16h64A8,8,0,0,1,112,72Zm-8,104H80V152a8,8,0,0,0-16,0v24H40a8,8,0,0,0,0,16H64v24a8,8,0,0,0,16,0V192h24a8,8,0,0,0,0-16Zm48,0h64a8,8,0,0,0,0-16H152a8,8,0,0,0,0,16Zm64,16H152a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm-61.66-90.34a8,8,0,0,0,11.32,0L184,83.31l18.34,18.35a8,8,0,0,0,11.32-11.32L195.31,72l18.35-18.34a8,8,0,0,0-11.32-11.32L184,60.69,165.66,42.34a8,8,0,0,0-11.32,11.32L172.69,72,154.34,90.34A8,8,0,0,0,154.34,101.66Z"
|
|
@@ -387,7 +385,7 @@ const mapButtonSets = buttonSets => {
|
|
|
387
385
|
};
|
|
388
386
|
const inputFocused = {
|
|
389
387
|
borderWidth: 2,
|
|
390
|
-
borderColor:
|
|
388
|
+
borderColor: color.blue,
|
|
391
389
|
margin: -1
|
|
392
390
|
};
|
|
393
391
|
const styles$D = StyleSheet.create({
|
|
@@ -395,35 +393,35 @@ const styles$D = StyleSheet.create({
|
|
|
395
393
|
display: "flex",
|
|
396
394
|
justifyContent: "center",
|
|
397
395
|
height: "100%",
|
|
398
|
-
padding:
|
|
396
|
+
padding: spacing.xxxSmall_4,
|
|
399
397
|
borderRadius: 1
|
|
400
398
|
},
|
|
401
399
|
iconInactive: {
|
|
402
|
-
backgroundColor:
|
|
400
|
+
backgroundColor: color.offBlack8
|
|
403
401
|
},
|
|
404
402
|
iconActive: {
|
|
405
|
-
backgroundColor:
|
|
403
|
+
backgroundColor: color.offBlack64
|
|
406
404
|
},
|
|
407
405
|
outerWrapper: {
|
|
408
406
|
display: "inline-block",
|
|
409
407
|
borderStyle: "solid",
|
|
410
408
|
borderWidth: 1,
|
|
411
|
-
borderColor:
|
|
409
|
+
borderColor: color.offBlack50,
|
|
412
410
|
borderRadius: 3,
|
|
413
|
-
background:
|
|
411
|
+
background: color.white,
|
|
414
412
|
":hover": inputFocused
|
|
415
413
|
},
|
|
416
414
|
wrapperFocused: inputFocused,
|
|
417
415
|
wrapperError: {
|
|
418
|
-
borderColor:
|
|
419
|
-
background:
|
|
416
|
+
borderColor: color.red,
|
|
417
|
+
background: color.fadedRed8,
|
|
420
418
|
":hover": {
|
|
421
|
-
borderColor:
|
|
419
|
+
borderColor: color.red
|
|
422
420
|
}
|
|
423
421
|
},
|
|
424
422
|
popoverContent: {
|
|
425
423
|
padding: 0,
|
|
426
|
-
paddingBottom:
|
|
424
|
+
paddingBottom: spacing.xxSmall_6,
|
|
427
425
|
maxWidth: "initial"
|
|
428
426
|
}
|
|
429
427
|
});
|
|
@@ -1923,7 +1921,7 @@ const KhanAnswerTypes = {
|
|
|
1923
1921
|
}
|
|
1924
1922
|
};
|
|
1925
1923
|
|
|
1926
|
-
const _excluded$
|
|
1924
|
+
const _excluded$5 = ["scoring"];
|
|
1927
1925
|
const ERROR_TITLE = i18n._("Oops!");
|
|
1928
1926
|
const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
|
|
1929
1927
|
|
|
@@ -2011,7 +2009,7 @@ class Expression extends React.Component {
|
|
|
2011
2009
|
let {
|
|
2012
2010
|
scoring
|
|
2013
2011
|
} = _ref,
|
|
2014
|
-
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
2012
|
+
rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
|
|
2015
2013
|
const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
|
|
2016
2014
|
|
|
2017
2015
|
// "scoring" is a flag that indicates when we are checking answers.
|
|
@@ -18834,7 +18832,7 @@ const isAccessible = widgetInfo => {
|
|
|
18834
18832
|
}
|
|
18835
18833
|
return !!accessible;
|
|
18836
18834
|
};
|
|
18837
|
-
const getAllWidgetTypes
|
|
18835
|
+
const getAllWidgetTypes = () => {
|
|
18838
18836
|
return _.keys(widgets);
|
|
18839
18837
|
};
|
|
18840
18838
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
@@ -19103,7 +19101,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19103
19101
|
getVersionVector: getVersionVector,
|
|
19104
19102
|
getPublicWidgets: getPublicWidgets,
|
|
19105
19103
|
isAccessible: isAccessible,
|
|
19106
|
-
getAllWidgetTypes: getAllWidgetTypes
|
|
19104
|
+
getAllWidgetTypes: getAllWidgetTypes,
|
|
19107
19105
|
upgradeWidgetInfoToLatestVersion: upgradeWidgetInfoToLatestVersion,
|
|
19108
19106
|
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
19109
19107
|
traverseChildWidgets: traverseChildWidgets$3,
|
|
@@ -19341,6 +19339,9 @@ class Renderer extends React.Component {
|
|
|
19341
19339
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19342
19340
|
const newValues = {};
|
|
19343
19341
|
if (!widgetInfo.type) {
|
|
19342
|
+
// TODO: why does widget have no type?
|
|
19343
|
+
// We don't want to derive type from widget ID
|
|
19344
|
+
// see: LEMS-1845
|
|
19344
19345
|
newValues.type = widgetId.split(" ")[0];
|
|
19345
19346
|
}
|
|
19346
19347
|
if (!widgetInfo.alignment) {
|
|
@@ -21430,7 +21431,7 @@ InputWithExamples.defaultProps = {
|
|
|
21430
21431
|
className: ""
|
|
21431
21432
|
};
|
|
21432
21433
|
|
|
21433
|
-
const _excluded$
|
|
21434
|
+
const _excluded$4 = ["keypadElement", "onFocus", "value"];
|
|
21434
21435
|
class SimpleKeypadInput extends React.Component {
|
|
21435
21436
|
constructor(...args) {
|
|
21436
21437
|
super(...args);
|
|
@@ -21469,7 +21470,7 @@ class SimpleKeypadInput extends React.Component {
|
|
|
21469
21470
|
onFocus,
|
|
21470
21471
|
value
|
|
21471
21472
|
} = _this$props,
|
|
21472
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
21473
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
|
|
21473
21474
|
return (
|
|
21474
21475
|
/*#__PURE__*/
|
|
21475
21476
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
@@ -21763,13 +21764,13 @@ const styles$A = StyleSheet.create({
|
|
|
21763
21764
|
width: 40
|
|
21764
21765
|
},
|
|
21765
21766
|
leftAlign: {
|
|
21766
|
-
paddingLeft:
|
|
21767
|
+
paddingLeft: spacing.xxxSmall_4,
|
|
21767
21768
|
paddingRight: 0
|
|
21768
21769
|
},
|
|
21769
21770
|
rightAlign: {
|
|
21770
21771
|
textAlign: "right",
|
|
21771
21772
|
paddingLeft: 0,
|
|
21772
|
-
paddingRight:
|
|
21773
|
+
paddingRight: spacing.xxxSmall_4
|
|
21773
21774
|
},
|
|
21774
21775
|
answerStateUnanswered: {
|
|
21775
21776
|
backgroundColor: "#eee",
|
|
@@ -22867,19 +22868,19 @@ function ChoiceInner(props) {
|
|
|
22867
22868
|
function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, correct) {
|
|
22868
22869
|
let backgroundColor;
|
|
22869
22870
|
let borderColor;
|
|
22870
|
-
let color;
|
|
22871
|
+
let color$1;
|
|
22871
22872
|
if (!showCorrectness && pressed) {
|
|
22872
|
-
borderColor =
|
|
22873
|
-
color =
|
|
22873
|
+
borderColor = color.blue;
|
|
22874
|
+
color$1 = color.blue;
|
|
22874
22875
|
backgroundColor = "transparent";
|
|
22875
22876
|
} else if (checked) {
|
|
22876
|
-
const bg = showCorrectness && correct ?
|
|
22877
|
-
color = white$1;
|
|
22877
|
+
const bg = showCorrectness && correct ? color.green : color.blue;
|
|
22878
|
+
color$1 = white$1;
|
|
22878
22879
|
backgroundColor = bg;
|
|
22879
22880
|
borderColor = bg;
|
|
22880
22881
|
} else {
|
|
22881
|
-
borderColor =
|
|
22882
|
-
color =
|
|
22882
|
+
borderColor = color.offBlack64;
|
|
22883
|
+
color$1 = color.offBlack64;
|
|
22883
22884
|
}
|
|
22884
22885
|
|
|
22885
22886
|
// define shape
|
|
@@ -22892,7 +22893,7 @@ function getDynamicStyles(checked, showCorrectness, pressed, multipleSelect, cor
|
|
|
22892
22893
|
return {
|
|
22893
22894
|
backgroundColor,
|
|
22894
22895
|
borderColor,
|
|
22895
|
-
color,
|
|
22896
|
+
color: color$1,
|
|
22896
22897
|
borderRadius
|
|
22897
22898
|
};
|
|
22898
22899
|
}
|
|
@@ -22913,7 +22914,7 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
22913
22914
|
return /*#__PURE__*/React.createElement("div", {
|
|
22914
22915
|
className: css(styles$y.iconWrapper)
|
|
22915
22916
|
}, /*#__PURE__*/React.createElement(FocusRing, {
|
|
22916
|
-
color:
|
|
22917
|
+
color: color.blue,
|
|
22917
22918
|
visible: focused || hovered,
|
|
22918
22919
|
multipleSelect: multipleSelect
|
|
22919
22920
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -22978,9 +22979,9 @@ const styles$w = StyleSheet.create({
|
|
|
22978
22979
|
color: gray68
|
|
22979
22980
|
},
|
|
22980
22981
|
circleIncorrectAnswered: {
|
|
22981
|
-
backgroundColor:
|
|
22982
|
-
borderColor:
|
|
22983
|
-
color:
|
|
22982
|
+
backgroundColor: color.red,
|
|
22983
|
+
borderColor: color.red,
|
|
22984
|
+
color: color.white
|
|
22984
22985
|
}
|
|
22985
22986
|
});
|
|
22986
22987
|
|
|
@@ -23048,13 +23049,13 @@ const styles$v = StyleSheet.create({
|
|
|
23048
23049
|
textTransform: "uppercase"
|
|
23049
23050
|
},
|
|
23050
23051
|
correct: {
|
|
23051
|
-
color:
|
|
23052
|
+
color: color.green
|
|
23052
23053
|
},
|
|
23053
23054
|
incorrectAnswered: {
|
|
23054
|
-
color:
|
|
23055
|
+
color: color.red
|
|
23055
23056
|
},
|
|
23056
23057
|
incorrect: {
|
|
23057
|
-
color:
|
|
23058
|
+
color: color.offBlack64
|
|
23058
23059
|
}
|
|
23059
23060
|
});
|
|
23060
23061
|
|
|
@@ -23129,7 +23130,7 @@ const Choice = function Choice(props) {
|
|
|
23129
23130
|
// @ts-expect-error - TS2322 - Type '{ dispay: string; flexDirection: "column"; color: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
|
|
23130
23131
|
dispay: "flex",
|
|
23131
23132
|
flexDirection: "column",
|
|
23132
|
-
color:
|
|
23133
|
+
color: color.offBlack
|
|
23133
23134
|
},
|
|
23134
23135
|
className: descriptionClassName
|
|
23135
23136
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -23172,7 +23173,7 @@ const Choice = function Choice(props) {
|
|
|
23172
23173
|
disabled: disabled || apiOptions.readOnly,
|
|
23173
23174
|
style: {
|
|
23174
23175
|
flex: 1,
|
|
23175
|
-
color:
|
|
23176
|
+
color: color.offBlack,
|
|
23176
23177
|
userSelect: "text"
|
|
23177
23178
|
},
|
|
23178
23179
|
ref: forwardedRef,
|
|
@@ -23187,9 +23188,9 @@ const Choice = function Choice(props) {
|
|
|
23187
23188
|
flexDirection: "row",
|
|
23188
23189
|
justifyContent: "center",
|
|
23189
23190
|
alignContent: "center",
|
|
23190
|
-
paddingTop:
|
|
23191
|
-
paddingBottom:
|
|
23192
|
-
paddingLeft:
|
|
23191
|
+
paddingTop: spacing.xSmall_8,
|
|
23192
|
+
paddingBottom: spacing.xSmall_8,
|
|
23193
|
+
paddingLeft: spacing.xSmall_8
|
|
23193
23194
|
}
|
|
23194
23195
|
}, /*#__PURE__*/React.createElement(ChoiceIcon, {
|
|
23195
23196
|
pos: pos,
|
|
@@ -23205,7 +23206,7 @@ const Choice = function Choice(props) {
|
|
|
23205
23206
|
previouslyAnswered: previouslyAnswered
|
|
23206
23207
|
}), /*#__PURE__*/React.createElement("span", {
|
|
23207
23208
|
style: {
|
|
23208
|
-
paddingLeft:
|
|
23209
|
+
paddingLeft: spacing.small_12,
|
|
23209
23210
|
textAlign: "left",
|
|
23210
23211
|
flex: 1,
|
|
23211
23212
|
paddingTop: 4
|
|
@@ -23225,7 +23226,7 @@ const Choice = function Choice(props) {
|
|
|
23225
23226
|
content: i18n._("Cross out option"),
|
|
23226
23227
|
closeButtonVisible: true,
|
|
23227
23228
|
actions: /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(Strut, {
|
|
23228
|
-
size:
|
|
23229
|
+
size: spacing.medium_16
|
|
23229
23230
|
}), /*#__PURE__*/React.createElement(Button, {
|
|
23230
23231
|
kind: "primary",
|
|
23231
23232
|
"aria-label": i18n._("Cross out Choice %(letter)s", {
|
|
@@ -23273,7 +23274,7 @@ const Choice = function Choice(props) {
|
|
|
23273
23274
|
}) => /*#__PURE__*/React.createElement(Icon, {
|
|
23274
23275
|
icon: ellipsisHorizontalIcon,
|
|
23275
23276
|
size: 3,
|
|
23276
|
-
color:
|
|
23277
|
+
color: color.offBlack64
|
|
23277
23278
|
})))), showRationale && /*#__PURE__*/React.createElement("div", {
|
|
23278
23279
|
className: rationaleClassName,
|
|
23279
23280
|
"data-test-id": `perseus-radio-rationale-content-${pos}`
|
|
@@ -23289,7 +23290,7 @@ const styles$u = StyleSheet.create({
|
|
|
23289
23290
|
padding: intermediateCheckboxPadding,
|
|
23290
23291
|
paddingTop: 0,
|
|
23291
23292
|
marginLeft: 54,
|
|
23292
|
-
color:
|
|
23293
|
+
color: color.offBlack64,
|
|
23293
23294
|
[mediaQueries.smOrSmaller]: {
|
|
23294
23295
|
padding: intermediateCheckboxPaddingPhone,
|
|
23295
23296
|
paddingTop: 0
|
|
@@ -23300,14 +23301,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
|
|
|
23300
23301
|
forwardedRef: ref
|
|
23301
23302
|
})));
|
|
23302
23303
|
|
|
23303
|
-
const _excluded$
|
|
23304
|
+
const _excluded$3 = ["showContent", "content", "forwardedRef"];
|
|
23304
23305
|
const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
|
|
23305
23306
|
const {
|
|
23306
23307
|
showContent,
|
|
23307
23308
|
content,
|
|
23308
23309
|
forwardedRef
|
|
23309
23310
|
} = props,
|
|
23310
|
-
rest = _objectWithoutPropertiesLoose(props, _excluded$
|
|
23311
|
+
rest = _objectWithoutPropertiesLoose(props, _excluded$3);
|
|
23311
23312
|
const choiceProps = _extends({}, rest, {
|
|
23312
23313
|
content: showContent ? content :
|
|
23313
23314
|
/*#__PURE__*/
|
|
@@ -24687,8 +24688,8 @@ class Definition extends React.Component {
|
|
|
24687
24688
|
pressed
|
|
24688
24689
|
}) => /*#__PURE__*/React.createElement("span", {
|
|
24689
24690
|
style: {
|
|
24690
|
-
color:
|
|
24691
|
-
borderBottom: hovered || focused || pressed ? `2px solid ${
|
|
24691
|
+
color: color.blue,
|
|
24692
|
+
borderBottom: hovered || focused || pressed ? `2px solid ${color.blue}` : "none"
|
|
24692
24693
|
}
|
|
24693
24694
|
}, this.props.togglePrompt))));
|
|
24694
24695
|
}
|
|
@@ -24699,7 +24700,7 @@ Definition.defaultProps = {
|
|
|
24699
24700
|
};
|
|
24700
24701
|
const styles$q = {
|
|
24701
24702
|
tooltipBody: {
|
|
24702
|
-
color:
|
|
24703
|
+
color: color.offBlack,
|
|
24703
24704
|
fontSize: 20,
|
|
24704
24705
|
fontWeight: 500,
|
|
24705
24706
|
lineHeight: "30px"
|
|
@@ -24999,7 +25000,7 @@ class GradedGroupAnswerBar extends React.Component {
|
|
|
24999
25000
|
onNextQuestion
|
|
25000
25001
|
} = this.props;
|
|
25001
25002
|
const answerBarStyle = _extends({}, styles$o.answerBar, {
|
|
25002
|
-
backgroundColor: answerBarState === "CORRECT" ?
|
|
25003
|
+
backgroundColor: answerBarState === "CORRECT" ? color.offWhite : "white",
|
|
25003
25004
|
// Center the "Correct!" message only when there's no next question
|
|
25004
25005
|
justifyContent: answerBarState === "CORRECT" && !onNextQuestion ? "center" : "space-between"
|
|
25005
25006
|
});
|
|
@@ -25029,7 +25030,7 @@ class GradedGroupAnswerBar extends React.Component {
|
|
|
25029
25030
|
}, /*#__PURE__*/React.createElement("span", {
|
|
25030
25031
|
style: {
|
|
25031
25032
|
fontSize: 28,
|
|
25032
|
-
color:
|
|
25033
|
+
color: color.green
|
|
25033
25034
|
}
|
|
25034
25035
|
}, /*#__PURE__*/React.createElement(InlineIcon, _extends({}, iconStar, {
|
|
25035
25036
|
style: {
|
|
@@ -25059,7 +25060,7 @@ const styles$o = {
|
|
|
25059
25060
|
marginTop: phoneMargin,
|
|
25060
25061
|
paddingLeft: phoneMargin,
|
|
25061
25062
|
paddingRight: 10,
|
|
25062
|
-
borderTop: `1px solid ${
|
|
25063
|
+
borderTop: `1px solid ${color.offBlack50}`
|
|
25063
25064
|
},
|
|
25064
25065
|
tryAgainIcon: {
|
|
25065
25066
|
fontSize: 28,
|
|
@@ -25361,7 +25362,7 @@ const styles$n = StyleSheet.create({
|
|
|
25361
25362
|
padding: 0,
|
|
25362
25363
|
border: "none",
|
|
25363
25364
|
marginTop: 20,
|
|
25364
|
-
color:
|
|
25365
|
+
color: color.blue,
|
|
25365
25366
|
cursor: "pointer",
|
|
25366
25367
|
display: "block",
|
|
25367
25368
|
clear: "both"
|
|
@@ -25369,7 +25370,7 @@ const styles$n = StyleSheet.create({
|
|
|
25369
25370
|
explanationTitle: {
|
|
25370
25371
|
backgroundColor: "unset",
|
|
25371
25372
|
marginTop: 20,
|
|
25372
|
-
color:
|
|
25373
|
+
color: color.blue,
|
|
25373
25374
|
marginBottom: 10,
|
|
25374
25375
|
cursor: "pointer",
|
|
25375
25376
|
fontSize: 14,
|
|
@@ -25578,7 +25579,7 @@ const styles$m = StyleSheet.create({
|
|
|
25578
25579
|
},
|
|
25579
25580
|
title: {
|
|
25580
25581
|
fontSize: 12,
|
|
25581
|
-
color:
|
|
25582
|
+
color: color.offBlack64,
|
|
25582
25583
|
textTransform: "uppercase",
|
|
25583
25584
|
marginBottom: 11,
|
|
25584
25585
|
letterSpacing: 0.8
|
|
@@ -25612,7 +25613,7 @@ const styles$m = StyleSheet.create({
|
|
|
25612
25613
|
height: 10,
|
|
25613
25614
|
borderRadius: "100%",
|
|
25614
25615
|
borderWidth: 2,
|
|
25615
|
-
borderColor:
|
|
25616
|
+
borderColor: color.blue,
|
|
25616
25617
|
borderStyle: "solid"
|
|
25617
25618
|
},
|
|
25618
25619
|
indicatorDotFocused: {
|
|
@@ -25620,7 +25621,7 @@ const styles$m = StyleSheet.create({
|
|
|
25620
25621
|
borderStyle: "double"
|
|
25621
25622
|
},
|
|
25622
25623
|
indicatorDotActive: {
|
|
25623
|
-
backgroundColor:
|
|
25624
|
+
backgroundColor: color.blue,
|
|
25624
25625
|
width: "100%",
|
|
25625
25626
|
height: "100%"
|
|
25626
25627
|
},
|
|
@@ -28216,6 +28217,58 @@ const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.
|
|
|
28216
28217
|
return (coord + ranges[i][1]) / extent;
|
|
28217
28218
|
}));
|
|
28218
28219
|
|
|
28220
|
+
const useTransform = (...points) => {
|
|
28221
|
+
const {
|
|
28222
|
+
viewTransform,
|
|
28223
|
+
userTransform
|
|
28224
|
+
} = useTransformContext();
|
|
28225
|
+
const transformToPx = vec.matrixMult(viewTransform, userTransform);
|
|
28226
|
+
const transformedPoints = points.map(point => vec.transform(point, transformToPx));
|
|
28227
|
+
return transformedPoints;
|
|
28228
|
+
};
|
|
28229
|
+
|
|
28230
|
+
/**
|
|
28231
|
+
* Given two points, find the tips that extends through the points to the edge of the range.
|
|
28232
|
+
* @param collinearPoint - The point that the line passes through. Needed to establish slope.
|
|
28233
|
+
* @param extendFrom - The point that the line extends from to the edge of the graph.
|
|
28234
|
+
*/
|
|
28235
|
+
const getRayIntersectionCoords = (collinearPoint, extendFrom, range) => {
|
|
28236
|
+
// edges of the graph
|
|
28237
|
+
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
28238
|
+
const [aX, aY] = collinearPoint;
|
|
28239
|
+
const [bX, bY] = extendFrom;
|
|
28240
|
+
const yDiff = bY - aY;
|
|
28241
|
+
const xDiff = bX - aX;
|
|
28242
|
+
const slope = yDiff / xDiff;
|
|
28243
|
+
const yAtXMin = slope * (xMin - aX) + aY;
|
|
28244
|
+
const yAtXMax = slope * (xMax - aX) + aY;
|
|
28245
|
+
const xAtYMin = (yMin - aY) / slope + aX;
|
|
28246
|
+
const xAtYMax = (yMax - aY) / slope + aX;
|
|
28247
|
+
|
|
28248
|
+
// clock analogy to describe quadrants
|
|
28249
|
+
switch (true) {
|
|
28250
|
+
// 12 o'clock to 2:59
|
|
28251
|
+
case yDiff > 0 && xDiff >= 0:
|
|
28252
|
+
return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
|
|
28253
|
+
// 3 o'clock to 5:59
|
|
28254
|
+
case yDiff <= 0 && xDiff > 0:
|
|
28255
|
+
// xAtYMin evaluates to -Infinity here, so we use absolute value
|
|
28256
|
+
return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
|
|
28257
|
+
// 9 o'clock to 11:59
|
|
28258
|
+
case yDiff >= 0 && xDiff < 0:
|
|
28259
|
+
return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
|
|
28260
|
+
// 6 o'clock to 8:59
|
|
28261
|
+
case yDiff < 0 && xDiff <= 0:
|
|
28262
|
+
return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
|
|
28263
|
+
default:
|
|
28264
|
+
return [xMax, yAtXMax];
|
|
28265
|
+
}
|
|
28266
|
+
};
|
|
28267
|
+
const getLines = points => points.map((point, i) => {
|
|
28268
|
+
const next = points[(i + 1) % points.length];
|
|
28269
|
+
return [point, next];
|
|
28270
|
+
});
|
|
28271
|
+
|
|
28219
28272
|
const defaultStroke = "var(--movable-line-stroke-color)";
|
|
28220
28273
|
const MovableLine = props => {
|
|
28221
28274
|
const {
|
|
@@ -28226,18 +28279,12 @@ const MovableLine = props => {
|
|
|
28226
28279
|
stroke = defaultStroke
|
|
28227
28280
|
} = props;
|
|
28228
28281
|
const midpoint = vec.midpoint(start, end);
|
|
28229
|
-
const
|
|
28230
|
-
viewTransform,
|
|
28231
|
-
userTransform
|
|
28232
|
-
} = useTransformContext();
|
|
28233
|
-
const transformToPx = vec.matrixMult(viewTransform, userTransform);
|
|
28234
|
-
const startPtPx = vec.transform(start, transformToPx);
|
|
28235
|
-
const endPtPx = vec.transform(end, transformToPx);
|
|
28282
|
+
const [startPtPx, endPtPx] = useTransform(start, end);
|
|
28236
28283
|
let startExtend = undefined;
|
|
28237
28284
|
let endExtend = undefined;
|
|
28238
28285
|
if (extend) {
|
|
28239
|
-
startExtend = extend.start ?
|
|
28240
|
-
endExtend = extend.end ?
|
|
28286
|
+
startExtend = extend.start ? getRayIntersectionCoords(end, start, extend.range) : undefined;
|
|
28287
|
+
endExtend = extend.end ? getRayIntersectionCoords(start, end, extend.range) : undefined;
|
|
28241
28288
|
}
|
|
28242
28289
|
const line = useRef(null);
|
|
28243
28290
|
const {
|
|
@@ -28299,50 +28346,12 @@ function SVGLine(props) {
|
|
|
28299
28346
|
});
|
|
28300
28347
|
}
|
|
28301
28348
|
|
|
28302
|
-
/**
|
|
28303
|
-
* Given two points, find the tips that extends through the points to the edge of the range.
|
|
28304
|
-
* @param collinearPoint - The point that the line passes through. Needed to establish slope.
|
|
28305
|
-
* @param extendFrom - The point that the line extends from to the edge of the graph.
|
|
28306
|
-
*/
|
|
28307
|
-
const getExtensionCoords = (collinearPoint, extendFrom, range) => {
|
|
28308
|
-
// edges of the graph
|
|
28309
|
-
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
28310
|
-
const [aX, aY] = collinearPoint;
|
|
28311
|
-
const [bX, bY] = extendFrom;
|
|
28312
|
-
const yDiff = bY - aY;
|
|
28313
|
-
const xDiff = bX - aX;
|
|
28314
|
-
const slope = yDiff / xDiff;
|
|
28315
|
-
const yAtXMin = slope * (xMin - aX) + aY;
|
|
28316
|
-
const yAtXMax = slope * (xMax - aX) + aY;
|
|
28317
|
-
const xAtYMin = (yMin - aY) / slope + aX;
|
|
28318
|
-
const xAtYMax = (yMax - aY) / slope + aX;
|
|
28319
|
-
|
|
28320
|
-
// clock analogy to describe quadrants
|
|
28321
|
-
switch (true) {
|
|
28322
|
-
// 12 o'clock to 2:59
|
|
28323
|
-
case yDiff > 0 && xDiff >= 0:
|
|
28324
|
-
return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
|
|
28325
|
-
// 3 o'clock to 5:59
|
|
28326
|
-
case yDiff <= 0 && xDiff > 0:
|
|
28327
|
-
// xAtYMin evaluates to -Infinity here, so we use absolute value
|
|
28328
|
-
return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
|
|
28329
|
-
// 9 o'clock to 11:59
|
|
28330
|
-
case yDiff >= 0 && xDiff < 0:
|
|
28331
|
-
return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
|
|
28332
|
-
// 6 o'clock to 8:59
|
|
28333
|
-
case yDiff < 0 && xDiff <= 0:
|
|
28334
|
-
return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
|
|
28335
|
-
default:
|
|
28336
|
-
return [xMax, yAtXMax];
|
|
28337
|
-
}
|
|
28338
|
-
};
|
|
28339
|
-
|
|
28340
28349
|
const StyledMovablePoint = props => {
|
|
28341
28350
|
const hitboxRef = useRef(null);
|
|
28342
28351
|
const {
|
|
28343
28352
|
point,
|
|
28344
28353
|
onMove,
|
|
28345
|
-
color =
|
|
28354
|
+
color: color$1 = color.blue
|
|
28346
28355
|
} = props;
|
|
28347
28356
|
const {
|
|
28348
28357
|
dragging
|
|
@@ -28352,12 +28361,7 @@ const StyledMovablePoint = props => {
|
|
|
28352
28361
|
onMove,
|
|
28353
28362
|
constrain: p => p
|
|
28354
28363
|
});
|
|
28355
|
-
const
|
|
28356
|
-
viewTransform,
|
|
28357
|
-
userTransform
|
|
28358
|
-
} = useTransformContext();
|
|
28359
|
-
const transformToPx = vec.matrixMult(viewTransform, userTransform);
|
|
28360
|
-
const [x, y] = vec.transform(point, transformToPx);
|
|
28364
|
+
const [[x, y]] = useTransform(point);
|
|
28361
28365
|
return /*#__PURE__*/React.createElement("g", {
|
|
28362
28366
|
ref: hitboxRef,
|
|
28363
28367
|
className: "movable-point",
|
|
@@ -28366,7 +28370,7 @@ const StyledMovablePoint = props => {
|
|
|
28366
28370
|
cursor: dragging ? "grabbing" : "grab",
|
|
28367
28371
|
touchAction: "none",
|
|
28368
28372
|
outline: "none",
|
|
28369
|
-
"--movable-point-color": color
|
|
28373
|
+
"--movable-point-color": color$1
|
|
28370
28374
|
}
|
|
28371
28375
|
}, /*#__PURE__*/React.createElement("circle", {
|
|
28372
28376
|
className: "movable-point-hitbox",
|
|
@@ -28386,7 +28390,7 @@ const StyledMovablePoint = props => {
|
|
|
28386
28390
|
cx: x,
|
|
28387
28391
|
cy: y,
|
|
28388
28392
|
style: {
|
|
28389
|
-
fill: color
|
|
28393
|
+
fill: color$1
|
|
28390
28394
|
}
|
|
28391
28395
|
}));
|
|
28392
28396
|
};
|
|
@@ -28524,6 +28528,167 @@ const RayGraph = props => {
|
|
|
28524
28528
|
}));
|
|
28525
28529
|
};
|
|
28526
28530
|
|
|
28531
|
+
/**
|
|
28532
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
28533
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
28534
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
28535
|
+
*/
|
|
28536
|
+
const MafsCssTransformWrapper = ({
|
|
28537
|
+
children
|
|
28538
|
+
}) => /*#__PURE__*/React.createElement("g", {
|
|
28539
|
+
style: {
|
|
28540
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
28541
|
+
}
|
|
28542
|
+
}, children);
|
|
28543
|
+
|
|
28544
|
+
const _excluded$2 = ["children"];
|
|
28545
|
+
const TextLabel = _ref => {
|
|
28546
|
+
let {
|
|
28547
|
+
children
|
|
28548
|
+
} = _ref,
|
|
28549
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
28550
|
+
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
28551
|
+
size: 16,
|
|
28552
|
+
svgTextProps: {
|
|
28553
|
+
filter: "url(#background)",
|
|
28554
|
+
fontWeight: "bold"
|
|
28555
|
+
}
|
|
28556
|
+
}, rest), children);
|
|
28557
|
+
};
|
|
28558
|
+
const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*#__PURE__*/React__default.createElement("filter", {
|
|
28559
|
+
id: "background",
|
|
28560
|
+
x: "-5%",
|
|
28561
|
+
width: "110%",
|
|
28562
|
+
y: "0%",
|
|
28563
|
+
height: "100%"
|
|
28564
|
+
}, /*#__PURE__*/React__default.createElement("feFlood", {
|
|
28565
|
+
floodColor: "#FFF",
|
|
28566
|
+
floodOpacity: "0.64"
|
|
28567
|
+
}), /*#__PURE__*/React__default.createElement("feComposite", {
|
|
28568
|
+
operator: "over",
|
|
28569
|
+
in: "SourceGraphic"
|
|
28570
|
+
})));
|
|
28571
|
+
|
|
28572
|
+
const Angle = ({
|
|
28573
|
+
centerPoint,
|
|
28574
|
+
endPoints,
|
|
28575
|
+
range,
|
|
28576
|
+
polygonLines,
|
|
28577
|
+
showAngles
|
|
28578
|
+
}) => {
|
|
28579
|
+
const [centerX, centerY] = centerPoint;
|
|
28580
|
+
const areClockwise = clockwise$1([centerPoint, ...endPoints]);
|
|
28581
|
+
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
28582
|
+
const radius = 0.3;
|
|
28583
|
+
const a = vec.dist(centerPoint, endPoints[0]);
|
|
28584
|
+
const b = vec.dist(centerPoint, endPoints[1]);
|
|
28585
|
+
const c = vec.dist(endPoints[0], endPoints[1]);
|
|
28586
|
+
|
|
28587
|
+
// Law of cosines
|
|
28588
|
+
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
28589
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
28590
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
28591
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
28592
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
28593
|
+
|
|
28594
|
+
// Fourth point that would create a rhomboid
|
|
28595
|
+
// Used to calculate the inside of the angle and to create square
|
|
28596
|
+
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
28597
|
+
if (!showAngles) {
|
|
28598
|
+
return isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28599
|
+
start: [x1, y1],
|
|
28600
|
+
vertex: [x2, y2],
|
|
28601
|
+
end: [x3, y3]
|
|
28602
|
+
}) : null;
|
|
28603
|
+
}
|
|
28604
|
+
|
|
28605
|
+
// Midpoint betwen ends of arc
|
|
28606
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
28607
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
28608
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
28609
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
28610
|
+
let angleInDegrees = angle * (180 / Math.PI);
|
|
28611
|
+
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
28612
|
+
if (isOutside) {
|
|
28613
|
+
angleInDegrees = 360 - angleInDegrees;
|
|
28614
|
+
}
|
|
28615
|
+
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(1));
|
|
28616
|
+
const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
|
|
28617
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
28618
|
+
id: "background",
|
|
28619
|
+
x: "-5%",
|
|
28620
|
+
width: "110%",
|
|
28621
|
+
y: "0%",
|
|
28622
|
+
height: "100%"
|
|
28623
|
+
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
28624
|
+
floodColor: "#FFF",
|
|
28625
|
+
floodOpacity: "0.5"
|
|
28626
|
+
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
28627
|
+
operator: "over",
|
|
28628
|
+
in: "SourceGraphic"
|
|
28629
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
28630
|
+
start: [x1, y1],
|
|
28631
|
+
vertex: [x2, y2],
|
|
28632
|
+
end: [x3, y3]
|
|
28633
|
+
}) : /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28634
|
+
d: arc,
|
|
28635
|
+
strokeWidth: 0.02,
|
|
28636
|
+
fill: "none"
|
|
28637
|
+
})), /*#__PURE__*/React.createElement(TextLabel, {
|
|
28638
|
+
x: x3,
|
|
28639
|
+
y: y3,
|
|
28640
|
+
attach: y3 - centerY > 0 ? "s" : "n",
|
|
28641
|
+
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
28642
|
+
}, angleLabel, "\xB0"));
|
|
28643
|
+
};
|
|
28644
|
+
|
|
28645
|
+
/**
|
|
28646
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
28647
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
28648
|
+
*/
|
|
28649
|
+
const RightAngleSquare = ({
|
|
28650
|
+
start: [x1, y1],
|
|
28651
|
+
vertex: [x2, y2],
|
|
28652
|
+
end: [x3, y3]
|
|
28653
|
+
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
28654
|
+
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
28655
|
+
strokeWidth: 0.02,
|
|
28656
|
+
fill: "none"
|
|
28657
|
+
}));
|
|
28658
|
+
const isRightAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
28659
|
+
|
|
28660
|
+
/**
|
|
28661
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
28662
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
28663
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
28664
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
28665
|
+
* will intersect with an even number of lines.
|
|
28666
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
28667
|
+
*/
|
|
28668
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
28669
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
28670
|
+
const rangeIntersectionPoint = getRayIntersectionCoords(midpoint, vertex, range);
|
|
28671
|
+
let lineIntersectionCount = 0;
|
|
28672
|
+
polygonLines.forEach(line => linesIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
28673
|
+
|
|
28674
|
+
// If the number of intersections is even, the angle is inside the polygon
|
|
28675
|
+
return !isEven(lineIntersectionCount);
|
|
28676
|
+
};
|
|
28677
|
+
|
|
28678
|
+
// https://stackoverflow.com/a/24392281/7347484
|
|
28679
|
+
// The "intersects" function in geometry doesn't seem to work for this use case
|
|
28680
|
+
const linesIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
|
|
28681
|
+
const determinant = (c - a) * (s - q) - (r - p) * (d - b);
|
|
28682
|
+
if (determinant === 0) {
|
|
28683
|
+
return false;
|
|
28684
|
+
} else {
|
|
28685
|
+
const lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / determinant;
|
|
28686
|
+
const gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / determinant;
|
|
28687
|
+
return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
|
|
28688
|
+
}
|
|
28689
|
+
};
|
|
28690
|
+
const isEven = n => n % 2 === 0;
|
|
28691
|
+
|
|
28527
28692
|
const PolygonGraph = props => {
|
|
28528
28693
|
var _points$length;
|
|
28529
28694
|
const [focused, setFocused] = React.useState(false);
|
|
@@ -28533,7 +28698,10 @@ const PolygonGraph = props => {
|
|
|
28533
28698
|
} = props;
|
|
28534
28699
|
const {
|
|
28535
28700
|
coords,
|
|
28536
|
-
type
|
|
28701
|
+
type,
|
|
28702
|
+
showAngles,
|
|
28703
|
+
showSides,
|
|
28704
|
+
range
|
|
28537
28705
|
} = props.graphState;
|
|
28538
28706
|
const points = coords != null ? coords : [[0, 0]];
|
|
28539
28707
|
const pointsSum = points.reduce((acc, point) => vec.add(acc, point), [0, 0]);
|
|
@@ -28551,12 +28719,36 @@ const PolygonGraph = props => {
|
|
|
28551
28719
|
constrain: p => p
|
|
28552
28720
|
});
|
|
28553
28721
|
const active = hovered || focused || dragging;
|
|
28722
|
+
const lines = getLines(points);
|
|
28554
28723
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polygon, {
|
|
28555
28724
|
points: [...points],
|
|
28556
28725
|
color: "var(--movable-line-stroke-color)",
|
|
28557
28726
|
svgPolygonProps: {
|
|
28558
28727
|
strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)"
|
|
28559
28728
|
}
|
|
28729
|
+
}), points.map((point, i) => {
|
|
28730
|
+
const pt1 = points.at(i - 1);
|
|
28731
|
+
const pt2 = points[(i + 1) % points.length];
|
|
28732
|
+
if (!pt1 || !pt2) {
|
|
28733
|
+
return null;
|
|
28734
|
+
}
|
|
28735
|
+
return /*#__PURE__*/React.createElement(Angle, {
|
|
28736
|
+
key: "angle-" + i,
|
|
28737
|
+
centerPoint: point,
|
|
28738
|
+
endPoints: [pt1, pt2],
|
|
28739
|
+
active: active,
|
|
28740
|
+
range: range,
|
|
28741
|
+
polygonLines: lines,
|
|
28742
|
+
showAngles: !!showAngles
|
|
28743
|
+
});
|
|
28744
|
+
}), showSides && lines.map(([start, end], i) => {
|
|
28745
|
+
const [x, y] = vec.midpoint(start, end);
|
|
28746
|
+
const length = parseFloat(vec.dist(start, end).toFixed(1));
|
|
28747
|
+
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
28748
|
+
key: "side-" + i,
|
|
28749
|
+
x: x,
|
|
28750
|
+
y: y
|
|
28751
|
+
}, !Number.isInteger(length) && "≈ ", length);
|
|
28560
28752
|
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
28561
28753
|
points: [...points],
|
|
28562
28754
|
color: "transparent",
|
|
@@ -28574,7 +28766,7 @@ const PolygonGraph = props => {
|
|
|
28574
28766
|
className: "movable-polygon"
|
|
28575
28767
|
}
|
|
28576
28768
|
}), points.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
28577
|
-
key: i,
|
|
28769
|
+
key: "point-" + i,
|
|
28578
28770
|
point: point,
|
|
28579
28771
|
onMove: destination => dispatch(moveControlPoint(i, destination)),
|
|
28580
28772
|
"data-testid": type + i
|
|
@@ -29256,7 +29448,10 @@ var components = /*#__PURE__*/Object.freeze({
|
|
|
29256
29448
|
* If a graphie URL is provided in `backgroundImage`, will return the rendered graphie background.
|
|
29257
29449
|
* Otherwise, returns `null`.
|
|
29258
29450
|
*/
|
|
29259
|
-
const
|
|
29451
|
+
const LegacyGrid = ({
|
|
29452
|
+
box,
|
|
29453
|
+
backgroundImage
|
|
29454
|
+
}) => {
|
|
29260
29455
|
const {
|
|
29261
29456
|
url,
|
|
29262
29457
|
width,
|
|
@@ -29533,17 +29728,17 @@ function initializeGraphState(params) {
|
|
|
29533
29728
|
})
|
|
29534
29729
|
};
|
|
29535
29730
|
case "polygon":
|
|
29536
|
-
return {
|
|
29537
|
-
type: "polygon",
|
|
29731
|
+
return _extends({
|
|
29538
29732
|
hasBeenInteractedWith: false,
|
|
29539
29733
|
range,
|
|
29540
|
-
snapStep
|
|
29734
|
+
snapStep
|
|
29735
|
+
}, graph, {
|
|
29541
29736
|
coords: getPolygonCoords({
|
|
29542
29737
|
graph,
|
|
29543
29738
|
range,
|
|
29544
29739
|
step
|
|
29545
29740
|
})
|
|
29546
|
-
};
|
|
29741
|
+
});
|
|
29547
29742
|
case "angle":
|
|
29548
29743
|
case "point":
|
|
29549
29744
|
case "circle":
|
|
@@ -29558,30 +29753,39 @@ function initializeGraphState(params) {
|
|
|
29558
29753
|
// TS v4 doesn't narrow return types, while v5 does.
|
|
29559
29754
|
// Instead of updating to v5, using generic type to relate input and output types.
|
|
29560
29755
|
function getGradableGraph(state, initialGraph) {
|
|
29561
|
-
var _state$coords;
|
|
29562
29756
|
if (!state.hasBeenInteractedWith) {
|
|
29563
|
-
return
|
|
29757
|
+
return {
|
|
29758
|
+
type: initialGraph.type
|
|
29759
|
+
};
|
|
29564
29760
|
}
|
|
29565
|
-
|
|
29566
|
-
|
|
29567
|
-
|
|
29568
|
-
|
|
29569
|
-
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
|
|
29579
|
-
|
|
29580
|
-
|
|
29581
|
-
|
|
29582
|
-
|
|
29583
|
-
|
|
29761
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
29762
|
+
return _extends({}, initialGraph, {
|
|
29763
|
+
coords: state.coords
|
|
29764
|
+
});
|
|
29765
|
+
}
|
|
29766
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
29767
|
+
return _extends({}, initialGraph, {
|
|
29768
|
+
coords: state.coords
|
|
29769
|
+
});
|
|
29770
|
+
}
|
|
29771
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
29772
|
+
var _state$coords;
|
|
29773
|
+
return _extends({}, initialGraph, {
|
|
29774
|
+
coords: (_state$coords = state.coords) == null ? void 0 : _state$coords[0]
|
|
29775
|
+
});
|
|
29776
|
+
}
|
|
29777
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
29778
|
+
var _state$coords2;
|
|
29779
|
+
return _extends({}, initialGraph, {
|
|
29780
|
+
coords: (_state$coords2 = state.coords) == null ? void 0 : _state$coords2[0]
|
|
29781
|
+
});
|
|
29782
|
+
}
|
|
29783
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
29784
|
+
return _extends({}, initialGraph, {
|
|
29785
|
+
coords: state.coords
|
|
29786
|
+
});
|
|
29584
29787
|
}
|
|
29788
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29585
29789
|
}
|
|
29586
29790
|
const getDefaultSegments = ({
|
|
29587
29791
|
graph,
|
|
@@ -29604,9 +29808,10 @@ const getDefaultSegments = ({
|
|
|
29604
29808
|
return [5];
|
|
29605
29809
|
}
|
|
29606
29810
|
};
|
|
29811
|
+
const defaultRange = [[-10, 10], [-10, 10]];
|
|
29607
29812
|
return ys(graph.numSegments).map(y => {
|
|
29608
29813
|
let endpoints = [[-5, y], [5, y]];
|
|
29609
|
-
endpoints = normalizeCoords(endpoints,
|
|
29814
|
+
endpoints = normalizeCoords(endpoints, defaultRange);
|
|
29610
29815
|
endpoints = normalizePoints(range, step, endpoints);
|
|
29611
29816
|
return endpoints;
|
|
29612
29817
|
});
|
|
@@ -29688,7 +29893,6 @@ const renderGraph = props => {
|
|
|
29688
29893
|
};
|
|
29689
29894
|
const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
29690
29895
|
const [width, height] = props.box;
|
|
29691
|
-
const legacyGrid = getLegacyGrid([width, height], props.backgroundImage);
|
|
29692
29896
|
const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
|
|
29693
29897
|
React.useImperativeHandle(ref, () => ({
|
|
29694
29898
|
getUserInput: () => getGradableGraph(state, props.graph)
|
|
@@ -29699,13 +29903,17 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
29699
29903
|
height,
|
|
29700
29904
|
position: "relative"
|
|
29701
29905
|
}
|
|
29702
|
-
},
|
|
29906
|
+
}, /*#__PURE__*/React.createElement(LegacyGrid, {
|
|
29907
|
+
box: props.box,
|
|
29908
|
+
backgroundImage: props.backgroundImage
|
|
29909
|
+
}), /*#__PURE__*/React.createElement(View, {
|
|
29703
29910
|
style: {
|
|
29704
29911
|
position: "absolute",
|
|
29705
29912
|
bottom: 0,
|
|
29706
29913
|
left: 0
|
|
29707
29914
|
}
|
|
29708
29915
|
}, /*#__PURE__*/React.createElement(Mafs, {
|
|
29916
|
+
preserveAspectRatio: false,
|
|
29709
29917
|
viewBox: {
|
|
29710
29918
|
x: props.range[0],
|
|
29711
29919
|
y: props.range[1],
|
|
@@ -29715,7 +29923,7 @@ const MafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
29715
29923
|
zoom: false,
|
|
29716
29924
|
width: width,
|
|
29717
29925
|
height: height
|
|
29718
|
-
},
|
|
29926
|
+
}, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, props), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
29719
29927
|
lockedFigures: props.lockedFigures
|
|
29720
29928
|
}), renderGraph({
|
|
29721
29929
|
state,
|
|
@@ -31369,18 +31577,18 @@ class InteractiveGraph extends React.Component {
|
|
|
31369
31577
|
const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
|
|
31370
31578
|
return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
|
|
31371
31579
|
}
|
|
31372
|
-
static validate(
|
|
31373
|
-
// TODO(kevinb): rename state to userInput
|
|
31374
|
-
// state === userInput
|
|
31375
|
-
state, rubric, component) {
|
|
31580
|
+
static validate(userInput, rubric, component) {
|
|
31376
31581
|
// When nothing has moved, there will neither be coords nor the
|
|
31377
31582
|
// circle's center/radius fields. When those fields are absent, skip
|
|
31378
31583
|
// all these checks; just go mark the answer as empty.
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31584
|
+
const hasValue = Boolean(
|
|
31585
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
31586
|
+
userInput.coords ||
|
|
31587
|
+
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
31588
|
+
userInput.center && userInput.radius);
|
|
31589
|
+
if (userInput.type === rubric.correct.type && hasValue) {
|
|
31590
|
+
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
31591
|
+
const guess = userInput.coords;
|
|
31384
31592
|
const correct = rubric.correct.coords;
|
|
31385
31593
|
|
|
31386
31594
|
// If both of the guess points are on the correct line, it's
|
|
@@ -31397,8 +31605,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31397
31605
|
message: null
|
|
31398
31606
|
};
|
|
31399
31607
|
}
|
|
31400
|
-
} else if (
|
|
31401
|
-
const guess =
|
|
31608
|
+
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
31609
|
+
const guess = userInput.coords;
|
|
31402
31610
|
const correct = rubric.correct.coords;
|
|
31403
31611
|
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
31404
31612
|
return {
|
|
@@ -31408,9 +31616,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31408
31616
|
message: null
|
|
31409
31617
|
};
|
|
31410
31618
|
}
|
|
31411
|
-
} else if (
|
|
31619
|
+
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
31412
31620
|
// If the parabola coefficients match, it's correct.
|
|
31413
|
-
const guessCoeffs = this.getQuadraticCoefficients(
|
|
31621
|
+
const guessCoeffs = this.getQuadraticCoefficients(userInput.coords);
|
|
31414
31622
|
const correctCoeffs = this.getQuadraticCoefficients(
|
|
31415
31623
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31416
31624
|
rubric.correct.coords);
|
|
@@ -31422,8 +31630,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31422
31630
|
message: null
|
|
31423
31631
|
};
|
|
31424
31632
|
}
|
|
31425
|
-
} else if (
|
|
31426
|
-
const guessCoeffs = this.getSinusoidCoefficients(
|
|
31633
|
+
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
31634
|
+
const guessCoeffs = this.getSinusoidCoefficients(userInput.coords);
|
|
31427
31635
|
const correctCoeffs = this.getSinusoidCoefficients(
|
|
31428
31636
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
31429
31637
|
rubric.correct.coords);
|
|
@@ -31438,8 +31646,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31438
31646
|
message: null
|
|
31439
31647
|
};
|
|
31440
31648
|
}
|
|
31441
|
-
} else if (
|
|
31442
|
-
if (deepEq$1(
|
|
31649
|
+
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
31650
|
+
if (deepEq$1(userInput.center, rubric.correct.center) && eq(userInput.radius, rubric.correct.radius)) {
|
|
31443
31651
|
return {
|
|
31444
31652
|
type: "points",
|
|
31445
31653
|
earned: 1,
|
|
@@ -31447,10 +31655,10 @@ class InteractiveGraph extends React.Component {
|
|
|
31447
31655
|
message: null
|
|
31448
31656
|
};
|
|
31449
31657
|
}
|
|
31450
|
-
} else if (
|
|
31451
|
-
var
|
|
31658
|
+
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
31659
|
+
var _userInput$coords;
|
|
31452
31660
|
let correct = InteractiveGraph.getPointCoords(rubric.correct, component);
|
|
31453
|
-
const guess = (
|
|
31661
|
+
const guess = (_userInput$coords = userInput.coords) == null ? void 0 : _userInput$coords.slice();
|
|
31454
31662
|
correct = correct.slice();
|
|
31455
31663
|
// Everything's already rounded so we shouldn't need to do an
|
|
31456
31664
|
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
@@ -31467,9 +31675,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31467
31675
|
message: null
|
|
31468
31676
|
};
|
|
31469
31677
|
}
|
|
31470
|
-
} else if (
|
|
31471
|
-
var
|
|
31472
|
-
const guess = (
|
|
31678
|
+
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
31679
|
+
var _userInput$coords2, _rubric$correct$coord;
|
|
31680
|
+
const guess = (_userInput$coords2 = userInput.coords) == null ? void 0 : _userInput$coords2.slice();
|
|
31473
31681
|
// @ts-expect-error - TS2322 - Type 'Coord[] | undefined' is not assignable to type 'Coord[]'.
|
|
31474
31682
|
const correct = (_rubric$correct$coord = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord.slice();
|
|
31475
31683
|
let match;
|
|
@@ -31493,9 +31701,9 @@ class InteractiveGraph extends React.Component {
|
|
|
31493
31701
|
message: null
|
|
31494
31702
|
};
|
|
31495
31703
|
}
|
|
31496
|
-
} else if (
|
|
31704
|
+
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
31497
31705
|
var _rubric$correct$coord2;
|
|
31498
|
-
let guess =
|
|
31706
|
+
let guess = userInput.coords.slice();
|
|
31499
31707
|
let correct = (_rubric$correct$coord2 = rubric.correct.coords) == null ? void 0 : _rubric$correct$coord2.slice();
|
|
31500
31708
|
guess = _.invoke(guess, "sort").sort();
|
|
31501
31709
|
// @ts-expect-error - TS2345 - Argument of type '(readonly Coord[])[] | undefined' is not assignable to parameter of type 'Collection<any>'.
|
|
@@ -31508,8 +31716,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31508
31716
|
message: null
|
|
31509
31717
|
};
|
|
31510
31718
|
}
|
|
31511
|
-
} else if (
|
|
31512
|
-
const guess =
|
|
31719
|
+
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
31720
|
+
const guess = userInput.coords;
|
|
31513
31721
|
const correct = rubric.correct.coords;
|
|
31514
31722
|
if (
|
|
31515
31723
|
// @ts-expect-error - TS2532 - Object is possibly 'undefined'.
|
|
@@ -31523,8 +31731,8 @@ class InteractiveGraph extends React.Component {
|
|
|
31523
31731
|
message: null
|
|
31524
31732
|
};
|
|
31525
31733
|
}
|
|
31526
|
-
} else if (
|
|
31527
|
-
const guess =
|
|
31734
|
+
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
31735
|
+
const guess = userInput.coords;
|
|
31528
31736
|
const correct = rubric.correct.coords;
|
|
31529
31737
|
let match;
|
|
31530
31738
|
if (rubric.correct.match === "congruent") {
|
|
@@ -31563,7 +31771,7 @@ class InteractiveGraph extends React.Component {
|
|
|
31563
31771
|
|
|
31564
31772
|
// The input wasn't correct, so check if it's a blank input or if it's
|
|
31565
31773
|
// actually just wrong
|
|
31566
|
-
if (!hasValue || _.isEqual(
|
|
31774
|
+
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
31567
31775
|
// We're where we started.
|
|
31568
31776
|
return {
|
|
31569
31777
|
type: "invalid",
|
|
@@ -32650,7 +32858,7 @@ const styles$g = StyleSheet.create({
|
|
|
32650
32858
|
});
|
|
32651
32859
|
|
|
32652
32860
|
const BringToFront = {
|
|
32653
|
-
boxShadow: `0 8px 8px ${
|
|
32861
|
+
boxShadow: `0 8px 8px ${color.offBlack64}`,
|
|
32654
32862
|
zIndex: 1000
|
|
32655
32863
|
};
|
|
32656
32864
|
const AnswerPill = props => {
|
|
@@ -32703,7 +32911,7 @@ const styles$f = StyleSheet.create({
|
|
|
32703
32911
|
backgroundColor: "#00880b"
|
|
32704
32912
|
},
|
|
32705
32913
|
incorrect: {
|
|
32706
|
-
backgroundColor:
|
|
32914
|
+
backgroundColor: color.offBlack64
|
|
32707
32915
|
},
|
|
32708
32916
|
pill: {
|
|
32709
32917
|
// Reset the Pill's default height in order to account
|
|
@@ -32746,7 +32954,7 @@ class Marker extends React.Component {
|
|
|
32746
32954
|
// default dot
|
|
32747
32955
|
let args = {
|
|
32748
32956
|
size: MARKER_SIZE,
|
|
32749
|
-
color:
|
|
32957
|
+
color: color.white,
|
|
32750
32958
|
icon: iconNull
|
|
32751
32959
|
};
|
|
32752
32960
|
if (showCorrectness) {
|
|
@@ -32809,7 +33017,7 @@ Marker.defaultProps = {
|
|
|
32809
33017
|
const styles$e = StyleSheet.create({
|
|
32810
33018
|
marker: {
|
|
32811
33019
|
position: "absolute",
|
|
32812
|
-
backgroundColor:
|
|
33020
|
+
backgroundColor: color.white,
|
|
32813
33021
|
borderRadius: MARKER_SIZE,
|
|
32814
33022
|
// Center marker position based on its maximum size.
|
|
32815
33023
|
width: MARKER_SIZE,
|
|
@@ -32817,7 +33025,7 @@ const styles$e = StyleSheet.create({
|
|
|
32817
33025
|
marginLeft: MARKER_SIZE / -2,
|
|
32818
33026
|
marginTop: MARKER_SIZE / -2,
|
|
32819
33027
|
// Add a shadow to the marker to make it stand out from the image.
|
|
32820
|
-
boxShadow: `0 8px 8px ${
|
|
33028
|
+
boxShadow: `0 8px 8px ${color.offBlack8}`
|
|
32821
33029
|
},
|
|
32822
33030
|
// The base and unfilled marker style.
|
|
32823
33031
|
markerIcon: {
|
|
@@ -32827,7 +33035,7 @@ const styles$e = StyleSheet.create({
|
|
|
32827
33035
|
boxSizing: "border-box",
|
|
32828
33036
|
width: MARKER_SIZE,
|
|
32829
33037
|
height: MARKER_SIZE,
|
|
32830
|
-
border: `2px solid ${
|
|
33038
|
+
border: `2px solid ${color.offBlack64}`,
|
|
32831
33039
|
borderRadius: MARKER_SIZE
|
|
32832
33040
|
},
|
|
32833
33041
|
// The animation that presents the marker to the learner
|
|
@@ -32835,11 +33043,11 @@ const styles$e = StyleSheet.create({
|
|
|
32835
33043
|
animationName: {
|
|
32836
33044
|
"0%": {
|
|
32837
33045
|
transform: "scale(1)",
|
|
32838
|
-
backgroundColor:
|
|
33046
|
+
backgroundColor: color.blue
|
|
32839
33047
|
},
|
|
32840
33048
|
"100%": {
|
|
32841
33049
|
transform: "scale(1.3)",
|
|
32842
|
-
backgroundColor:
|
|
33050
|
+
backgroundColor: color.blue
|
|
32843
33051
|
}
|
|
32844
33052
|
},
|
|
32845
33053
|
animationDirection: "alternate",
|
|
@@ -32856,35 +33064,35 @@ const styles$e = StyleSheet.create({
|
|
|
32856
33064
|
animationIterationCount: "2"
|
|
32857
33065
|
},
|
|
32858
33066
|
markerActive: {
|
|
32859
|
-
outline: `2px solid ${
|
|
33067
|
+
outline: `2px solid ${color.blue}`,
|
|
32860
33068
|
outlineOffset: 2
|
|
32861
33069
|
},
|
|
32862
33070
|
// The learner is making an initial selection
|
|
32863
33071
|
markerSelected: {
|
|
32864
|
-
boxShadow: `0 8px 8px ${
|
|
32865
|
-
border: `solid 4px ${
|
|
32866
|
-
backgroundColor:
|
|
33072
|
+
boxShadow: `0 8px 8px ${color.offBlack8}`,
|
|
33073
|
+
border: `solid 4px ${color.white}`,
|
|
33074
|
+
backgroundColor: color.blue,
|
|
32867
33075
|
borderRadius: MARKER_SIZE,
|
|
32868
33076
|
transform: "rotate(180deg)"
|
|
32869
33077
|
},
|
|
32870
33078
|
// The learner has made a selection
|
|
32871
33079
|
markerFilled: {
|
|
32872
33080
|
backgroundColor: "#ECF3FE",
|
|
32873
|
-
border: `4px solid ${
|
|
33081
|
+
border: `4px solid ${color.blue}`
|
|
32874
33082
|
},
|
|
32875
33083
|
markerGraded: {
|
|
32876
33084
|
width: MARKER_SIZE,
|
|
32877
33085
|
height: MARKER_SIZE,
|
|
32878
33086
|
justifyContent: "center",
|
|
32879
33087
|
alignItems: "center",
|
|
32880
|
-
border: `2px solid ${
|
|
33088
|
+
border: `2px solid ${color.white}`
|
|
32881
33089
|
},
|
|
32882
33090
|
markerCorrect: {
|
|
32883
33091
|
background: "#00880b" // WB green darkened by 18%
|
|
32884
33092
|
},
|
|
32885
33093
|
|
|
32886
33094
|
markerIncorrect: {
|
|
32887
|
-
background:
|
|
33095
|
+
background: color.offBlack64
|
|
32888
33096
|
}
|
|
32889
33097
|
});
|
|
32890
33098
|
|
|
@@ -33313,6 +33521,7 @@ class LabelImage extends React.Component {
|
|
|
33313
33521
|
const answerChoicesActive = index === activeMarkerIndex;
|
|
33314
33522
|
const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
|
|
33315
33523
|
return /*#__PURE__*/React.createElement(View, {
|
|
33524
|
+
key: index,
|
|
33316
33525
|
style: {
|
|
33317
33526
|
position: "absolute",
|
|
33318
33527
|
left: `${marker.x}%`,
|
|
@@ -37911,7 +38120,7 @@ const styles$8 = StyleSheet.create({
|
|
|
37911
38120
|
// and the "Add highlight" action fails.
|
|
37912
38121
|
userSelect: "none",
|
|
37913
38122
|
fontFamily: `"Lato", sans-serif`,
|
|
37914
|
-
padding: `10px ${
|
|
38123
|
+
padding: `10px ${spacing.medium_16}px`,
|
|
37915
38124
|
cursor: "pointer"
|
|
37916
38125
|
}
|
|
37917
38126
|
});
|
|
@@ -41594,7 +41803,7 @@ const VideoTranscriptLink = props => {
|
|
|
41594
41803
|
const video = (_result$data = result.data) == null ? void 0 : _result$data.video;
|
|
41595
41804
|
return /*#__PURE__*/React.createElement(View, {
|
|
41596
41805
|
style: styles$4.transcriptLink
|
|
41597
|
-
}, /*#__PURE__*/React.createElement(Text$
|
|
41806
|
+
}, /*#__PURE__*/React.createElement(Text$2, null, video == null ? void 0 : video.title), /*#__PURE__*/React.createElement(Strut, {
|
|
41598
41807
|
size: 10
|
|
41599
41808
|
}), /*#__PURE__*/React.createElement(Link, {
|
|
41600
41809
|
href: "/transcript/" + ((video == null ? void 0 : video.contentId) || "videoNotFound"),
|
|
@@ -41763,7 +41972,7 @@ const init = function init(options) {
|
|
|
41763
41972
|
|
|
41764
41973
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41765
41974
|
const libName = "@khanacademy/perseus";
|
|
41766
|
-
const libVersion = "
|
|
41975
|
+
const libVersion = "21.0.0";
|
|
41767
41976
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41768
41977
|
|
|
41769
41978
|
const apiVersion = {
|
|
@@ -44015,6 +44224,140 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44015
44224
|
replaceDeprecatedWidgets();
|
|
44016
44225
|
};
|
|
44017
44226
|
|
|
44227
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44228
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44229
|
+
|
|
44230
|
+
/**
|
|
44231
|
+
* Regex for widget placeholders in a string.
|
|
44232
|
+
*
|
|
44233
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
44234
|
+
* Second capture group is the widget type (ex. "radio)
|
|
44235
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44236
|
+
*/
|
|
44237
|
+
function getWidgetRegex() {
|
|
44238
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
44239
|
+
}
|
|
44240
|
+
|
|
44241
|
+
/**
|
|
44242
|
+
* Add a widget placeholder using the widget ID.
|
|
44243
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
44244
|
+
*
|
|
44245
|
+
* @param {string} id
|
|
44246
|
+
* @returns {string}
|
|
44247
|
+
*/
|
|
44248
|
+
function addWidget(id) {
|
|
44249
|
+
return `[[☃ ${id}]]`;
|
|
44250
|
+
}
|
|
44251
|
+
|
|
44252
|
+
/**
|
|
44253
|
+
* Get a widget type by a widget's ID
|
|
44254
|
+
* (please don't derive type from ID)
|
|
44255
|
+
*
|
|
44256
|
+
* @param {string} widgetId the ID of the widget
|
|
44257
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44258
|
+
* @returns {string} the widget type (ie "radio")
|
|
44259
|
+
*/
|
|
44260
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44261
|
+
var _widget$type;
|
|
44262
|
+
const widget = widgetMap[WidgetId];
|
|
44263
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
44264
|
+
}
|
|
44265
|
+
|
|
44266
|
+
/**
|
|
44267
|
+
* Does the content have a specific type of widget?
|
|
44268
|
+
*
|
|
44269
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44270
|
+
* @param {string} content the string to search through
|
|
44271
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44272
|
+
* @returns {boolean} if the content includes the widget type
|
|
44273
|
+
*/
|
|
44274
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44275
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44276
|
+
}
|
|
44277
|
+
|
|
44278
|
+
/**
|
|
44279
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44280
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44281
|
+
*
|
|
44282
|
+
* Content should contain Perseus widget placeholders,
|
|
44283
|
+
* which look like: '[[☃ radio 1]]'.
|
|
44284
|
+
*
|
|
44285
|
+
* @param {string} content
|
|
44286
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
44287
|
+
*/
|
|
44288
|
+
function getWidgetIdsFromContent(content) {
|
|
44289
|
+
const widgets = [];
|
|
44290
|
+
const localWidgetRegex = getWidgetRegex();
|
|
44291
|
+
let match = localWidgetRegex.exec(content);
|
|
44292
|
+
while (match !== null) {
|
|
44293
|
+
widgets.push(match[1]);
|
|
44294
|
+
match = localWidgetRegex.exec(content);
|
|
44295
|
+
}
|
|
44296
|
+
return widgets;
|
|
44297
|
+
}
|
|
44298
|
+
|
|
44299
|
+
/**
|
|
44300
|
+
* Get a list of widget IDs from content,
|
|
44301
|
+
* but only for specific widget types
|
|
44302
|
+
*
|
|
44303
|
+
* @param {string} type the type of widget (ie "radio")
|
|
44304
|
+
* @param {string} content the string to parse
|
|
44305
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44306
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
44307
|
+
*/
|
|
44308
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
44309
|
+
const rv = [];
|
|
44310
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
44311
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
44312
|
+
const widget = widgetMap[widgetId];
|
|
44313
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
44314
|
+
rv.push(widgetId);
|
|
44315
|
+
}
|
|
44316
|
+
});
|
|
44317
|
+
return rv;
|
|
44318
|
+
}
|
|
44319
|
+
|
|
44320
|
+
/**
|
|
44321
|
+
* Pull the widget map out of ItemData
|
|
44322
|
+
*
|
|
44323
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44324
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44325
|
+
*/
|
|
44326
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44327
|
+
return itemData.question.widgets;
|
|
44328
|
+
}
|
|
44329
|
+
|
|
44330
|
+
/**
|
|
44331
|
+
* Get the widget information from a WidgetMap
|
|
44332
|
+
*
|
|
44333
|
+
* @param {string} widgetId the ID of the widget
|
|
44334
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44335
|
+
*/
|
|
44336
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44337
|
+
var _widgetMap$widgetId;
|
|
44338
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
44339
|
+
}
|
|
44340
|
+
|
|
44341
|
+
/**
|
|
44342
|
+
* Get select widgets from a widget map.
|
|
44343
|
+
* Useful for multi-items that needs to split
|
|
44344
|
+
* one PerseusItem into several
|
|
44345
|
+
*
|
|
44346
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44347
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44348
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44349
|
+
*/
|
|
44350
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44351
|
+
const widgets = {};
|
|
44352
|
+
widgetIds.forEach(widgetId => {
|
|
44353
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44354
|
+
if (widget) {
|
|
44355
|
+
widgets[widgetId] = widget;
|
|
44356
|
+
}
|
|
44357
|
+
});
|
|
44358
|
+
return widgets;
|
|
44359
|
+
}
|
|
44360
|
+
|
|
44018
44361
|
/**
|
|
44019
44362
|
* This function extracts the answers from the widgets.
|
|
44020
44363
|
*
|
|
@@ -44425,18 +44768,15 @@ const INDIVIDUAL_ANSWER_WIDGETS = ["interactive-graph", "categorizer", "grapher"
|
|
|
44425
44768
|
|
|
44426
44769
|
/* Widgets that are supported for automatic scoring */
|
|
44427
44770
|
const SUPPORTED_WIDGETS = ["radio", "numeric-input", "input-number", "expression", ...INDIVIDUAL_ANSWER_WIDGETS];
|
|
44428
|
-
const getWidgetTypeFromWidgetKey = widgetKey => {
|
|
44429
|
-
return widgetKey.split(" ")[0];
|
|
44430
|
-
};
|
|
44431
44771
|
|
|
44432
44772
|
/* Verify if the perseus item has supported widgets for automatic scoring */
|
|
44433
|
-
const isWrongAnswerSupported =
|
|
44434
|
-
return
|
|
44773
|
+
const isWrongAnswerSupported = (widgetIds, widgetMap) => {
|
|
44774
|
+
return widgetIds.length !== 0 && widgetIds.every(widgetId => SUPPORTED_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap)));
|
|
44435
44775
|
};
|
|
44436
44776
|
|
|
44437
|
-
/* Verify if the widget
|
|
44438
|
-
const shouldHaveIndividualAnswer =
|
|
44439
|
-
return INDIVIDUAL_ANSWER_WIDGETS.includes(
|
|
44777
|
+
/* Verify if the widget ID has an individual answer for the coach report view */
|
|
44778
|
+
const shouldHaveIndividualAnswer = (widgetId, widgetMap) => {
|
|
44779
|
+
return INDIVIDUAL_ANSWER_WIDGETS.includes(getWidgetTypeByWidgetId(widgetId, widgetMap));
|
|
44440
44780
|
};
|
|
44441
44781
|
|
|
44442
44782
|
/* Returns the answer userInput submission for currently supported widgets */
|
|
@@ -44455,29 +44795,30 @@ const getAnswerFromUserInput = (widgetType, userInput) => {
|
|
|
44455
44795
|
return userInput;
|
|
44456
44796
|
};
|
|
44457
44797
|
|
|
44458
|
-
/* Returns the correct answer for a given widget
|
|
44798
|
+
/* Returns the correct answer for a given widget ID and Perseus Item */
|
|
44459
44799
|
// TODO (LEMS-1835): We should fix the resonse type from getWidget to be specific.
|
|
44460
44800
|
// TODO (LEMS-1836): We should also consider adding the getOneCorrectAnswerFromRubric method to all widgets.
|
|
44461
|
-
const
|
|
44462
|
-
const rubric = itemData.question.widgets[
|
|
44463
|
-
const
|
|
44801
|
+
const getCorrectAnswerForWidgetId = (widgetId, itemData) => {
|
|
44802
|
+
const rubric = itemData.question.widgets[widgetId].options;
|
|
44803
|
+
const widgetMap = getWidgetsMapFromItemData(itemData);
|
|
44804
|
+
const widgetType = getWidgetTypeByWidgetId(widgetId, widgetMap);
|
|
44464
44805
|
const widget = getWidget(widgetType);
|
|
44465
44806
|
|
|
44466
44807
|
// @ts-expect-error - TS2339 - Property 'getOneCorrectAnswerFromRubric' does not exist on type 'ComponentType<any>'.
|
|
44467
44808
|
return widget == null || widget.getOneCorrectAnswerFromRubric == null ? void 0 : widget.getOneCorrectAnswerFromRubric(rubric);
|
|
44468
44809
|
};
|
|
44469
44810
|
|
|
44470
|
-
/* Verify if the widget
|
|
44471
|
-
const
|
|
44472
|
-
return perseusItem.question.content.indexOf(
|
|
44811
|
+
/* Verify if the widget ID exists in the content string of the Perseus Item */
|
|
44812
|
+
const isWidgetIdInContent = (perseusItem, widgetId) => {
|
|
44813
|
+
return perseusItem.question.content.indexOf(widgetId) !== -1;
|
|
44473
44814
|
};
|
|
44474
44815
|
|
|
44475
|
-
/* Return an array of all the widget
|
|
44476
|
-
const
|
|
44816
|
+
/* Return an array of all the widget IDs that exist in the content string of a Perseus Item */
|
|
44817
|
+
const getValidWidgetIds = perseusItem => {
|
|
44477
44818
|
const {
|
|
44478
44819
|
widgets
|
|
44479
44820
|
} = perseusItem.question;
|
|
44480
|
-
return keys(widgets).filter(
|
|
44821
|
+
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44481
44822
|
};
|
|
44482
44823
|
|
|
44483
44824
|
/**
|
|
@@ -44525,12 +44866,113 @@ const genericPerseusItemData = {
|
|
|
44525
44866
|
* uses a basic Perseus object and updates it with custom values as needed.
|
|
44526
44867
|
*
|
|
44527
44868
|
* @param {Partial<PerseusItem>} customFields
|
|
44528
|
-
* @returns PerseusItem
|
|
44869
|
+
* @returns {PerseusItem}
|
|
44529
44870
|
*/
|
|
44530
44871
|
function generateTestPerseusItem(customFields = {}) {
|
|
44531
44872
|
return _extends({}, genericPerseusItemData, customFields);
|
|
44532
44873
|
}
|
|
44533
44874
|
|
|
44875
|
+
/**
|
|
44876
|
+
* Creates an object with the bare amount
|
|
44877
|
+
* of data to be a properly typed RadioWidget
|
|
44878
|
+
*
|
|
44879
|
+
* @returns {RadioWidget} skeleton RadioWidget for testing
|
|
44880
|
+
*/
|
|
44881
|
+
function generateTestRadioWidget() {
|
|
44882
|
+
return {
|
|
44883
|
+
type: "radio",
|
|
44884
|
+
options: {
|
|
44885
|
+
choices: []
|
|
44886
|
+
}
|
|
44887
|
+
};
|
|
44888
|
+
}
|
|
44889
|
+
|
|
44890
|
+
/**
|
|
44891
|
+
* Creates an object with the bare amount
|
|
44892
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
44893
|
+
*
|
|
44894
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
44895
|
+
*/
|
|
44896
|
+
function generateTestInteractiveGraphWidget() {
|
|
44897
|
+
return {
|
|
44898
|
+
type: "interactive-graph",
|
|
44899
|
+
options: {
|
|
44900
|
+
step: [1, 1],
|
|
44901
|
+
gridStep: [1, 1],
|
|
44902
|
+
snapStep: [1, 1],
|
|
44903
|
+
markings: "graph",
|
|
44904
|
+
labels: ["x", "y"],
|
|
44905
|
+
showProtractor: false,
|
|
44906
|
+
showRuler: false,
|
|
44907
|
+
rulerLabel: "mm",
|
|
44908
|
+
rulerTicks: 2,
|
|
44909
|
+
range: [[-10, 10], [-10, 10]],
|
|
44910
|
+
graph: {
|
|
44911
|
+
type: "angle"
|
|
44912
|
+
},
|
|
44913
|
+
correct: {
|
|
44914
|
+
type: "angle"
|
|
44915
|
+
}
|
|
44916
|
+
}
|
|
44917
|
+
};
|
|
44918
|
+
}
|
|
44919
|
+
|
|
44920
|
+
/**
|
|
44921
|
+
* Creates an object with the bare amount
|
|
44922
|
+
* of data to be a properly typed InteractiveGraphWidget
|
|
44923
|
+
*
|
|
44924
|
+
* @returns {InteractiveGraphWidget} skeleton InteractiveGraphWidget for testing
|
|
44925
|
+
*/
|
|
44926
|
+
function generateTestCategorizerWidget() {
|
|
44927
|
+
return {
|
|
44928
|
+
type: "categorizer",
|
|
44929
|
+
options: {
|
|
44930
|
+
items: [],
|
|
44931
|
+
categories: [],
|
|
44932
|
+
randomizeItems: false,
|
|
44933
|
+
static: false,
|
|
44934
|
+
values: []
|
|
44935
|
+
}
|
|
44936
|
+
};
|
|
44937
|
+
}
|
|
44938
|
+
|
|
44939
|
+
/**
|
|
44940
|
+
* Creates an object with the bare amount
|
|
44941
|
+
* of data to be a properly typed ExpressionWidget
|
|
44942
|
+
*
|
|
44943
|
+
* @returns {ExpressionWidget} skeleton ExpressionWidget for testing
|
|
44944
|
+
*/
|
|
44945
|
+
function generateTestExpressionWidget() {
|
|
44946
|
+
return {
|
|
44947
|
+
type: "expression",
|
|
44948
|
+
options: {
|
|
44949
|
+
answerForms: [],
|
|
44950
|
+
buttonSets: ["basic"],
|
|
44951
|
+
functions: [],
|
|
44952
|
+
times: false
|
|
44953
|
+
}
|
|
44954
|
+
};
|
|
44955
|
+
}
|
|
44956
|
+
|
|
44957
|
+
/**
|
|
44958
|
+
* Creates an object with the bare amount
|
|
44959
|
+
* of data to be a properly typed NumericInputWidget
|
|
44960
|
+
*
|
|
44961
|
+
* @returns {NumericInputWidget} skeleton NumericInputWidget for testing
|
|
44962
|
+
*/
|
|
44963
|
+
function generateTestNumericInputWidget() {
|
|
44964
|
+
return {
|
|
44965
|
+
type: "numeric-input",
|
|
44966
|
+
options: {
|
|
44967
|
+
answers: [],
|
|
44968
|
+
labelText: "",
|
|
44969
|
+
size: "normal",
|
|
44970
|
+
coefficient: false,
|
|
44971
|
+
static: false
|
|
44972
|
+
}
|
|
44973
|
+
};
|
|
44974
|
+
}
|
|
44975
|
+
|
|
44534
44976
|
/**
|
|
44535
44977
|
* Some helpers for CEDAR logging. CEDAR convention dictates
|
|
44536
44978
|
* that enum values should be upper case. Ex: for the
|
|
@@ -44585,163 +45027,6 @@ function convertWidgetNameToEnum(name) {
|
|
|
44585
45027
|
}
|
|
44586
45028
|
return widgetEnum;
|
|
44587
45029
|
}
|
|
44588
|
-
function getWidgetTypeFromWidgetId(widgetId) {
|
|
44589
|
-
const [widgetType, widgetNumber] = widgetId.split(" ");
|
|
44590
|
-
if (!widgetNumber) {
|
|
44591
|
-
throw new Error(`Incorrect format for widget ID: ${widgetId}`);
|
|
44592
|
-
}
|
|
44593
|
-
if (!widgetNameToEnum[widgetType]) {
|
|
44594
|
-
throw new Error(`Unknown widget type (${widgetType}) in widget ID: ${widgetId}`);
|
|
44595
|
-
}
|
|
44596
|
-
return widgetType;
|
|
44597
|
-
}
|
|
44598
|
-
|
|
44599
|
-
let WidgetType = /*#__PURE__*/function (WidgetType) {
|
|
44600
|
-
WidgetType["Categorizer"] = "categorizer";
|
|
44601
|
-
WidgetType["CSProgram"] = "cs-program";
|
|
44602
|
-
WidgetType["Definition"] = "definition";
|
|
44603
|
-
WidgetType["Dropdown"] = "dropdown";
|
|
44604
|
-
WidgetType["Explanation"] = "explanation";
|
|
44605
|
-
WidgetType["Expression"] = "expression";
|
|
44606
|
-
WidgetType["GradedGroup"] = "graded-group";
|
|
44607
|
-
WidgetType["GradedGroupAnswerBar"] = "graded-group-answer-bar";
|
|
44608
|
-
WidgetType["GradedGroupSet"] = "graded-group-set";
|
|
44609
|
-
WidgetType["Grapher"] = "grapher";
|
|
44610
|
-
WidgetType["Group"] = "group";
|
|
44611
|
-
WidgetType["Iframe"] = "iframe";
|
|
44612
|
-
WidgetType["Image"] = "image";
|
|
44613
|
-
WidgetType["InputNumber"] = "input-number";
|
|
44614
|
-
WidgetType["Interaction"] = "interaction";
|
|
44615
|
-
WidgetType["InteractiveGraph"] = "interactive-graph";
|
|
44616
|
-
WidgetType["InteractiveNumberLine"] = "interactive-number-line";
|
|
44617
|
-
WidgetType["LabelImage"] = "label-image";
|
|
44618
|
-
WidgetType["Matcher"] = "matcher";
|
|
44619
|
-
WidgetType["Matrix"] = "matrix";
|
|
44620
|
-
WidgetType["Measurer"] = "measurer";
|
|
44621
|
-
WidgetType["Molecule"] = "molecule";
|
|
44622
|
-
WidgetType["NumberLine"] = "number-line";
|
|
44623
|
-
WidgetType["NumericInput"] = "numeric-input";
|
|
44624
|
-
WidgetType["Orderer"] = "orderer";
|
|
44625
|
-
WidgetType["Passage"] = "passage";
|
|
44626
|
-
WidgetType["PassageRef"] = "passage-ref";
|
|
44627
|
-
WidgetType["PassageRefTarget"] = "passage-ref-target";
|
|
44628
|
-
WidgetType["Plotter"] = "plotter";
|
|
44629
|
-
WidgetType["Protractor"] = "protractor";
|
|
44630
|
-
WidgetType["PythonProgram"] = "python-program";
|
|
44631
|
-
WidgetType["Radio"] = "radio";
|
|
44632
|
-
WidgetType["Sorter"] = "sorter";
|
|
44633
|
-
WidgetType["Table"] = "table";
|
|
44634
|
-
WidgetType["Unit"] = "unit";
|
|
44635
|
-
WidgetType["Video"] = "video";
|
|
44636
|
-
WidgetType["VideoTranscriptLink"] = "video-transcript-link";
|
|
44637
|
-
return WidgetType;
|
|
44638
|
-
}({});
|
|
44639
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
44640
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
44641
|
-
|
|
44642
|
-
/**
|
|
44643
|
-
* Regex for widget placeholders in a string.
|
|
44644
|
-
*
|
|
44645
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
44646
|
-
* Second capture group is the widget type (ex. "radio)
|
|
44647
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
44648
|
-
*/
|
|
44649
|
-
function getWidgetRegex() {
|
|
44650
|
-
return /\[\[☃ (([a-z-]+) \d+)\]\]/g;
|
|
44651
|
-
}
|
|
44652
|
-
|
|
44653
|
-
/**
|
|
44654
|
-
* Add a widget placeholder using the provided widget type and instance number.
|
|
44655
|
-
* ex. addWidget(WidgetType.Radio, 1) => "[[☃ radio 1]]"
|
|
44656
|
-
*
|
|
44657
|
-
* @param {WidgetType} widgetType
|
|
44658
|
-
* @param {number} instance
|
|
44659
|
-
* @returns {string}
|
|
44660
|
-
*/
|
|
44661
|
-
function addWidget(widgetType, instance) {
|
|
44662
|
-
return `[[☃ ${widgetType} ${String(instance)}]]`;
|
|
44663
|
-
}
|
|
44664
|
-
|
|
44665
|
-
/**
|
|
44666
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
44667
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
44668
|
-
*
|
|
44669
|
-
* Content should contain Perseus widget placeholders,
|
|
44670
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44671
|
-
*
|
|
44672
|
-
* @param {string} content
|
|
44673
|
-
* @returns {Array<WidgetId>} widgets
|
|
44674
|
-
*/
|
|
44675
|
-
function getAllWidgetIds(content) {
|
|
44676
|
-
const widgets = [];
|
|
44677
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44678
|
-
let match = localWidgetRegex.exec(content);
|
|
44679
|
-
while (match !== null) {
|
|
44680
|
-
widgets.push(match[1]);
|
|
44681
|
-
match = localWidgetRegex.exec(content);
|
|
44682
|
-
}
|
|
44683
|
-
return widgets;
|
|
44684
|
-
}
|
|
44685
|
-
|
|
44686
|
-
/**
|
|
44687
|
-
* Check if a specific widget is an actual widget type using its name.
|
|
44688
|
-
* The widget name should include a widget type, without an instance number,
|
|
44689
|
-
* as a string. ex. 'radio'
|
|
44690
|
-
*
|
|
44691
|
-
* @param {string} widgetName
|
|
44692
|
-
* @returns {boolean}
|
|
44693
|
-
*/
|
|
44694
|
-
function isWidgetType(widgetName) {
|
|
44695
|
-
return Object.values(WidgetType).includes(widgetName);
|
|
44696
|
-
}
|
|
44697
|
-
|
|
44698
|
-
/**
|
|
44699
|
-
* Extract all widget types from a Perseus JSON content string.
|
|
44700
|
-
* This does not include the instance number and prevents duplicates.
|
|
44701
|
-
* example output: ['radio', 'categorizer']
|
|
44702
|
-
*
|
|
44703
|
-
* @param {string} content
|
|
44704
|
-
* @returns {Array<WidgetId>}
|
|
44705
|
-
*/
|
|
44706
|
-
function getAllWidgetTypes(content) {
|
|
44707
|
-
const widgetTypes = [];
|
|
44708
|
-
const localWidgetRegex = getWidgetRegex();
|
|
44709
|
-
let match = localWidgetRegex.exec(content);
|
|
44710
|
-
while (match !== null) {
|
|
44711
|
-
const matchType = match[2];
|
|
44712
|
-
if (!widgetTypes.includes(matchType) && isWidgetType(matchType)) {
|
|
44713
|
-
widgetTypes.push(matchType);
|
|
44714
|
-
}
|
|
44715
|
-
match = localWidgetRegex.exec(content);
|
|
44716
|
-
}
|
|
44717
|
-
return widgetTypes;
|
|
44718
|
-
}
|
|
44719
|
-
|
|
44720
|
-
/**
|
|
44721
|
-
* Check if a specific widget is a question widget type using its ID.
|
|
44722
|
-
* The widget ID includes the widget type and the instance number.
|
|
44723
|
-
*
|
|
44724
|
-
* @param {WidgetId} widgetId
|
|
44725
|
-
* @returns {boolean}
|
|
44726
|
-
*/
|
|
44727
|
-
function isQuestionWidgetType(widgetId) {
|
|
44728
|
-
return QUESTION_WIDGETS.includes(widgetId.split(" ")[0]);
|
|
44729
|
-
}
|
|
44730
|
-
|
|
44731
|
-
/**
|
|
44732
|
-
* Extract the widget IDs of all widgets considered question widgets. Widget IDs
|
|
44733
|
-
* include widget type and instance number.
|
|
44734
|
-
* example output: ['radio 1', 'categorizer 1']
|
|
44735
|
-
*
|
|
44736
|
-
* Content should contain Perseus widget placeholders,
|
|
44737
|
-
* which look like: '[[☃ radio 1]]'.
|
|
44738
|
-
*
|
|
44739
|
-
* @param {string} content
|
|
44740
|
-
* @returns Array<WidgetId>
|
|
44741
|
-
*/
|
|
44742
|
-
function getQuestionWidgetIds(content) {
|
|
44743
|
-
return getAllWidgetIds(content).filter(isQuestionWidgetType);
|
|
44744
|
-
}
|
|
44745
45030
|
|
|
44746
45031
|
const EditorJsonify = {
|
|
44747
45032
|
serialize: function () {
|
|
@@ -44751,5 +45036,5 @@ const EditorJsonify = {
|
|
|
44751
45036
|
}
|
|
44752
45037
|
};
|
|
44753
45038
|
|
|
44754
|
-
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated,
|
|
45039
|
+
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
|
|
44755
45040
|
//# sourceMappingURL=index.js.map
|