@khanacademy/perseus 42.0.3 → 43.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/components/svg-image.d.ts +1 -1
- package/dist/es/index.css +0 -3
- package/dist/es/index.js +513 -674
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +4 -0
- package/dist/es/strings.js.map +1 -1
- package/dist/index.css +0 -3
- package/dist/index.d.ts +3 -6
- package/dist/index.js +489 -653
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +2 -3
- package/dist/renderer.d.ts +9 -6
- package/dist/server-item-renderer.d.ts +2 -33
- package/dist/strings.d.ts +2 -0
- package/dist/strings.js +4 -0
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/validation.types.d.ts +31 -1
- package/dist/widgets/expression/expression.d.ts +3 -3
- package/dist/widgets/expression/validate-expression.d.ts +14 -0
- package/dist/widgets/group/group.d.ts +1 -1
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +13 -1
- package/dist/widgets/interactive-graphs/types.d.ts +1 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +10 -0
- package/dist/widgets/matrix/validate-matrix.d.ts +15 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +0 -7
- package/package.json +5 -5
- package/dist/util/deprecated-widgets/input-number.d.ts +0 -13
- package/dist/util/deprecated-widgets/modernize-widgets-utils.d.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var kmath = require('@khanacademy/kmath');
|
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
26
26
|
var SimpleMarkdown = require('@khanacademy/simple-markdown');
|
|
27
|
-
var
|
|
27
|
+
var pureMarkdown = require('@khanacademy/pure-markdown');
|
|
28
28
|
var wonderBlocksForm = require('@khanacademy/wonder-blocks-form');
|
|
29
29
|
var Button = require('@khanacademy/wonder-blocks-button');
|
|
30
30
|
var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
|
|
@@ -77,7 +77,6 @@ var Clickable__default = /*#__PURE__*/_interopDefaultLegacy(Clickable);
|
|
|
77
77
|
var $__default = /*#__PURE__*/_interopDefaultLegacy($);
|
|
78
78
|
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
79
79
|
var SimpleMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(SimpleMarkdown);
|
|
80
|
-
var SimpleMarkdown__namespace = /*#__PURE__*/_interopNamespace(SimpleMarkdown$1);
|
|
81
80
|
var Button__default = /*#__PURE__*/_interopDefaultLegacy(Button);
|
|
82
81
|
var Banner__default = /*#__PURE__*/_interopDefaultLegacy(Banner);
|
|
83
82
|
var caretDown__default = /*#__PURE__*/_interopDefaultLegacy(caretDown);
|
|
@@ -369,6 +368,8 @@ const mockStrings = {
|
|
|
369
368
|
addPoint: "Add Point",
|
|
370
369
|
removePoint: "Remove Point",
|
|
371
370
|
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
371
|
+
closePolygon: "Close shape",
|
|
372
|
+
openPolygon: "Re-open shape",
|
|
372
373
|
srPointAtCoordinates: _ref27 => {
|
|
373
374
|
let {
|
|
374
375
|
num,
|
|
@@ -530,7 +531,10 @@ class InnerMathInput extends React__namespace.Component {
|
|
|
530
531
|
locale
|
|
531
532
|
} = this.context;
|
|
532
533
|
// Initialize MathQuill.MathField instance
|
|
533
|
-
this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
534
|
+
this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
535
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
536
|
+
// @ts-expect-error: Type 'EditableMathQuill' is not assignable to type 'MathFieldInterface'.
|
|
537
|
+
baseConfig => ({
|
|
534
538
|
...baseConfig,
|
|
535
539
|
handlers: {
|
|
536
540
|
edit: debounce(mathField => {
|
|
@@ -2259,6 +2263,24 @@ const KhanAnswerTypes = {
|
|
|
2259
2263
|
}
|
|
2260
2264
|
};
|
|
2261
2265
|
|
|
2266
|
+
/**
|
|
2267
|
+
* Checks user input from the expression widget to see if it is scorable.
|
|
2268
|
+
*
|
|
2269
|
+
* Note: Most of the expression widget's validation requires the Rubric because
|
|
2270
|
+
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
2271
|
+
*
|
|
2272
|
+
* @see `scoreExpression()` for more details.
|
|
2273
|
+
*/
|
|
2274
|
+
function validateExpression(userInput) {
|
|
2275
|
+
if (userInput === "") {
|
|
2276
|
+
return {
|
|
2277
|
+
type: "invalid",
|
|
2278
|
+
message: null
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2262
2284
|
/* Content creators input a list of answers which are matched from top to
|
|
2263
2285
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2264
2286
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2278,6 +2300,10 @@ const KhanAnswerTypes = {
|
|
|
2278
2300
|
* - Otherwise, pass through the resulting points and message.
|
|
2279
2301
|
*/
|
|
2280
2302
|
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2303
|
+
const validationError = validateExpression(userInput);
|
|
2304
|
+
if (validationError) {
|
|
2305
|
+
return validationError;
|
|
2306
|
+
}
|
|
2281
2307
|
const options = ___default["default"].clone(rubric);
|
|
2282
2308
|
___default["default"].extend(options, {
|
|
2283
2309
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2535,11 +2561,17 @@ class Expression extends React__namespace.Component {
|
|
|
2535
2561
|
}
|
|
2536
2562
|
return true;
|
|
2537
2563
|
};
|
|
2564
|
+
|
|
2565
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2566
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2538
2567
|
focusInputPath(inputPath) {
|
|
2539
2568
|
// eslint-disable-next-line react/no-string-refs
|
|
2540
2569
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
2541
2570
|
this.refs.input.focus();
|
|
2542
2571
|
}
|
|
2572
|
+
|
|
2573
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2574
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2543
2575
|
blurInputPath(inputPath) {
|
|
2544
2576
|
// eslint-disable-next-line react/no-string-refs
|
|
2545
2577
|
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
|
|
@@ -2713,7 +2745,7 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
2713
2745
|
times: widgetOptions.times
|
|
2714
2746
|
};
|
|
2715
2747
|
};
|
|
2716
|
-
const propUpgrades$
|
|
2748
|
+
const propUpgrades$2 = {
|
|
2717
2749
|
/* c8 ignore next */
|
|
2718
2750
|
"1": v0props => ({
|
|
2719
2751
|
times: v0props.times,
|
|
@@ -2773,10 +2805,14 @@ var Expression$1 = {
|
|
|
2773
2805
|
major: 1,
|
|
2774
2806
|
minor: 0
|
|
2775
2807
|
},
|
|
2776
|
-
propUpgrades: propUpgrades$
|
|
2808
|
+
propUpgrades: propUpgrades$2,
|
|
2777
2809
|
// For use by the editor
|
|
2778
2810
|
isLintable: true,
|
|
2811
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2812
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2779
2813
|
scorer: scoreExpression,
|
|
2814
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2815
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2780
2816
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2781
2817
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2782
2818
|
if (correctAnswers.length === 0) {
|
|
@@ -2859,6 +2895,8 @@ const nestedMap$2 = function (children, func, context) {
|
|
|
2859
2895
|
return nestedMap$2(child, func);
|
|
2860
2896
|
});
|
|
2861
2897
|
}
|
|
2898
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2899
|
+
// @ts-expect-error: T | ReadonlyArray<T> is not assignable to T
|
|
2862
2900
|
return func.call(context, children);
|
|
2863
2901
|
};
|
|
2864
2902
|
|
|
@@ -14637,7 +14675,9 @@ const constraints$2 = {
|
|
|
14637
14675
|
});
|
|
14638
14676
|
|
|
14639
14677
|
// bound the delta by the calculated bounds
|
|
14640
|
-
const boundedDelta = ___default["default"].reduce(deltaBounds,
|
|
14678
|
+
const boundedDelta = ___default["default"].reduce(deltaBounds,
|
|
14679
|
+
// @ts-expect-error: Type 'number[]' is not assignable to type 'Coord'.
|
|
14680
|
+
function (delta, bound) {
|
|
14641
14681
|
const lower = bound[0];
|
|
14642
14682
|
const upper = bound[1];
|
|
14643
14683
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
@@ -15172,6 +15212,7 @@ const constraints$1 = {
|
|
|
15172
15212
|
};
|
|
15173
15213
|
},
|
|
15174
15214
|
snap: function (snap) {
|
|
15215
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15175
15216
|
return function (coord) {
|
|
15176
15217
|
if (snap === null) {
|
|
15177
15218
|
// TODO(benchristel), NOTE(kevinb): this should probably return
|
|
@@ -15194,6 +15235,8 @@ const constraints$1 = {
|
|
|
15194
15235
|
return optionalPaddingPx;
|
|
15195
15236
|
}
|
|
15196
15237
|
})();
|
|
15238
|
+
|
|
15239
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15197
15240
|
return function (coord, prev, options) {
|
|
15198
15241
|
const graphie = this.graphie;
|
|
15199
15242
|
range = range || graphie.range;
|
|
@@ -16097,15 +16140,15 @@ const constraints = {
|
|
|
16097
16140
|
});
|
|
16098
16141
|
|
|
16099
16142
|
// bound the delta by the calculated bounds
|
|
16100
|
-
const boundedDelta = ___default["default"].reduce(deltaBounds,
|
|
16143
|
+
const boundedDelta = ___default["default"].reduce(deltaBounds,
|
|
16144
|
+
// @ts-expect-error - TS2345 - Type 'number[]' is not assignable to type 'Coord'.
|
|
16145
|
+
function (delta, bound) {
|
|
16101
16146
|
const lower = bound[0];
|
|
16102
16147
|
const upper = bound[1];
|
|
16103
16148
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
16104
16149
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
16105
16150
|
return [deltaX, deltaY];
|
|
16106
16151
|
}, delta);
|
|
16107
|
-
|
|
16108
|
-
// @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'Coord'.
|
|
16109
16152
|
return kmath.vector.add(prevCoord, boundedDelta);
|
|
16110
16153
|
};
|
|
16111
16154
|
}
|
|
@@ -17022,6 +17065,7 @@ class ImageLoader extends React__namespace.Component {
|
|
|
17022
17065
|
|
|
17023
17066
|
this.img = new Image();
|
|
17024
17067
|
this.img.onload = this.handleLoad;
|
|
17068
|
+
// @ts-expect-error: Type 'string | Event' is not assignable to type 'Event'.
|
|
17025
17069
|
this.img.onerror = this.handleError;
|
|
17026
17070
|
this.img.src = this.props.src;
|
|
17027
17071
|
};
|
|
@@ -18080,6 +18124,7 @@ class InteractionTracker {
|
|
|
18080
18124
|
this.widgetType = widgetType;
|
|
18081
18125
|
this.widgetID = widgetID;
|
|
18082
18126
|
this.setting = setting;
|
|
18127
|
+
// @ts-expect-error: Type 'T | undefined' is not assignable to type 'T'.
|
|
18083
18128
|
this.track = this._track;
|
|
18084
18129
|
}
|
|
18085
18130
|
}
|
|
@@ -18514,10 +18559,10 @@ const styles$C = aphrodite.StyleSheet.create({
|
|
|
18514
18559
|
|
|
18515
18560
|
/* eslint-disable no-useless-escape, no-prototype-builtins */
|
|
18516
18561
|
const rules$1 = {
|
|
18517
|
-
...
|
|
18562
|
+
...pureMarkdown.pureMarkdownRules,
|
|
18518
18563
|
// NOTE: basically ignored by JIPT. wraps everything at the outer layer
|
|
18519
18564
|
columns: {
|
|
18520
|
-
...
|
|
18565
|
+
...pureMarkdown.pureMarkdownRules.columns,
|
|
18521
18566
|
react: (node, output, state) => {
|
|
18522
18567
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
18523
18568
|
className: "perseus-two-columns",
|
|
@@ -18545,7 +18590,7 @@ const rules$1 = {
|
|
|
18545
18590
|
// for exercises the renderer just renders the crowdin id to the
|
|
18546
18591
|
// renderer div.
|
|
18547
18592
|
crowdinId: {
|
|
18548
|
-
...
|
|
18593
|
+
...pureMarkdown.pureMarkdownRules.crowdinId,
|
|
18549
18594
|
react: (node, output, state) => node.id
|
|
18550
18595
|
},
|
|
18551
18596
|
// Ensure that the table is focusable in sequential keyboard navigation
|
|
@@ -18554,7 +18599,7 @@ const rules$1 = {
|
|
|
18554
18599
|
// left/right gesture. Note, setting `tabindex` doesn't appear required for
|
|
18555
18600
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18556
18601
|
table: {
|
|
18557
|
-
...
|
|
18602
|
+
...pureMarkdown.pureMarkdownRules.table,
|
|
18558
18603
|
react: function (node, output, state) {
|
|
18559
18604
|
const table = SimpleMarkdown__default["default"].defaultRules.table.react(node, output, state);
|
|
18560
18605
|
if (!state.isMobile) {
|
|
@@ -18572,7 +18617,7 @@ const rules$1 = {
|
|
|
18572
18617
|
// our table regex into tableCapture[1..], and we pass tableCapture to
|
|
18573
18618
|
// our nptable regex
|
|
18574
18619
|
titledTable: {
|
|
18575
|
-
...
|
|
18620
|
+
...pureMarkdown.pureMarkdownRules.titledTable,
|
|
18576
18621
|
react: (node, output, state) => {
|
|
18577
18622
|
let contents;
|
|
18578
18623
|
if (!node.table) {
|
|
@@ -18632,7 +18677,7 @@ const rules$1 = {
|
|
|
18632
18677
|
}
|
|
18633
18678
|
},
|
|
18634
18679
|
widget: {
|
|
18635
|
-
...
|
|
18680
|
+
...pureMarkdown.pureMarkdownRules.widget,
|
|
18636
18681
|
react: (node, output, state) => {
|
|
18637
18682
|
// The actual output is handled in the renderer, where
|
|
18638
18683
|
// we know the current widget props/state. This is
|
|
@@ -18643,7 +18688,7 @@ const rules$1 = {
|
|
|
18643
18688
|
}
|
|
18644
18689
|
},
|
|
18645
18690
|
blockMath: {
|
|
18646
|
-
...
|
|
18691
|
+
...pureMarkdown.pureMarkdownRules.blockMath,
|
|
18647
18692
|
react: (node, output, state) => {
|
|
18648
18693
|
const {
|
|
18649
18694
|
TeX
|
|
@@ -18657,7 +18702,7 @@ const rules$1 = {
|
|
|
18657
18702
|
}
|
|
18658
18703
|
},
|
|
18659
18704
|
math: {
|
|
18660
|
-
...
|
|
18705
|
+
...pureMarkdown.pureMarkdownRules.math,
|
|
18661
18706
|
react: (node, output, state) => {
|
|
18662
18707
|
const {
|
|
18663
18708
|
TeX
|
|
@@ -18671,7 +18716,7 @@ const rules$1 = {
|
|
|
18671
18716
|
}
|
|
18672
18717
|
},
|
|
18673
18718
|
unescapedDollar: {
|
|
18674
|
-
...
|
|
18719
|
+
...pureMarkdown.pureMarkdownRules.unescapedDollar,
|
|
18675
18720
|
react: (node, output, state) => {
|
|
18676
18721
|
// Unescaped dollar signs render correctly, but result in
|
|
18677
18722
|
// untranslatable text after the i18n python linter flags it
|
|
@@ -18681,7 +18726,7 @@ const rules$1 = {
|
|
|
18681
18726
|
// Extend the SimpleMarkdown link parser to make the link open in a new
|
|
18682
18727
|
// window and handle reverse tabnapping phishing attacks
|
|
18683
18728
|
link: {
|
|
18684
|
-
...
|
|
18729
|
+
...pureMarkdown.pureMarkdownRules.link,
|
|
18685
18730
|
react: function (node, output, state) {
|
|
18686
18731
|
const link = SimpleMarkdown__default["default"].defaultRules.link.react(node, output, state);
|
|
18687
18732
|
const href = link.props.href;
|
|
@@ -18708,7 +18753,7 @@ const rules$1 = {
|
|
|
18708
18753
|
}
|
|
18709
18754
|
},
|
|
18710
18755
|
codeBlock: {
|
|
18711
|
-
...
|
|
18756
|
+
...pureMarkdown.pureMarkdownRules.codeBlock,
|
|
18712
18757
|
react: (node, output, state) => {
|
|
18713
18758
|
// ideally this should be a different rule, with only an
|
|
18714
18759
|
// output function, but right now that breaks the parser.
|
|
@@ -18726,7 +18771,7 @@ const rules$1 = {
|
|
|
18726
18771
|
// transform the tree to add lint nodes. This rule is here
|
|
18727
18772
|
// just for the react() function
|
|
18728
18773
|
lint: {
|
|
18729
|
-
...
|
|
18774
|
+
...pureMarkdown.pureMarkdownRules.lint,
|
|
18730
18775
|
react: (node, output, state) => {
|
|
18731
18776
|
return /*#__PURE__*/React__namespace.createElement(Lint, {
|
|
18732
18777
|
message: node.message,
|
|
@@ -18875,342 +18920,6 @@ class QuestionParagraph extends React__namespace.Component {
|
|
|
18875
18920
|
}
|
|
18876
18921
|
}
|
|
18877
18922
|
|
|
18878
|
-
// Methods to convert input-number widgets to numeric-input widgets
|
|
18879
|
-
|
|
18880
|
-
const inputNumberToNumericInput = json => {
|
|
18881
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
18882
|
-
// so that we can ensure we update the content and widgets accordingly
|
|
18883
|
-
const renameMap = getInputNumberRenameMap(json);
|
|
18884
|
-
|
|
18885
|
-
// Then we can use this to update the JSON
|
|
18886
|
-
return convertInputNumberJson(json, renameMap);
|
|
18887
|
-
};
|
|
18888
|
-
|
|
18889
|
-
// We need to be able to run this code recursively in order to convert nested input-number widgets
|
|
18890
|
-
const convertInputNumberJson = (json, renameMap) => {
|
|
18891
|
-
const updatedContent = convertDeprecatedWidgetsInContent(json, renameMap);
|
|
18892
|
-
const updatedWidgets = convertInputNumberWidgetOptions(json, renameMap);
|
|
18893
|
-
const modernizedJson = {
|
|
18894
|
-
...json,
|
|
18895
|
-
content: updatedContent,
|
|
18896
|
-
widgets: updatedWidgets
|
|
18897
|
-
};
|
|
18898
|
-
return modernizedJson;
|
|
18899
|
-
};
|
|
18900
|
-
|
|
18901
|
-
// Convert the input-number json in the widgets of a PerseusRenderer object
|
|
18902
|
-
const convertInputNumberWidgetOptions = (json, renameMap) => {
|
|
18903
|
-
const widgets = {
|
|
18904
|
-
...json.widgets
|
|
18905
|
-
};
|
|
18906
|
-
// The question.widgets is a dictionary map of widgets, so we need to loop through in order to convert input-number to numeric-input
|
|
18907
|
-
// which can exist as both the key or as a value on widget.type.
|
|
18908
|
-
for (const key of Object.keys(widgets)) {
|
|
18909
|
-
// Loop through the keys of the widgets dictionary
|
|
18910
|
-
if (widgets[key].options.widgets) {
|
|
18911
|
-
widgets[key].options = {
|
|
18912
|
-
...inputNumberToNumericInput(widgets[key].options)
|
|
18913
|
-
};
|
|
18914
|
-
}
|
|
18915
|
-
// Check if the widget is an input-number
|
|
18916
|
-
if (widgets[key].type === "input-number") {
|
|
18917
|
-
// If the answerType is not number or percent, we need to provide
|
|
18918
|
-
// the answer form for the numeric-input widget
|
|
18919
|
-
const provideAnswerForm = widgets[key].options.answerType !== "number" && widgets[key].options.answerType !== "percent";
|
|
18920
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
18921
|
-
const mathFormat = widgets[key].options.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
18922
|
-
: widgets[key].options.answerType; // Otherwise, we can use the answerType directly
|
|
18923
|
-
|
|
18924
|
-
// We need to update the answers prop to match the numeric-input widget format
|
|
18925
|
-
const answers = [{
|
|
18926
|
-
value: widgets[key].options.value,
|
|
18927
|
-
simplify: widgets[key].options.simplify,
|
|
18928
|
-
// Input Number doesn't have a strict prop, so we default to false
|
|
18929
|
-
strict: false,
|
|
18930
|
-
// We only want to set maxError if the inexact prop is true
|
|
18931
|
-
maxError: widgets[key].options.inexact ? widgets[key].options.maxError : 0,
|
|
18932
|
-
status: "correct",
|
|
18933
|
-
// Input-number only allows correct answers
|
|
18934
|
-
message: ""
|
|
18935
|
-
}];
|
|
18936
|
-
|
|
18937
|
-
// Add the required answerForms if provided/applicable
|
|
18938
|
-
if (provideAnswerForm) {
|
|
18939
|
-
answers[0].answerForms = [mathFormat];
|
|
18940
|
-
}
|
|
18941
|
-
|
|
18942
|
-
// Update the options prop to match the numeric-input widget format
|
|
18943
|
-
const upgradedWidgetOptions = {
|
|
18944
|
-
answers,
|
|
18945
|
-
size: widgets[key].options.size,
|
|
18946
|
-
coefficient: false,
|
|
18947
|
-
// input-number doesn't have a coefficient prop
|
|
18948
|
-
labelText: "",
|
|
18949
|
-
// input-number doesn't have a labelText prop
|
|
18950
|
-
static: false,
|
|
18951
|
-
// static is always false for numeric-input
|
|
18952
|
-
rightAlign: widgets[key].options.rightAlign || false
|
|
18953
|
-
};
|
|
18954
|
-
const upgradedWidget = {
|
|
18955
|
-
options: upgradedWidgetOptions,
|
|
18956
|
-
type: "numeric-input"
|
|
18957
|
-
};
|
|
18958
|
-
const newWidgetName = renameMap[key];
|
|
18959
|
-
// Create the new key entry
|
|
18960
|
-
widgets[newWidgetName] = upgradedWidget;
|
|
18961
|
-
|
|
18962
|
-
// We need to delete the old widget key
|
|
18963
|
-
delete widgets[key];
|
|
18964
|
-
}
|
|
18965
|
-
}
|
|
18966
|
-
return widgets;
|
|
18967
|
-
};
|
|
18968
|
-
|
|
18969
|
-
// Convert the deprecated widget refs in the content string
|
|
18970
|
-
// of a PerseusRenderer object to their renamed equivalents
|
|
18971
|
-
const convertDeprecatedWidgetsInContent = (json, renameMap) => {
|
|
18972
|
-
return Object.keys(renameMap).reduce((newContent, oldKey) => {
|
|
18973
|
-
const newKey = renameMap[oldKey];
|
|
18974
|
-
return newKey ? newContent.replace(oldKey, newKey) : newContent;
|
|
18975
|
-
}, json.content);
|
|
18976
|
-
};
|
|
18977
|
-
|
|
18978
|
-
// Create a map of the old input-number keys to the new numeric-input keys
|
|
18979
|
-
const getInputNumberRenameMap = json => {
|
|
18980
|
-
const numericRegex = /(?<=\[\[\u2603 )(numeric-input \d+)(?=\]\])/g;
|
|
18981
|
-
const inputNumberRegex = /(?<=\[\[\u2603 )(input-number \d+)(?=\]\])/g;
|
|
18982
|
-
|
|
18983
|
-
// Get all the content strings within the json, which might be nested within widgets
|
|
18984
|
-
const allContentStrings = json.content;
|
|
18985
|
-
|
|
18986
|
-
// Loop through the content strings to get all the input-number widgets
|
|
18987
|
-
const renameMap = {};
|
|
18988
|
-
const inputNumberMatches = [...(allContentStrings.match(inputNumberRegex) || [])];
|
|
18989
|
-
|
|
18990
|
-
// We want to count any pre-existing numeric-input widgets
|
|
18991
|
-
// so that we can start the new ids at the next number
|
|
18992
|
-
const numericMatches = [...(allContentStrings.match(numericRegex) || [])];
|
|
18993
|
-
let currentNumericCount = numericMatches.reduce((count, match) => {
|
|
18994
|
-
const id = parseInt(match.split(" ")[1], 10);
|
|
18995
|
-
return id >= count ? id + 1 : count; // We want to start at the next highest number
|
|
18996
|
-
}, 1);
|
|
18997
|
-
|
|
18998
|
-
// Now that we have all the necessary information, we can create the renameMap
|
|
18999
|
-
for (const match of inputNumberMatches) {
|
|
19000
|
-
const oldKey = match;
|
|
19001
|
-
const newKey = `numeric-input ${currentNumericCount}`;
|
|
19002
|
-
renameMap[oldKey] = newKey;
|
|
19003
|
-
currentNumericCount++;
|
|
19004
|
-
}
|
|
19005
|
-
return renameMap;
|
|
19006
|
-
};
|
|
19007
|
-
|
|
19008
|
-
// Convert the user input data keys from input-number to numeric-input
|
|
19009
|
-
const convertUserInputNumberData = (userInputMap, renameMap) => {
|
|
19010
|
-
const updatedUserInputMap = {
|
|
19011
|
-
...userInputMap
|
|
19012
|
-
};
|
|
19013
|
-
for (const key of Object.keys(userInputMap)) {
|
|
19014
|
-
if (key.includes("input-number")) {
|
|
19015
|
-
const updatedKey = renameMap[key];
|
|
19016
|
-
updatedUserInputMap[updatedKey] = userInputMap[key];
|
|
19017
|
-
delete updatedUserInputMap[key];
|
|
19018
|
-
}
|
|
19019
|
-
}
|
|
19020
|
-
return updatedUserInputMap;
|
|
19021
|
-
};
|
|
19022
|
-
|
|
19023
|
-
// Used to convert InputNumber widgets to NumericInput widgets for scoring
|
|
19024
|
-
const convertInputNumberForScoring = (rubric, userInputMap) => {
|
|
19025
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
19026
|
-
// so that we can ensure we update the content, widgets, AND userInput accordingly
|
|
19027
|
-
const renameMap = getInputNumberRenameMap(rubric);
|
|
19028
|
-
const convertedRubric = convertInputNumberJson(rubric, renameMap);
|
|
19029
|
-
const convertedUserData = convertUserInputNumberData(userInputMap, renameMap);
|
|
19030
|
-
return {
|
|
19031
|
-
convertedRubric,
|
|
19032
|
-
convertedUserData
|
|
19033
|
-
};
|
|
19034
|
-
};
|
|
19035
|
-
|
|
19036
|
-
const widgetRegExes = [/input-number \d+/]; // We can add more regexes here in the future
|
|
19037
|
-
|
|
19038
|
-
// This utils in this file are used to modernize our Perseus JSON structure,
|
|
19039
|
-
// so that we can convert deprecated widgets to their modern equivalents when
|
|
19040
|
-
// content creators use the Editor Page to update content containing these widgets.
|
|
19041
|
-
// Currently, we're only converting input-number to numeric-input,
|
|
19042
|
-
// but we can add more conversions here in the future.
|
|
19043
|
-
|
|
19044
|
-
// Modernize the json content of a PerseusRenderer object
|
|
19045
|
-
// by converting deprecated widgets to their modern equivalents
|
|
19046
|
-
const convertDeprecatedWidgets = json => {
|
|
19047
|
-
// Currently we're only converting input-number to numeric-input,
|
|
19048
|
-
// But we can add more conversions here in the future
|
|
19049
|
-
return inputNumberToNumericInput(json);
|
|
19050
|
-
};
|
|
19051
|
-
const conversionRequired = json => {
|
|
19052
|
-
// If there's no content, then there's no conversion required
|
|
19053
|
-
if (!json.content) {
|
|
19054
|
-
return false;
|
|
19055
|
-
}
|
|
19056
|
-
// Check the content string for any top-level input-number widgets
|
|
19057
|
-
if (widgetRegExes.some(regex => regex.test(json.content))) {
|
|
19058
|
-
return true;
|
|
19059
|
-
}
|
|
19060
|
-
|
|
19061
|
-
// If there's no deprecated widget in the top-level, then check for any nested widgets
|
|
19062
|
-
for (const key of Object.keys(json.widgets)) {
|
|
19063
|
-
if (json.widgets[key].options.widgets) {
|
|
19064
|
-
const nestedJson = json.widgets[key].options;
|
|
19065
|
-
if (conversionRequired(nestedJson)) {
|
|
19066
|
-
return true;
|
|
19067
|
-
}
|
|
19068
|
-
}
|
|
19069
|
-
}
|
|
19070
|
-
return false;
|
|
19071
|
-
};
|
|
19072
|
-
|
|
19073
|
-
/**
|
|
19074
|
-
* Convert the deprecated widgets in the rubric and user data to their
|
|
19075
|
-
* modern equivalents for scoring. These need to be updated together
|
|
19076
|
-
* in order to ensure that the widgetKeys match between the rubric and user data.
|
|
19077
|
-
*/
|
|
19078
|
-
const convertDeprecatedWidgetsForScoring = (rubric, userInputMap) => {
|
|
19079
|
-
return convertInputNumberForScoring(rubric, userInputMap);
|
|
19080
|
-
};
|
|
19081
|
-
|
|
19082
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
19083
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
19084
|
-
|
|
19085
|
-
/**
|
|
19086
|
-
* Regex for widget placeholders in a string.
|
|
19087
|
-
*
|
|
19088
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
19089
|
-
* Second capture group is the widget type (ex. "radio)
|
|
19090
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
19091
|
-
*/
|
|
19092
|
-
function getWidgetRegex() {
|
|
19093
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
19094
|
-
}
|
|
19095
|
-
|
|
19096
|
-
/**
|
|
19097
|
-
* Add a widget placeholder using the widget ID.
|
|
19098
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
19099
|
-
*
|
|
19100
|
-
* @param {string} id
|
|
19101
|
-
* @returns {string}
|
|
19102
|
-
*/
|
|
19103
|
-
function addWidget(id) {
|
|
19104
|
-
return `[[☃ ${id}]]`;
|
|
19105
|
-
}
|
|
19106
|
-
|
|
19107
|
-
/**
|
|
19108
|
-
* Get a widget type by a widget's ID
|
|
19109
|
-
* (please don't derive type from ID)
|
|
19110
|
-
*
|
|
19111
|
-
* @param {string} widgetId the ID of the widget
|
|
19112
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
19113
|
-
* @returns {string} the widget type (ie "radio")
|
|
19114
|
-
*/
|
|
19115
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
19116
|
-
const widget = widgetMap[WidgetId];
|
|
19117
|
-
return widget?.type ?? null;
|
|
19118
|
-
}
|
|
19119
|
-
|
|
19120
|
-
/**
|
|
19121
|
-
* Does the content have a specific type of widget?
|
|
19122
|
-
*
|
|
19123
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
19124
|
-
* @param {string} content the string to search through
|
|
19125
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
19126
|
-
* @returns {boolean} if the content includes the widget type
|
|
19127
|
-
*/
|
|
19128
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
19129
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
19130
|
-
}
|
|
19131
|
-
|
|
19132
|
-
/**
|
|
19133
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
19134
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
19135
|
-
*
|
|
19136
|
-
* Content should contain Perseus widget placeholders,
|
|
19137
|
-
* which look like: '[[☃ radio 1]]'.
|
|
19138
|
-
*
|
|
19139
|
-
* @param {string} content
|
|
19140
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
19141
|
-
*/
|
|
19142
|
-
function getWidgetIdsFromContent(content) {
|
|
19143
|
-
const widgets = [];
|
|
19144
|
-
const localWidgetRegex = getWidgetRegex();
|
|
19145
|
-
let match = localWidgetRegex.exec(content);
|
|
19146
|
-
while (match !== null) {
|
|
19147
|
-
widgets.push(match[1]);
|
|
19148
|
-
match = localWidgetRegex.exec(content);
|
|
19149
|
-
}
|
|
19150
|
-
return widgets;
|
|
19151
|
-
}
|
|
19152
|
-
|
|
19153
|
-
/**
|
|
19154
|
-
* Get a list of widget IDs from content,
|
|
19155
|
-
* but only for specific widget types
|
|
19156
|
-
*
|
|
19157
|
-
* @param {string} type the type of widget (ie "radio")
|
|
19158
|
-
* @param {string} content the string to parse
|
|
19159
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
19160
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
19161
|
-
*/
|
|
19162
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
19163
|
-
const rv = [];
|
|
19164
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
19165
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
19166
|
-
const widget = widgetMap[widgetId];
|
|
19167
|
-
if (widget?.type === type) {
|
|
19168
|
-
rv.push(widgetId);
|
|
19169
|
-
}
|
|
19170
|
-
});
|
|
19171
|
-
return rv;
|
|
19172
|
-
}
|
|
19173
|
-
|
|
19174
|
-
/**
|
|
19175
|
-
* Pull the widget map out of ItemData
|
|
19176
|
-
*
|
|
19177
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
19178
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
19179
|
-
*/
|
|
19180
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
19181
|
-
return itemData.question.widgets;
|
|
19182
|
-
}
|
|
19183
|
-
|
|
19184
|
-
/**
|
|
19185
|
-
* Get the widget information from a WidgetMap
|
|
19186
|
-
*
|
|
19187
|
-
* @param {string} widgetId the ID of the widget
|
|
19188
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
19189
|
-
*/
|
|
19190
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
19191
|
-
return widgetMap[widgetId] ?? null;
|
|
19192
|
-
}
|
|
19193
|
-
|
|
19194
|
-
/**
|
|
19195
|
-
* Get select widgets from a widget map.
|
|
19196
|
-
* Useful for multi-items that needs to split
|
|
19197
|
-
* one PerseusItem into several
|
|
19198
|
-
*
|
|
19199
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
19200
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
19201
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
19202
|
-
*/
|
|
19203
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
19204
|
-
const widgets = {};
|
|
19205
|
-
widgetIds.forEach(widgetId => {
|
|
19206
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
19207
|
-
if (widget) {
|
|
19208
|
-
widgets[widgetId] = widget;
|
|
19209
|
-
}
|
|
19210
|
-
});
|
|
19211
|
-
return widgets;
|
|
19212
|
-
}
|
|
19213
|
-
|
|
19214
18923
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
19215
18924
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
19216
18925
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19261,9 +18970,6 @@ const replaceDeprecatedWidgets = () => {
|
|
|
19261
18970
|
replaceWidget("sequence", "deprecated-standin");
|
|
19262
18971
|
replaceWidget("simulator", "deprecated-standin");
|
|
19263
18972
|
replaceWidget("unit-input", "deprecated-standin");
|
|
19264
|
-
|
|
19265
|
-
// Input-Number is a special case as it is being replaced by Numeric-Input
|
|
19266
|
-
replaceWidget("input-number", "numeric-input");
|
|
19267
18973
|
};
|
|
19268
18974
|
const registerEditors = editorsToRegister => {
|
|
19269
18975
|
editorsToRegister.forEach(editor => {
|
|
@@ -19298,11 +19004,6 @@ const replaceDeprecatedEditors = () => {
|
|
|
19298
19004
|
replaceEditor("sequence", "deprecated-standin");
|
|
19299
19005
|
replaceEditor("simulator", "deprecated-standin");
|
|
19300
19006
|
replaceEditor("unit-input", "deprecated-standin");
|
|
19301
|
-
|
|
19302
|
-
// We're replacing the input-number editor with the numeric-input editor here,
|
|
19303
|
-
// but we're also modifying the JSON content to convert input-number into the
|
|
19304
|
-
// numeric-input format over in editor-page.tsx on line 92.
|
|
19305
|
-
replaceEditor("input-number", "numeric-input");
|
|
19306
19007
|
};
|
|
19307
19008
|
const getWidget = name => {
|
|
19308
19009
|
// TODO(alex): Consider referring to these as renderers to avoid
|
|
@@ -19349,8 +19050,10 @@ const getVersionVector = () => {
|
|
|
19349
19050
|
const getPublicWidgets = () => {
|
|
19350
19051
|
// TODO(alex): Update underscore.js so that _.pick can take a function.
|
|
19351
19052
|
// @ts-expect-error - TS2740 - Type 'Pick<{ [key: string]: Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>; }, string>' is missing the following properties from type 'readonly Readonly<{ name: string; displayName: string; getWidget?: (() => ComponentType<any>) | undefined; accessible?: boolean | ((props: any) => boolean) | undefined; hidden?: boolean | undefined; ... 10 more ...; widget: ComponentType<...>; }>[]': length, concat, join, slice, and 18 more.
|
|
19352
|
-
return ___default["default"].pick(widgets,
|
|
19353
|
-
|
|
19053
|
+
return ___default["default"].pick(widgets,
|
|
19054
|
+
// @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
|
|
19055
|
+
___default["default"].reject(___default["default"].keys(widgets), function (name) {
|
|
19056
|
+
return widgets[name].hidden;
|
|
19354
19057
|
}));
|
|
19355
19058
|
};
|
|
19356
19059
|
const isAccessible = widgetInfo => {
|
|
@@ -19671,34 +19374,6 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19671
19374
|
}
|
|
19672
19375
|
});
|
|
19673
19376
|
}
|
|
19674
|
-
|
|
19675
|
-
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19676
|
-
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19677
|
-
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19678
|
-
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19679
|
-
// need to be removed before we move this to the server
|
|
19680
|
-
strings, locale) {
|
|
19681
|
-
let convertedRenderData = perseusRenderData;
|
|
19682
|
-
let convertedUserInputMap = userInputMap;
|
|
19683
|
-
|
|
19684
|
-
// Check if the PerseusRenderer object contains any deprecated widgets that need to be converted
|
|
19685
|
-
const mustConvertData = conversionRequired(perseusRenderData);
|
|
19686
|
-
if (mustConvertData) {
|
|
19687
|
-
const {
|
|
19688
|
-
convertedRubric,
|
|
19689
|
-
convertedUserData
|
|
19690
|
-
} = convertDeprecatedWidgetsForScoring(perseusRenderData, userInputMap);
|
|
19691
|
-
convertedRenderData = convertedRubric;
|
|
19692
|
-
convertedUserInputMap = convertedUserData;
|
|
19693
|
-
}
|
|
19694
|
-
|
|
19695
|
-
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19696
|
-
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19697
|
-
// so this checks that the widgets are being used before scoring them
|
|
19698
|
-
const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
|
|
19699
|
-
const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
|
|
19700
|
-
return Util.flattenScores(scores);
|
|
19701
|
-
}
|
|
19702
19377
|
function scoreWidgetsFunctional(widgets,
|
|
19703
19378
|
// This is a port of old code, I'm not sure why
|
|
19704
19379
|
// we need widgetIds vs the keys of the widgets object
|
|
@@ -19719,7 +19394,7 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19719
19394
|
const userInput = userInputMap[id];
|
|
19720
19395
|
const scorer = getWidgetScorer(widget.type);
|
|
19721
19396
|
if (widget.type === "group") {
|
|
19722
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19397
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19723
19398
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19724
19399
|
} else if (scorer) {
|
|
19725
19400
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -20768,7 +20443,7 @@ class Renderer extends React__namespace.Component {
|
|
|
20768
20443
|
// Instead, we center them with css magic in styles.less
|
|
20769
20444
|
// /cry(aria)
|
|
20770
20445
|
this._foundTextNodes = true;
|
|
20771
|
-
if (this.widgetIds
|
|
20446
|
+
if (___default["default"].contains(this.widgetIds, node.id)) {
|
|
20772
20447
|
// We don't want to render a duplicate widget key/ref,
|
|
20773
20448
|
// as this causes problems with react (for obvious
|
|
20774
20449
|
// reasons). Instead we just notify the
|
|
@@ -21093,7 +20768,7 @@ class Renderer extends React__namespace.Component {
|
|
|
21093
20768
|
};
|
|
21094
20769
|
getInputPaths = () => {
|
|
21095
20770
|
const inputPaths = [];
|
|
21096
|
-
this.widgetIds
|
|
20771
|
+
___default["default"].each(this.widgetIds, widgetId => {
|
|
21097
20772
|
const widget = this.getWidgetInstance(widgetId);
|
|
21098
20773
|
if (widget && widget.getInputPaths) {
|
|
21099
20774
|
// Grab all input paths and add widgetID to the front
|
|
@@ -21101,7 +20776,7 @@ class Renderer extends React__namespace.Component {
|
|
|
21101
20776
|
// Prefix paths with their widgetID and add to collective
|
|
21102
20777
|
// list of paths.
|
|
21103
20778
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
21104
|
-
|
|
20779
|
+
___default["default"].each(widgetInputPaths, inputPath => {
|
|
21105
20780
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
21106
20781
|
inputPaths.push(relativeInputPath);
|
|
21107
20782
|
});
|
|
@@ -21294,30 +20969,33 @@ class Renderer extends React__namespace.Component {
|
|
|
21294
20969
|
}
|
|
21295
20970
|
|
|
21296
20971
|
/**
|
|
21297
|
-
*
|
|
21298
|
-
*
|
|
21299
|
-
*
|
|
20972
|
+
* Returns an object mapping from widget ID to perseus-style score.
|
|
20973
|
+
* The keys of this object are the values of the array returned
|
|
20974
|
+
* from `getWidgetIds`.
|
|
21300
20975
|
*/
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
const combinedScore = Util.flattenScores(scores);
|
|
21304
|
-
return combinedScore;
|
|
20976
|
+
scoreWidgets() {
|
|
20977
|
+
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
21305
20978
|
}
|
|
21306
20979
|
|
|
21307
20980
|
/**
|
|
21308
|
-
*
|
|
20981
|
+
* Grades the content.
|
|
21309
20982
|
*/
|
|
21310
|
-
|
|
20983
|
+
score() {
|
|
20984
|
+
const scores = this.scoreWidgets();
|
|
20985
|
+
const combinedScore = Util.flattenScores(scores);
|
|
20986
|
+
return combinedScore;
|
|
20987
|
+
}
|
|
20988
|
+
guessAndScore() {
|
|
21311
20989
|
const totalGuess = this.getUserInput();
|
|
21312
20990
|
const totalScore = this.score();
|
|
21313
20991
|
return [totalGuess, totalScore];
|
|
21314
|
-
}
|
|
20992
|
+
}
|
|
21315
20993
|
examples = () => {
|
|
21316
20994
|
const widgetIds = this.widgetIds;
|
|
21317
|
-
const examples =
|
|
20995
|
+
const examples = ___default["default"].compact(___default["default"].map(widgetIds, widgetId => {
|
|
21318
20996
|
const widget = this.getWidgetInstance(widgetId);
|
|
21319
20997
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
21320
|
-
})
|
|
20998
|
+
}));
|
|
21321
20999
|
|
|
21322
21000
|
// no widgets with examples
|
|
21323
21001
|
if (!examples.length) {
|
|
@@ -22322,11 +22000,17 @@ class InputNumber extends React__namespace.Component {
|
|
|
22322
22000
|
this.refs.input.focus();
|
|
22323
22001
|
return true;
|
|
22324
22002
|
};
|
|
22003
|
+
|
|
22004
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22005
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22325
22006
|
focusInputPath = inputPath => {
|
|
22326
22007
|
// eslint-disable-next-line react/no-string-refs
|
|
22327
22008
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
22328
22009
|
this.refs.input.focus();
|
|
22329
22010
|
};
|
|
22011
|
+
|
|
22012
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22013
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22330
22014
|
blurInputPath = inputPath => {
|
|
22331
22015
|
// eslint-disable-next-line react/no-string-refs
|
|
22332
22016
|
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
|
|
@@ -22342,6 +22026,9 @@ class InputNumber extends React__namespace.Component {
|
|
|
22342
22026
|
/* c8 ignore next */
|
|
22343
22027
|
return [[]];
|
|
22344
22028
|
};
|
|
22029
|
+
|
|
22030
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22031
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22345
22032
|
setInputValue = (path, newValue, cb) => {
|
|
22346
22033
|
this.props.onChange({
|
|
22347
22034
|
currentValue: newValue
|
|
@@ -22452,6 +22139,8 @@ var InputNumber$1 = {
|
|
|
22452
22139
|
widget: InputNumber,
|
|
22453
22140
|
transform: propTransform$3,
|
|
22454
22141
|
isLintable: true,
|
|
22142
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22143
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
22455
22144
|
scorer: scoreInputNumber,
|
|
22456
22145
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22457
22146
|
if (rubric.value == null) {
|
|
@@ -22745,7 +22434,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22745
22434
|
height: "auto"
|
|
22746
22435
|
}
|
|
22747
22436
|
});
|
|
22748
|
-
return /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
|
|
22437
|
+
return /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement(InputWithExamples, {
|
|
22749
22438
|
ref: ref => this.inputRef = ref,
|
|
22750
22439
|
value: this.props.currentValue,
|
|
22751
22440
|
onChange: this.handleChange,
|
|
@@ -22757,7 +22446,7 @@ class NumericInput extends React__namespace.Component {
|
|
|
22757
22446
|
id: this.props.widgetId,
|
|
22758
22447
|
disabled: this.props.apiOptions.readOnly,
|
|
22759
22448
|
style: styles.input
|
|
22760
|
-
});
|
|
22449
|
+
}));
|
|
22761
22450
|
}
|
|
22762
22451
|
}
|
|
22763
22452
|
|
|
@@ -22815,69 +22504,19 @@ const propsTransform = function (widgetOptions) {
|
|
|
22815
22504
|
});
|
|
22816
22505
|
return rendererProps;
|
|
22817
22506
|
};
|
|
22818
|
-
|
|
22819
|
-
// This function is being used to replace the input-number widget
|
|
22820
|
-
// with the numeric-input widget
|
|
22821
|
-
const propUpgrades$2 = {
|
|
22822
|
-
/* c8 ignore next */
|
|
22823
|
-
"1": initialProps => {
|
|
22824
|
-
// If the initialProps has simplify, it means we're upgrading from
|
|
22825
|
-
// input-number to numeric-input. In this case, we need to upgrade
|
|
22826
|
-
// the widget options accordingly.
|
|
22827
|
-
if (initialProps.simplify !== undefined) {
|
|
22828
|
-
// If the answerType is not number or percent, we need to provide
|
|
22829
|
-
// the answer form for the numeric-input widget
|
|
22830
|
-
const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
|
|
22831
|
-
|
|
22832
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
22833
|
-
const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
22834
|
-
: initialProps.answerType; // Otherwise, we can use the answerType directly
|
|
22835
|
-
|
|
22836
|
-
// If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
|
|
22837
|
-
// function in input-number.ts in the Perseus Editor package's util folder
|
|
22838
|
-
const answers = [{
|
|
22839
|
-
value: initialProps.value,
|
|
22840
|
-
simplify: initialProps.simplify,
|
|
22841
|
-
answerForms: provideAnswerForm ? [mathFormat] : undefined,
|
|
22842
|
-
strict: initialProps.inexact,
|
|
22843
|
-
// We only want to set maxError if the inexact prop is true
|
|
22844
|
-
maxError: initialProps.inexact ? initialProps.maxError : 0,
|
|
22845
|
-
status: "correct",
|
|
22846
|
-
// Input-number only allows correct answers
|
|
22847
|
-
message: ""
|
|
22848
|
-
}];
|
|
22849
|
-
return {
|
|
22850
|
-
answers,
|
|
22851
|
-
size: initialProps.size,
|
|
22852
|
-
coefficient: false,
|
|
22853
|
-
// input-number doesn't have a coefficient prop
|
|
22854
|
-
labelText: "",
|
|
22855
|
-
// input-number doesn't have a labelText prop
|
|
22856
|
-
static: false,
|
|
22857
|
-
// static is always false for numeric-input
|
|
22858
|
-
rightAlign: initialProps.rightAlign || false
|
|
22859
|
-
};
|
|
22860
|
-
} else {
|
|
22861
|
-
// Otherwise simply return the initialProps as there's no differences
|
|
22862
|
-
// between v0 and v1 for numeric-input
|
|
22863
|
-
return initialProps;
|
|
22864
|
-
}
|
|
22865
|
-
}
|
|
22866
|
-
};
|
|
22867
22507
|
var NumericInput$1 = {
|
|
22868
22508
|
name: "numeric-input",
|
|
22869
22509
|
displayName: "Numeric input",
|
|
22870
22510
|
defaultAlignment: "inline-block",
|
|
22871
22511
|
accessible: true,
|
|
22872
22512
|
widget: NumericInput,
|
|
22873
|
-
version: {
|
|
22874
|
-
major: 1,
|
|
22875
|
-
minor: 0
|
|
22876
|
-
},
|
|
22877
22513
|
transform: propsTransform,
|
|
22878
|
-
propUpgrades: propUpgrades$2,
|
|
22879
22514
|
isLintable: true,
|
|
22515
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22516
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
22880
22517
|
scorer: scoreNumericInput,
|
|
22518
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22519
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
22881
22520
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22882
22521
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22883
22522
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -24696,6 +24335,9 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24696
24335
|
borderRadius: "50%",
|
|
24697
24336
|
textAlign: "center"
|
|
24698
24337
|
};
|
|
24338
|
+
|
|
24339
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
24340
|
+
// @ts-expect-error: State is not assignable to ParseState
|
|
24699
24341
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
24700
24342
|
const parse$2 = (source, state) => {
|
|
24701
24343
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
@@ -27151,6 +26793,8 @@ var Radio = {
|
|
|
27151
26793
|
},
|
|
27152
26794
|
propUpgrades: propUpgrades$1,
|
|
27153
26795
|
isLintable: true,
|
|
26796
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26797
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
27154
26798
|
scorer: scoreRadio
|
|
27155
26799
|
};
|
|
27156
26800
|
|
|
@@ -27374,6 +27018,8 @@ var Categorizer$1 = {
|
|
|
27374
27018
|
return ___default["default"].pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
27375
27019
|
},
|
|
27376
27020
|
isLintable: true,
|
|
27021
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27022
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27377
27023
|
scorer: scoreCategorizer
|
|
27378
27024
|
};
|
|
27379
27025
|
|
|
@@ -27611,6 +27257,8 @@ var CSProgram$1 = {
|
|
|
27611
27257
|
supportedAlignments: ["block", "full-width"],
|
|
27612
27258
|
widget: CSProgram,
|
|
27613
27259
|
hidden: true,
|
|
27260
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27261
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27614
27262
|
scorer: scoreCSProgram
|
|
27615
27263
|
};
|
|
27616
27264
|
|
|
@@ -27826,6 +27474,8 @@ var Dropdown$1 = {
|
|
|
27826
27474
|
accessible: true,
|
|
27827
27475
|
widget: Dropdown,
|
|
27828
27476
|
transform: optionsTransform,
|
|
27477
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27478
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
27829
27479
|
scorer: scoreDropdown
|
|
27830
27480
|
};
|
|
27831
27481
|
|
|
@@ -29412,6 +29062,8 @@ class FunctionGrapher extends React__namespace.Component {
|
|
|
29412
29062
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
29413
29063
|
args[_key] = arguments[_key];
|
|
29414
29064
|
}
|
|
29065
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29066
|
+
// @ts-expect-error: Argument of type 'any[]' is not assignable to parameter of type '[newPropsOrSinglePropName: string | { [key: string]: any; }, propValue?: any, callback?: (() => unknown) | undefined]'. Target requires 1 element(s) but source may have fewer.
|
|
29415
29067
|
return change.apply(_this, args);
|
|
29416
29068
|
};
|
|
29417
29069
|
})();
|
|
@@ -29769,6 +29421,8 @@ var Grapher$1 = {
|
|
|
29769
29421
|
widget: Grapher,
|
|
29770
29422
|
transform: propTransform$2,
|
|
29771
29423
|
staticTransform: staticTransform$4,
|
|
29424
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29425
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
29772
29426
|
scorer: scoreGrapher
|
|
29773
29427
|
};
|
|
29774
29428
|
|
|
@@ -29892,9 +29546,6 @@ class Group extends React__namespace.Component {
|
|
|
29892
29546
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29893
29547
|
}
|
|
29894
29548
|
};
|
|
29895
|
-
|
|
29896
|
-
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29897
|
-
// for valid/invalid state
|
|
29898
29549
|
const score = this.rendererRef?.score();
|
|
29899
29550
|
const isValid = score && score.type !== "invalid";
|
|
29900
29551
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -30102,6 +29753,8 @@ var Iframe$1 = {
|
|
|
30102
29753
|
widget: Iframe,
|
|
30103
29754
|
// Let's not expose it to all content creators yet
|
|
30104
29755
|
hidden: true,
|
|
29756
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29757
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
30105
29758
|
scorer: scoreIframe
|
|
30106
29759
|
};
|
|
30107
29760
|
|
|
@@ -31787,7 +31440,7 @@ function isUnlimitedGraphState(state) {
|
|
|
31787
31440
|
function replaceOutsideTeX(mathString) {
|
|
31788
31441
|
// All the information we need is in the first section,
|
|
31789
31442
|
// whether it's typed as "blockmath" or "paragraph"
|
|
31790
|
-
const firstSection =
|
|
31443
|
+
const firstSection = pureMarkdown.parse(mathString)[0];
|
|
31791
31444
|
|
|
31792
31445
|
// If it's blockMath, the outer level has the full math content.
|
|
31793
31446
|
if (firstSection.type === "blockMath") {
|
|
@@ -31840,6 +31493,38 @@ function escapeSpecialChars(str) {
|
|
|
31840
31493
|
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31841
31494
|
}
|
|
31842
31495
|
|
|
31496
|
+
/**
|
|
31497
|
+
* Parse a string of text and math into a list of objects with type and content
|
|
31498
|
+
*
|
|
31499
|
+
* Example: "Pi is about $\frac{22}{7}$" ==>
|
|
31500
|
+
* [
|
|
31501
|
+
* {type: "text", content: "Pi is about "},
|
|
31502
|
+
* {type: "math", content: "\\frac{22}{7}"},
|
|
31503
|
+
* ]
|
|
31504
|
+
*/
|
|
31505
|
+
const mathOnlyParser = SimpleMarkdown__default["default"].parserFor({
|
|
31506
|
+
math: {
|
|
31507
|
+
...pureMarkdown.pureMarkdownRules.math,
|
|
31508
|
+
order: 0
|
|
31509
|
+
},
|
|
31510
|
+
text: {
|
|
31511
|
+
order: 1,
|
|
31512
|
+
match: SimpleMarkdown__default["default"].anyScopeRegex(/^([^$\\{}]+)/),
|
|
31513
|
+
parse: capture => ({
|
|
31514
|
+
content: capture[0]
|
|
31515
|
+
})
|
|
31516
|
+
},
|
|
31517
|
+
specialCharacter: {
|
|
31518
|
+
order: 2,
|
|
31519
|
+
match: SimpleMarkdown__default["default"].anyScopeRegex(/^(\\[\S\s]|\$|\\$|{|})/),
|
|
31520
|
+
parse: capture => ({
|
|
31521
|
+
content: capture[0]
|
|
31522
|
+
})
|
|
31523
|
+
}
|
|
31524
|
+
}, {
|
|
31525
|
+
inline: true
|
|
31526
|
+
});
|
|
31527
|
+
|
|
31843
31528
|
function AxisLabels() {
|
|
31844
31529
|
const {
|
|
31845
31530
|
range,
|
|
@@ -32786,12 +32471,17 @@ const ItemExtras = [
|
|
|
32786
32471
|
// prettier-ignore
|
|
32787
32472
|
|
|
32788
32473
|
// prettier-ignore
|
|
32789
|
-
|
|
32474
|
+
|
|
32790
32475
|
// prettier-ignore
|
|
32476
|
+
|
|
32791
32477
|
// prettier-ignore
|
|
32478
|
+
|
|
32792
32479
|
// prettier-ignore
|
|
32480
|
+
|
|
32793
32481
|
//prettier-ignore
|
|
32482
|
+
|
|
32794
32483
|
// A background image applied to various widgets.
|
|
32484
|
+
|
|
32795
32485
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32796
32486
|
|
|
32797
32487
|
// 2D range: xMin, xMax, yMin, yMax
|
|
@@ -33293,7 +32983,9 @@ const actions = {
|
|
|
33293
32983
|
removePoint,
|
|
33294
32984
|
focusPoint,
|
|
33295
32985
|
blurPoint,
|
|
33296
|
-
clickPoint
|
|
32986
|
+
clickPoint,
|
|
32987
|
+
closePolygon,
|
|
32988
|
+
openPolygon
|
|
33297
32989
|
},
|
|
33298
32990
|
quadratic: {
|
|
33299
32991
|
movePoint
|
|
@@ -33372,6 +33064,18 @@ function changeKeyboardInvitationVisibility(shouldShow) {
|
|
|
33372
33064
|
shouldShow
|
|
33373
33065
|
};
|
|
33374
33066
|
}
|
|
33067
|
+
const CLOSE_POLYGON = "close-polygon";
|
|
33068
|
+
function closePolygon() {
|
|
33069
|
+
return {
|
|
33070
|
+
type: CLOSE_POLYGON
|
|
33071
|
+
};
|
|
33072
|
+
}
|
|
33073
|
+
const OPEN_POLYGON = "open-polygon";
|
|
33074
|
+
function openPolygon() {
|
|
33075
|
+
return {
|
|
33076
|
+
type: OPEN_POLYGON
|
|
33077
|
+
};
|
|
33078
|
+
}
|
|
33375
33079
|
const MOVE_ALL = "move-all";
|
|
33376
33080
|
function moveAll(delta) {
|
|
33377
33081
|
return {
|
|
@@ -34915,33 +34619,17 @@ const LimitedPolygonGraph = props => {
|
|
|
34915
34619
|
}
|
|
34916
34620
|
})));
|
|
34917
34621
|
};
|
|
34918
|
-
|
|
34919
|
-
// TODO(catjohnson): reduce redundancy between LimitedPolygonGraph and UnlimitedPolygonGraph
|
|
34920
|
-
// both components are vary similar, however more implementation is needed to be added before
|
|
34921
|
-
// it is clear what can and can't be shared between components.
|
|
34922
34622
|
const UnlimitedPolygonGraph = props => {
|
|
34923
|
-
const [hovered, setHovered] = React__namespace.useState(false);
|
|
34924
|
-
// This is more so required for the re-rendering that occurs when state
|
|
34925
|
-
// updates; specifically with regard to line weighting and polygon focus.
|
|
34926
|
-
const [focusVisible, setFocusVisible] = React__namespace.useState(false);
|
|
34927
34623
|
const {
|
|
34928
34624
|
dispatch
|
|
34929
34625
|
} = props;
|
|
34930
34626
|
const {
|
|
34931
34627
|
coords,
|
|
34932
|
-
|
|
34933
|
-
showSides,
|
|
34934
|
-
range,
|
|
34935
|
-
snapStep,
|
|
34936
|
-
snapTo = "grid"
|
|
34628
|
+
closedPolygon
|
|
34937
34629
|
} = props.graphState;
|
|
34938
34630
|
const graphConfig = useGraphConfig();
|
|
34939
|
-
|
|
34940
|
-
// TODO(catjohnson): Explore abstracting this code as it is similar to point.tsx
|
|
34941
|
-
// and hopefully we can cut down ont the unlimited graph redundancy.
|
|
34942
34631
|
const {
|
|
34943
34632
|
range: [x, y],
|
|
34944
|
-
disableKeyboardInteraction,
|
|
34945
34633
|
graphDimensionsInPixels
|
|
34946
34634
|
} = graphConfig;
|
|
34947
34635
|
const widthPx = graphDimensionsInPixels[0];
|
|
@@ -34952,116 +34640,66 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34952
34640
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
34953
34641
|
// think coords can be null.
|
|
34954
34642
|
const points = coords ?? [[0, 0]];
|
|
34955
|
-
const polygonRef = React__namespace.useRef(null);
|
|
34956
|
-
const dragReferencePoint = points[0];
|
|
34957
|
-
const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
|
|
34958
|
-
const {
|
|
34959
|
-
dragging
|
|
34960
|
-
} = useDraggable({
|
|
34961
|
-
gestureTarget: polygonRef,
|
|
34962
|
-
point: dragReferencePoint,
|
|
34963
|
-
onMove: newPoint => {
|
|
34964
|
-
const delta = mafs.vec.sub(newPoint, dragReferencePoint);
|
|
34965
|
-
dispatch(actions.polygon.moveAll(delta));
|
|
34966
|
-
},
|
|
34967
|
-
constrainKeyboardMovement: constrain
|
|
34968
|
-
});
|
|
34969
|
-
const lines = getLines(points);
|
|
34970
34643
|
React__namespace.useEffect(() => {
|
|
34971
34644
|
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34972
34645
|
if (focusedIndex != null) {
|
|
34973
34646
|
pointRef.current[focusedIndex]?.focus();
|
|
34974
34647
|
}
|
|
34975
34648
|
}, [props.graphState.focusedPointIndex, pointRef]);
|
|
34976
|
-
|
|
34977
|
-
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
}
|
|
34981
|
-
|
|
34982
|
-
|
|
34983
|
-
|
|
34984
|
-
y: top,
|
|
34985
|
-
onClick: event => {
|
|
34986
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34987
|
-
const x = event.clientX - elementRect.x;
|
|
34988
|
-
const y = event.clientY - elementRect.y;
|
|
34989
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34990
|
-
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34991
|
-
}
|
|
34992
|
-
}), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
34993
|
-
points: [...points],
|
|
34994
|
-
color: "var(--movable-line-stroke-color)",
|
|
34995
|
-
svgPolygonProps: {
|
|
34996
|
-
strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
34649
|
+
if (closedPolygon) {
|
|
34650
|
+
const closedPolygonProps = {
|
|
34651
|
+
...props,
|
|
34652
|
+
numSides: coords.length
|
|
34653
|
+
};
|
|
34654
|
+
return /*#__PURE__*/React__namespace.createElement(LimitedPolygonGraph, closedPolygonProps);
|
|
34655
|
+
} else {
|
|
34656
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34997
34657
|
style: {
|
|
34998
|
-
fill: "
|
|
34658
|
+
fill: "rgba(0,0,0,0)",
|
|
34659
|
+
cursor: "crosshair"
|
|
34660
|
+
},
|
|
34661
|
+
width: widthPx,
|
|
34662
|
+
height: heightPx,
|
|
34663
|
+
x: left,
|
|
34664
|
+
y: top,
|
|
34665
|
+
onClick: event => {
|
|
34666
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34667
|
+
const x = event.clientX - elementRect.x;
|
|
34668
|
+
const y = event.clientY - elementRect.y;
|
|
34669
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34670
|
+
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34671
|
+
}
|
|
34672
|
+
}), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
|
|
34673
|
+
points: [...points],
|
|
34674
|
+
color: "var(--movable-line-stroke-color)",
|
|
34675
|
+
svgPolylineProps: {
|
|
34676
|
+
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34677
|
+
style: {
|
|
34678
|
+
fill: "transparent"
|
|
34679
|
+
}
|
|
34999
34680
|
}
|
|
35000
|
-
}
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35006
|
-
|
|
35007
|
-
return /*#__PURE__*/React__namespace.createElement(PolygonAngle, {
|
|
35008
|
-
key: "angle-" + i,
|
|
35009
|
-
centerPoint: point,
|
|
35010
|
-
endPoints: [pt1, pt2],
|
|
35011
|
-
range: range,
|
|
35012
|
-
polygonLines: lines,
|
|
35013
|
-
showAngles: !!showAngles,
|
|
35014
|
-
snapTo: snapTo
|
|
35015
|
-
});
|
|
35016
|
-
}), showSides && lines.map((_ref2, i) => {
|
|
35017
|
-
let [start, end] = _ref2;
|
|
35018
|
-
const [x, y] = mafs.vec.midpoint(start, end);
|
|
35019
|
-
const length = parseFloat(mafs.vec.dist(start, end).toFixed(snapTo === "sides" ? 0 : 1));
|
|
35020
|
-
return /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
35021
|
-
key: "side-" + i,
|
|
35022
|
-
x: x,
|
|
35023
|
-
y: y
|
|
35024
|
-
}, !Number.isInteger(length) && "≈ ", length);
|
|
35025
|
-
}), /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
|
|
35026
|
-
points: [...points],
|
|
35027
|
-
color: "transparent",
|
|
35028
|
-
svgPolygonProps: {
|
|
35029
|
-
ref: polygonRef,
|
|
35030
|
-
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
35031
|
-
strokeWidth: TARGET_SIZE,
|
|
35032
|
-
style: {
|
|
35033
|
-
cursor: dragging ? "grabbing" : "grab",
|
|
35034
|
-
fill: hovered ? "var(--mafs-blue)" : "transparent"
|
|
34681
|
+
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34682
|
+
key: i,
|
|
34683
|
+
point: point,
|
|
34684
|
+
sequenceNumber: i + 1,
|
|
34685
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
34686
|
+
ref: ref => {
|
|
34687
|
+
pointRef.current[i] = ref;
|
|
35035
34688
|
},
|
|
35036
|
-
|
|
35037
|
-
|
|
35038
|
-
// Required to remove line weighting when user clicks away
|
|
35039
|
-
// from the focused polygon
|
|
35040
|
-
onKeyDownCapture: () => {
|
|
35041
|
-
setFocusVisible(hasFocusVisible(polygonRef.current));
|
|
34689
|
+
onFocus: () => {
|
|
34690
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
35042
34691
|
},
|
|
35043
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
|
|
35050
|
-
|
|
35051
|
-
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
35055
|
-
ref: ref => {
|
|
35056
|
-
pointRef.current[i] = ref;
|
|
35057
|
-
},
|
|
35058
|
-
onFocus: () => {
|
|
35059
|
-
dispatch(actions.pointGraph.focusPoint(i));
|
|
35060
|
-
},
|
|
35061
|
-
onClick: () => {
|
|
35062
|
-
dispatch(actions.pointGraph.clickPoint(i));
|
|
35063
|
-
}
|
|
35064
|
-
})));
|
|
34692
|
+
onClick: () => {
|
|
34693
|
+
// If the point being clicked is the first point and
|
|
34694
|
+
// there's enough points to form a polygon (3 or more)
|
|
34695
|
+
// Close the shape before setting focus.
|
|
34696
|
+
if (i === 0 && props.graphState.coords.length >= 3) {
|
|
34697
|
+
dispatch(actions.polygon.closePolygon());
|
|
34698
|
+
}
|
|
34699
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
34700
|
+
}
|
|
34701
|
+
})));
|
|
34702
|
+
}
|
|
35065
34703
|
};
|
|
35066
34704
|
function getLines(points) {
|
|
35067
34705
|
return points.map((point, i) => {
|
|
@@ -35660,23 +35298,47 @@ const renderPointGraphControls = props => {
|
|
|
35660
35298
|
}
|
|
35661
35299
|
}, perseusStrings.removePoint));
|
|
35662
35300
|
};
|
|
35663
|
-
|
|
35664
|
-
/**
|
|
35665
|
-
* TODO(catjohnson): The polygon controls will be vastly different from the point controls.
|
|
35666
|
-
* Will be keeping this function as is for the time being, even if it's a copy of the point graph
|
|
35667
|
-
* controls.
|
|
35668
|
-
*/
|
|
35669
35301
|
const renderPolygonGraphControls = props => {
|
|
35670
35302
|
const {
|
|
35671
35303
|
interactionMode,
|
|
35672
35304
|
showRemovePointButton,
|
|
35673
|
-
focusedPointIndex
|
|
35305
|
+
focusedPointIndex,
|
|
35306
|
+
closedPolygon,
|
|
35307
|
+
coords
|
|
35674
35308
|
} = props.state;
|
|
35675
35309
|
const {
|
|
35676
35310
|
perseusStrings
|
|
35677
35311
|
} = props;
|
|
35678
35312
|
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
35679
|
-
|
|
35313
|
+
|
|
35314
|
+
// If polygon is closed, show open button.
|
|
35315
|
+
// If polygon is open, show close button.
|
|
35316
|
+
const polygonButton = closedPolygon ? /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
35317
|
+
kind: "secondary",
|
|
35318
|
+
style: {
|
|
35319
|
+
width: "100%",
|
|
35320
|
+
marginLeft: "20px"
|
|
35321
|
+
},
|
|
35322
|
+
tabIndex: 0,
|
|
35323
|
+
onClick: () => {
|
|
35324
|
+
props.dispatch(actions.polygon.openPolygon());
|
|
35325
|
+
}
|
|
35326
|
+
}, perseusStrings.openPolygon) : /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
|
|
35327
|
+
kind: "secondary"
|
|
35328
|
+
// Conditional disable when there are less than 3 points in
|
|
35329
|
+
// the graph
|
|
35330
|
+
,
|
|
35331
|
+
disabled: coords.length < 3,
|
|
35332
|
+
style: {
|
|
35333
|
+
width: "100%",
|
|
35334
|
+
marginLeft: "20px"
|
|
35335
|
+
},
|
|
35336
|
+
tabIndex: 0,
|
|
35337
|
+
onClick: () => {
|
|
35338
|
+
props.dispatch(actions.polygon.closePolygon());
|
|
35339
|
+
}
|
|
35340
|
+
}, perseusStrings.closePolygon);
|
|
35341
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
35680
35342
|
style: {
|
|
35681
35343
|
flexDirection: "row",
|
|
35682
35344
|
width: props.width
|
|
@@ -35686,8 +35348,13 @@ const renderPolygonGraphControls = props => {
|
|
|
35686
35348
|
style: {
|
|
35687
35349
|
width: "100%",
|
|
35688
35350
|
marginLeft: "20px"
|
|
35689
|
-
}
|
|
35690
|
-
|
|
35351
|
+
}
|
|
35352
|
+
// Disable button when polygon is closed.
|
|
35353
|
+
,
|
|
35354
|
+
disabled: closedPolygon
|
|
35355
|
+
// Do not make the button tabbable when it is disabled.
|
|
35356
|
+
,
|
|
35357
|
+
tabIndex: closedPolygon ? -1 : 0,
|
|
35691
35358
|
onClick: () => {
|
|
35692
35359
|
props.dispatch(actions.polygon.addPoint([0, 0]));
|
|
35693
35360
|
}
|
|
@@ -35695,19 +35362,21 @@ const renderPolygonGraphControls = props => {
|
|
|
35695
35362
|
id: REMOVE_BUTTON_ID,
|
|
35696
35363
|
kind: "secondary",
|
|
35697
35364
|
color: "destructive"
|
|
35365
|
+
// Disable button when polygon is closed.
|
|
35366
|
+
,
|
|
35367
|
+
disabled: closedPolygon || !shouldShowRemoveButton
|
|
35698
35368
|
// This button is meant to be interacted with by the mouse only
|
|
35699
35369
|
// Never allow learners to tab to this button
|
|
35700
35370
|
,
|
|
35701
35371
|
tabIndex: -1,
|
|
35702
35372
|
style: {
|
|
35703
35373
|
width: "100%",
|
|
35704
|
-
marginLeft: "20px"
|
|
35705
|
-
visibility: shouldShowRemoveButton ? "visible" : "hidden"
|
|
35374
|
+
marginLeft: "20px"
|
|
35706
35375
|
},
|
|
35707
35376
|
onClick: _event => {
|
|
35708
35377
|
props.dispatch(actions.polygon.removePoint(props.state.focusedPointIndex));
|
|
35709
35378
|
}
|
|
35710
|
-
}, perseusStrings.removePoint));
|
|
35379
|
+
}, perseusStrings.removePoint), polygonButton));
|
|
35711
35380
|
};
|
|
35712
35381
|
const renderGraphControls = props => {
|
|
35713
35382
|
const {
|
|
@@ -35758,14 +35427,17 @@ function handleBlurEvent(_event, state, dispatch) {
|
|
|
35758
35427
|
}
|
|
35759
35428
|
function handleKeyboardEvent(event, state, dispatch) {
|
|
35760
35429
|
if (isUnlimitedGraphState(state)) {
|
|
35761
|
-
if (event.key === "Backspace") {
|
|
35430
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
35762
35431
|
// TODO(benchristel): Checking classList here is a hack to prevent
|
|
35763
35432
|
// points from being deleted if the user presses the backspace key
|
|
35764
35433
|
// while the whole graph is focused. Instead of doing this, we
|
|
35765
35434
|
// should move the keyboard event handler to the movable point
|
|
35766
35435
|
// handle element.
|
|
35767
35436
|
if (document.activeElement?.classList.contains("movable-point__focusable-handle")) {
|
|
35768
|
-
|
|
35437
|
+
// Only allow delete if type is point or a polygon that is open.
|
|
35438
|
+
if (state.type === "point" || state.type === "polygon" && !state.closedPolygon) {
|
|
35439
|
+
dispatch(actions.global.deleteIntent());
|
|
35440
|
+
}
|
|
35769
35441
|
}
|
|
35770
35442
|
|
|
35771
35443
|
// After removing a point blur
|
|
@@ -36015,7 +35687,8 @@ function initializeGraphState(params) {
|
|
|
36015
35687
|
focusedPointIndex: null,
|
|
36016
35688
|
showRemovePointButton: false,
|
|
36017
35689
|
interactionMode: "mouse",
|
|
36018
|
-
showKeyboardInteractionInvitation: false
|
|
35690
|
+
showKeyboardInteractionInvitation: false,
|
|
35691
|
+
closedPolygon: false
|
|
36019
35692
|
};
|
|
36020
35693
|
case "point":
|
|
36021
35694
|
return {
|
|
@@ -36299,6 +35972,10 @@ function interactiveGraphReducer(state, action) {
|
|
|
36299
35972
|
return doDeleteIntent(state);
|
|
36300
35973
|
case CLICK_POINT:
|
|
36301
35974
|
return doClickPoint(state, action);
|
|
35975
|
+
case CLOSE_POLYGON:
|
|
35976
|
+
return doClosePolygon(state);
|
|
35977
|
+
case OPEN_POLYGON:
|
|
35978
|
+
return doOpenPolygon(state);
|
|
36302
35979
|
case CHANGE_INTERACTION_MODE:
|
|
36303
35980
|
return doChangeInteractionMode(state, action);
|
|
36304
35981
|
case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
|
|
@@ -36355,6 +36032,24 @@ function doClickPoint(state, action) {
|
|
|
36355
36032
|
}
|
|
36356
36033
|
return state;
|
|
36357
36034
|
}
|
|
36035
|
+
function doClosePolygon(state) {
|
|
36036
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
36037
|
+
return {
|
|
36038
|
+
...state,
|
|
36039
|
+
closedPolygon: true
|
|
36040
|
+
};
|
|
36041
|
+
}
|
|
36042
|
+
return state;
|
|
36043
|
+
}
|
|
36044
|
+
function doOpenPolygon(state) {
|
|
36045
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
36046
|
+
return {
|
|
36047
|
+
...state,
|
|
36048
|
+
closedPolygon: false
|
|
36049
|
+
};
|
|
36050
|
+
}
|
|
36051
|
+
return state;
|
|
36052
|
+
}
|
|
36358
36053
|
function doChangeInteractionMode(state, action) {
|
|
36359
36054
|
if (isUnlimitedGraphState(state)) {
|
|
36360
36055
|
const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
|
|
@@ -36728,14 +36423,15 @@ function doAddPoint(state, action) {
|
|
|
36728
36423
|
return state;
|
|
36729
36424
|
}
|
|
36730
36425
|
}
|
|
36426
|
+
const newCoords = [...state.coords, snappedPoint];
|
|
36731
36427
|
|
|
36732
36428
|
// If there's no point in spot where we want the new point to go we add it there
|
|
36733
36429
|
return {
|
|
36734
36430
|
...state,
|
|
36735
36431
|
hasBeenInteractedWith: true,
|
|
36736
|
-
coords:
|
|
36432
|
+
coords: newCoords,
|
|
36737
36433
|
showRemovePointButton: false,
|
|
36738
|
-
focusedPointIndex:
|
|
36434
|
+
focusedPointIndex: newCoords.length - 1
|
|
36739
36435
|
};
|
|
36740
36436
|
}
|
|
36741
36437
|
function doRemovePoint(state, action) {
|
|
@@ -39031,6 +38727,8 @@ var InteractiveGraph$1 = {
|
|
|
39031
38727
|
displayName: "Interactive graph (Assessments only)",
|
|
39032
38728
|
widget: InteractiveGraph,
|
|
39033
38729
|
staticTransform: staticTransform$3,
|
|
38730
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
38731
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
39034
38732
|
scorer: scoreInteractiveGraph
|
|
39035
38733
|
};
|
|
39036
38734
|
|
|
@@ -40040,6 +39738,8 @@ var LabelImage$1 = {
|
|
|
40040
39738
|
widget: LabelImageWithDependencies,
|
|
40041
39739
|
accessible: true,
|
|
40042
39740
|
isLintable: true,
|
|
39741
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39742
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
40043
39743
|
scorer: scoreLabelImage
|
|
40044
39744
|
};
|
|
40045
39745
|
|
|
@@ -40979,6 +40679,8 @@ var Matcher$1 = {
|
|
|
40979
40679
|
displayName: "Matcher (two column)",
|
|
40980
40680
|
widget: Matcher,
|
|
40981
40681
|
isLintable: true,
|
|
40682
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40683
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
40982
40684
|
scorer: scoreMatcher
|
|
40983
40685
|
};
|
|
40984
40686
|
|
|
@@ -40995,7 +40697,35 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40995
40697
|
};
|
|
40996
40698
|
};
|
|
40997
40699
|
|
|
40700
|
+
/**
|
|
40701
|
+
* Checks user input from the matrix widget to see if it is scorable.
|
|
40702
|
+
*
|
|
40703
|
+
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40704
|
+
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40705
|
+
*
|
|
40706
|
+
* @see `scoreMatrix()` for more details.
|
|
40707
|
+
*/
|
|
40708
|
+
function validateMatrix(userInput, validationData, strings) {
|
|
40709
|
+
const supplied = userInput.answers;
|
|
40710
|
+
const suppliedSize = getMatrixSize(supplied);
|
|
40711
|
+
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40712
|
+
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40713
|
+
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40714
|
+
return {
|
|
40715
|
+
type: "invalid",
|
|
40716
|
+
message: strings.fillAllCells
|
|
40717
|
+
};
|
|
40718
|
+
}
|
|
40719
|
+
}
|
|
40720
|
+
}
|
|
40721
|
+
return null;
|
|
40722
|
+
}
|
|
40723
|
+
|
|
40998
40724
|
function scoreMatrix(userInput, rubric, strings) {
|
|
40725
|
+
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40726
|
+
if (validationResult != null) {
|
|
40727
|
+
return validationResult;
|
|
40728
|
+
}
|
|
40999
40729
|
const solution = rubric.answers;
|
|
41000
40730
|
const supplied = userInput.answers;
|
|
41001
40731
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -41003,13 +40733,9 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41003
40733
|
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
41004
40734
|
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
41005
40735
|
let message = null;
|
|
41006
|
-
let hasEmptyCell = false;
|
|
41007
40736
|
let incorrect = false;
|
|
41008
40737
|
___default["default"](suppliedSize[0]).times(row => {
|
|
41009
40738
|
___default["default"](suppliedSize[1]).times(col => {
|
|
41010
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
41011
|
-
hasEmptyCell = true;
|
|
41012
|
-
}
|
|
41013
40739
|
if (!incorrectSize) {
|
|
41014
40740
|
const validator = createValidator(
|
|
41015
40741
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
@@ -41027,12 +40753,6 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
41027
40753
|
}
|
|
41028
40754
|
});
|
|
41029
40755
|
});
|
|
41030
|
-
if (hasEmptyCell) {
|
|
41031
|
-
return {
|
|
41032
|
-
type: "invalid",
|
|
41033
|
-
message: strings.fillAllCells
|
|
41034
|
-
};
|
|
41035
|
-
}
|
|
41036
40756
|
if (incorrectSize) {
|
|
41037
40757
|
return {
|
|
41038
40758
|
type: "points",
|
|
@@ -41460,6 +41180,8 @@ var Matrix$1 = {
|
|
|
41460
41180
|
transform: propTransform$1,
|
|
41461
41181
|
staticTransform: staticTransform$2,
|
|
41462
41182
|
isLintable: true,
|
|
41183
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41184
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
41463
41185
|
scorer: scoreMatrix
|
|
41464
41186
|
};
|
|
41465
41187
|
|
|
@@ -43095,6 +42817,8 @@ var NumberLine$1 = {
|
|
|
43095
42817
|
widget: NumberLine,
|
|
43096
42818
|
transform: numberLineTransform,
|
|
43097
42819
|
staticTransform: staticTransform$1,
|
|
42820
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42821
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
43098
42822
|
scorer: scoreNumberLine
|
|
43099
42823
|
};
|
|
43100
42824
|
|
|
@@ -43669,6 +43393,8 @@ var Orderer$1 = {
|
|
|
43669
43393
|
hidden: true,
|
|
43670
43394
|
widget: Orderer,
|
|
43671
43395
|
isLintable: true,
|
|
43396
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43397
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
43672
43398
|
scorer: scoreOrderer
|
|
43673
43399
|
};
|
|
43674
43400
|
|
|
@@ -44761,6 +44487,8 @@ var Plotter$1 = {
|
|
|
44761
44487
|
hidden: true,
|
|
44762
44488
|
widget: Plotter,
|
|
44763
44489
|
staticTransform: staticTransform,
|
|
44490
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44491
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
44764
44492
|
scorer: scorePlotter
|
|
44765
44493
|
};
|
|
44766
44494
|
|
|
@@ -44944,6 +44672,8 @@ var Sorter$1 = {
|
|
|
44944
44672
|
displayName: "Sorter",
|
|
44945
44673
|
widget: Sorter,
|
|
44946
44674
|
isLintable: true,
|
|
44675
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44676
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
44947
44677
|
scorer: scoreSorter
|
|
44948
44678
|
};
|
|
44949
44679
|
|
|
@@ -45236,6 +44966,8 @@ var Table$1 = {
|
|
|
45236
44966
|
transform: propTransform,
|
|
45237
44967
|
hidden: true,
|
|
45238
44968
|
isLintable: true,
|
|
44969
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44970
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
45239
44971
|
scorer: scoreTable
|
|
45240
44972
|
};
|
|
45241
44973
|
|
|
@@ -45421,7 +45153,7 @@ const init = function () {
|
|
|
45421
45153
|
|
|
45422
45154
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45423
45155
|
const libName = "@khanacademy/perseus";
|
|
45424
|
-
const libVersion = "
|
|
45156
|
+
const libVersion = "43.0.0";
|
|
45425
45157
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45426
45158
|
|
|
45427
45159
|
const apiVersion = {
|
|
@@ -46026,13 +45758,6 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
46026
45758
|
this.props.onRendered(true);
|
|
46027
45759
|
}
|
|
46028
45760
|
}
|
|
46029
|
-
if (this.props.score && this.props.score !== prevProps.score) {
|
|
46030
|
-
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
46031
|
-
this.setState({
|
|
46032
|
-
questionCompleted: this.props.score.correct,
|
|
46033
|
-
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
46034
|
-
});
|
|
46035
|
-
}
|
|
46036
45761
|
}
|
|
46037
45762
|
componentWillUnmount() {
|
|
46038
45763
|
if (this.blurTimeoutID != null) {
|
|
@@ -46121,7 +45846,7 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
46121
45846
|
|
|
46122
45847
|
/**
|
|
46123
45848
|
* Accepts a question area widgetId, or an answer area widgetId of
|
|
46124
|
-
* the form "answer-
|
|
45849
|
+
* the form "answer-input-number 1", or the string "answer-area"
|
|
46125
45850
|
* for the whole answer area (if the answer area is a single widget).
|
|
46126
45851
|
*/
|
|
46127
45852
|
_setWidgetProps(widgetId, newProps, callback) {
|
|
@@ -46168,37 +45893,18 @@ class ServerItemRenderer extends React__namespace.Component {
|
|
|
46168
45893
|
return this.questionRenderer.getPromptJSON();
|
|
46169
45894
|
}
|
|
46170
45895
|
|
|
46171
|
-
/**
|
|
46172
|
-
* Returns an array of the widget `.getUserInput()` results
|
|
46173
|
-
*
|
|
46174
|
-
* TODO: can we remove this? Seems to be just for backwards
|
|
46175
|
-
* compatibility with old Perseus Chrome logging
|
|
46176
|
-
* @deprecated use getUserInput
|
|
46177
|
-
*/
|
|
46178
|
-
getUserInputLegacy() {
|
|
46179
|
-
return this.questionRenderer.getUserInput();
|
|
46180
|
-
}
|
|
46181
|
-
|
|
46182
|
-
/**
|
|
46183
|
-
* Returns an object of the widget `.getUserInput()` results
|
|
46184
|
-
*/
|
|
46185
|
-
getUserInput() {
|
|
46186
|
-
return this.questionRenderer.getUserInputMap();
|
|
46187
|
-
}
|
|
46188
|
-
|
|
46189
45896
|
/**
|
|
46190
45897
|
* Grades the item.
|
|
46191
|
-
*
|
|
46192
|
-
* @deprecated use scorePerseusItem
|
|
46193
45898
|
*/
|
|
46194
45899
|
scoreInput() {
|
|
46195
|
-
const
|
|
46196
|
-
const
|
|
45900
|
+
const guessAndScore = this.questionRenderer.guessAndScore();
|
|
45901
|
+
const guess = guessAndScore[0];
|
|
45902
|
+
const score = guessAndScore[1];
|
|
46197
45903
|
|
|
46198
45904
|
// Continue to include an empty guess for the now defunct answer area.
|
|
46199
45905
|
// TODO(alex): Check whether we rely on the format here for
|
|
46200
45906
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
46201
|
-
const maxCompatGuess = [
|
|
45907
|
+
const maxCompatGuess = [guess, []];
|
|
46202
45908
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
46203
45909
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
46204
45910
|
this.setState({
|
|
@@ -48060,6 +47766,138 @@ const registerAllWidgetsForTesting = () => {
|
|
|
48060
47766
|
replaceDeprecatedWidgets();
|
|
48061
47767
|
};
|
|
48062
47768
|
|
|
47769
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47770
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47771
|
+
|
|
47772
|
+
/**
|
|
47773
|
+
* Regex for widget placeholders in a string.
|
|
47774
|
+
*
|
|
47775
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
47776
|
+
* Second capture group is the widget type (ex. "radio)
|
|
47777
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47778
|
+
*/
|
|
47779
|
+
function getWidgetRegex() {
|
|
47780
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47781
|
+
}
|
|
47782
|
+
|
|
47783
|
+
/**
|
|
47784
|
+
* Add a widget placeholder using the widget ID.
|
|
47785
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47786
|
+
*
|
|
47787
|
+
* @param {string} id
|
|
47788
|
+
* @returns {string}
|
|
47789
|
+
*/
|
|
47790
|
+
function addWidget(id) {
|
|
47791
|
+
return `[[☃ ${id}]]`;
|
|
47792
|
+
}
|
|
47793
|
+
|
|
47794
|
+
/**
|
|
47795
|
+
* Get a widget type by a widget's ID
|
|
47796
|
+
* (please don't derive type from ID)
|
|
47797
|
+
*
|
|
47798
|
+
* @param {string} widgetId the ID of the widget
|
|
47799
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47800
|
+
* @returns {string} the widget type (ie "radio")
|
|
47801
|
+
*/
|
|
47802
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47803
|
+
const widget = widgetMap[WidgetId];
|
|
47804
|
+
return widget?.type ?? null;
|
|
47805
|
+
}
|
|
47806
|
+
|
|
47807
|
+
/**
|
|
47808
|
+
* Does the content have a specific type of widget?
|
|
47809
|
+
*
|
|
47810
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
47811
|
+
* @param {string} content the string to search through
|
|
47812
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47813
|
+
* @returns {boolean} if the content includes the widget type
|
|
47814
|
+
*/
|
|
47815
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
47816
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47817
|
+
}
|
|
47818
|
+
|
|
47819
|
+
/**
|
|
47820
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47821
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47822
|
+
*
|
|
47823
|
+
* Content should contain Perseus widget placeholders,
|
|
47824
|
+
* which look like: '[[☃ radio 1]]'.
|
|
47825
|
+
*
|
|
47826
|
+
* @param {string} content
|
|
47827
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
47828
|
+
*/
|
|
47829
|
+
function getWidgetIdsFromContent(content) {
|
|
47830
|
+
const widgets = [];
|
|
47831
|
+
const localWidgetRegex = getWidgetRegex();
|
|
47832
|
+
let match = localWidgetRegex.exec(content);
|
|
47833
|
+
while (match !== null) {
|
|
47834
|
+
widgets.push(match[1]);
|
|
47835
|
+
match = localWidgetRegex.exec(content);
|
|
47836
|
+
}
|
|
47837
|
+
return widgets;
|
|
47838
|
+
}
|
|
47839
|
+
|
|
47840
|
+
/**
|
|
47841
|
+
* Get a list of widget IDs from content,
|
|
47842
|
+
* but only for specific widget types
|
|
47843
|
+
*
|
|
47844
|
+
* @param {string} type the type of widget (ie "radio")
|
|
47845
|
+
* @param {string} content the string to parse
|
|
47846
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47847
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47848
|
+
*/
|
|
47849
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47850
|
+
const rv = [];
|
|
47851
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47852
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
47853
|
+
const widget = widgetMap[widgetId];
|
|
47854
|
+
if (widget?.type === type) {
|
|
47855
|
+
rv.push(widgetId);
|
|
47856
|
+
}
|
|
47857
|
+
});
|
|
47858
|
+
return rv;
|
|
47859
|
+
}
|
|
47860
|
+
|
|
47861
|
+
/**
|
|
47862
|
+
* Pull the widget map out of ItemData
|
|
47863
|
+
*
|
|
47864
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47865
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47866
|
+
*/
|
|
47867
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
47868
|
+
return itemData.question.widgets;
|
|
47869
|
+
}
|
|
47870
|
+
|
|
47871
|
+
/**
|
|
47872
|
+
* Get the widget information from a WidgetMap
|
|
47873
|
+
*
|
|
47874
|
+
* @param {string} widgetId the ID of the widget
|
|
47875
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47876
|
+
*/
|
|
47877
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47878
|
+
return widgetMap[widgetId] ?? null;
|
|
47879
|
+
}
|
|
47880
|
+
|
|
47881
|
+
/**
|
|
47882
|
+
* Get select widgets from a widget map.
|
|
47883
|
+
* Useful for multi-items that needs to split
|
|
47884
|
+
* one PerseusItem into several
|
|
47885
|
+
*
|
|
47886
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47887
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47888
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47889
|
+
*/
|
|
47890
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47891
|
+
const widgets = {};
|
|
47892
|
+
widgetIds.forEach(widgetId => {
|
|
47893
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47894
|
+
if (widget) {
|
|
47895
|
+
widgets[widgetId] = widget;
|
|
47896
|
+
}
|
|
47897
|
+
});
|
|
47898
|
+
return widgets;
|
|
47899
|
+
}
|
|
47900
|
+
|
|
48063
47901
|
/**
|
|
48064
47902
|
* This function extracts the answers from the widgets.
|
|
48065
47903
|
*
|
|
@@ -50061,8 +49899,6 @@ exports.buildMapper = buildMapper;
|
|
|
50061
49899
|
exports.components = components;
|
|
50062
49900
|
exports.containerSizeClass = containerSizeClass;
|
|
50063
49901
|
exports.contentHasWidgetType = contentHasWidgetType;
|
|
50064
|
-
exports.conversionRequired = conversionRequired;
|
|
50065
|
-
exports.convertDeprecatedWidgets = convertDeprecatedWidgets;
|
|
50066
49902
|
exports.convertWidgetNameToEnum = convertWidgetNameToEnum;
|
|
50067
49903
|
exports.displaySigFigs = displaySigFigs;
|
|
50068
49904
|
exports.generateTestCategorizerWidget = generateTestCategorizerWidget;
|
|
@@ -50109,12 +49945,12 @@ exports.libVersion = libVersion;
|
|
|
50109
49945
|
exports.lockedFigureColors = lockedFigureColors;
|
|
50110
49946
|
exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
50111
49947
|
exports.makeSafeUrl = makeSafeUrl;
|
|
49948
|
+
exports.mathOnlyParser = mathOnlyParser;
|
|
50112
49949
|
exports.mediaQueries = mediaQueries;
|
|
50113
49950
|
exports.parsePerseusItem = parsePerseusItem;
|
|
50114
49951
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
50115
49952
|
exports.preprocessTex = preprocessTex;
|
|
50116
49953
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
50117
|
-
exports.scorePerseusItem = scorePerseusItem;
|
|
50118
49954
|
exports.shapes = shapes;
|
|
50119
49955
|
exports.shouldHaveIndividualAnswer = shouldHaveIndividualAnswer;
|
|
50120
49956
|
exports.traverse = traverse;
|