@khanacademy/perseus 42.0.3 → 43.0.1
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 +536 -723
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +11 -3
- 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 +513 -703
- 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 +11 -3
- 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 +11 -1
- 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/es/index.js
CHANGED
|
@@ -22,8 +22,7 @@ import PropTypes from 'prop-types';
|
|
|
22
22
|
import { number as number$1, point, vector as vector$1, line } from '@khanacademy/kmath';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import SimpleMarkdown
|
|
26
|
-
import * as SimpleMarkdown from '@khanacademy/pure-markdown';
|
|
25
|
+
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
27
26
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
28
27
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
29
28
|
import Button from '@khanacademy/wonder-blocks-button';
|
|
@@ -33,7 +32,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
33
32
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
34
33
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
35
34
|
import debounce$2 from 'lodash.debounce';
|
|
36
|
-
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Mafs } from 'mafs';
|
|
35
|
+
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
|
|
37
36
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
38
37
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
39
38
|
import { useDrag } from '@use-gesture/react';
|
|
@@ -240,6 +239,8 @@ const mockStrings = {
|
|
|
240
239
|
addPoint: "Add Point",
|
|
241
240
|
removePoint: "Remove Point",
|
|
242
241
|
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
242
|
+
closePolygon: "Close shape",
|
|
243
|
+
openPolygon: "Re-open shape",
|
|
243
244
|
srPointAtCoordinates: ({
|
|
244
245
|
num,
|
|
245
246
|
x,
|
|
@@ -370,7 +371,10 @@ class InnerMathInput extends React.Component {
|
|
|
370
371
|
locale
|
|
371
372
|
} = this.context;
|
|
372
373
|
// Initialize MathQuill.MathField instance
|
|
373
|
-
this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
374
|
+
this.__mathField = createMathField(this.__mathFieldWrapperRef, locale, this.props.mathInputStrings,
|
|
375
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
376
|
+
// @ts-expect-error: Type 'EditableMathQuill' is not assignable to type 'MathFieldInterface'.
|
|
377
|
+
baseConfig => _extends({}, baseConfig, {
|
|
374
378
|
handlers: {
|
|
375
379
|
edit: debounce(mathField => {
|
|
376
380
|
// This handler is guaranteed to be called on change, but
|
|
@@ -2134,6 +2138,24 @@ const KhanAnswerTypes = {
|
|
|
2134
2138
|
}
|
|
2135
2139
|
};
|
|
2136
2140
|
|
|
2141
|
+
/**
|
|
2142
|
+
* Checks user input from the expression widget to see if it is scorable.
|
|
2143
|
+
*
|
|
2144
|
+
* Note: Most of the expression widget's validation requires the Rubric because
|
|
2145
|
+
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
2146
|
+
*
|
|
2147
|
+
* @see `scoreExpression()` for more details.
|
|
2148
|
+
*/
|
|
2149
|
+
function validateExpression(userInput) {
|
|
2150
|
+
if (userInput === "") {
|
|
2151
|
+
return {
|
|
2152
|
+
type: "invalid",
|
|
2153
|
+
message: null
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2137
2159
|
/* Content creators input a list of answers which are matched from top to
|
|
2138
2160
|
* bottom. The intent is that they can include spcific solutions which should
|
|
2139
2161
|
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
@@ -2153,6 +2175,10 @@ const KhanAnswerTypes = {
|
|
|
2153
2175
|
* - Otherwise, pass through the resulting points and message.
|
|
2154
2176
|
*/
|
|
2155
2177
|
function scoreExpression(userInput, rubric, strings, locale) {
|
|
2178
|
+
const validationError = validateExpression(userInput);
|
|
2179
|
+
if (validationError) {
|
|
2180
|
+
return validationError;
|
|
2181
|
+
}
|
|
2156
2182
|
const options = _.clone(rubric);
|
|
2157
2183
|
_.extend(options, {
|
|
2158
2184
|
decimal_separator: getDecimalSeparator(locale)
|
|
@@ -2405,11 +2431,16 @@ class Expression extends React.Component {
|
|
|
2405
2431
|
getPromptJSON() {
|
|
2406
2432
|
return getPromptJSON$t(this.props, this.getUserInput());
|
|
2407
2433
|
}
|
|
2434
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2435
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2408
2436
|
focusInputPath(inputPath) {
|
|
2409
2437
|
// eslint-disable-next-line react/no-string-refs
|
|
2410
2438
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
2411
2439
|
this.refs.input.focus();
|
|
2412
2440
|
}
|
|
2441
|
+
|
|
2442
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2443
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'InputPath'.
|
|
2413
2444
|
blurInputPath(inputPath) {
|
|
2414
2445
|
var _this$refs$input;
|
|
2415
2446
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -2592,7 +2623,7 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
2592
2623
|
times: widgetOptions.times
|
|
2593
2624
|
};
|
|
2594
2625
|
};
|
|
2595
|
-
const propUpgrades$
|
|
2626
|
+
const propUpgrades$2 = {
|
|
2596
2627
|
/* c8 ignore next */
|
|
2597
2628
|
"1": v0props => ({
|
|
2598
2629
|
times: v0props.times,
|
|
@@ -2652,10 +2683,14 @@ var Expression$1 = {
|
|
|
2652
2683
|
major: 1,
|
|
2653
2684
|
minor: 0
|
|
2654
2685
|
},
|
|
2655
|
-
propUpgrades: propUpgrades$
|
|
2686
|
+
propUpgrades: propUpgrades$2,
|
|
2656
2687
|
// For use by the editor
|
|
2657
2688
|
isLintable: true,
|
|
2689
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2690
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2658
2691
|
scorer: scoreExpression,
|
|
2692
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2693
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2659
2694
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2660
2695
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
2661
2696
|
if (correctAnswers.length === 0) {
|
|
@@ -2738,6 +2773,8 @@ const nestedMap$2 = function nestedMap(children, func, context) {
|
|
|
2738
2773
|
return nestedMap(child, func);
|
|
2739
2774
|
});
|
|
2740
2775
|
}
|
|
2776
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2777
|
+
// @ts-expect-error: T | ReadonlyArray<T> is not assignable to T
|
|
2741
2778
|
return func.call(context, children);
|
|
2742
2779
|
};
|
|
2743
2780
|
|
|
@@ -14489,7 +14526,9 @@ const constraints$2 = {
|
|
|
14489
14526
|
});
|
|
14490
14527
|
|
|
14491
14528
|
// bound the delta by the calculated bounds
|
|
14492
|
-
const boundedDelta = _.reduce(deltaBounds,
|
|
14529
|
+
const boundedDelta = _.reduce(deltaBounds,
|
|
14530
|
+
// @ts-expect-error: Type 'number[]' is not assignable to type 'Coord'.
|
|
14531
|
+
function (delta, bound) {
|
|
14493
14532
|
const lower = bound[0];
|
|
14494
14533
|
const upper = bound[1];
|
|
14495
14534
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
@@ -15020,6 +15059,7 @@ const constraints$1 = {
|
|
|
15020
15059
|
};
|
|
15021
15060
|
},
|
|
15022
15061
|
snap: function (snap) {
|
|
15062
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15023
15063
|
return function (coord) {
|
|
15024
15064
|
if (snap === null) {
|
|
15025
15065
|
// TODO(benchristel), NOTE(kevinb): this should probably return
|
|
@@ -15042,6 +15082,8 @@ const constraints$1 = {
|
|
|
15042
15082
|
return optionalPaddingPx;
|
|
15043
15083
|
}
|
|
15044
15084
|
})();
|
|
15085
|
+
|
|
15086
|
+
// @ts-expect-error: Type 'boolean | Coord' is not assignable to type 'Coord'.
|
|
15045
15087
|
return function (coord, prev, options) {
|
|
15046
15088
|
const graphie = this.graphie;
|
|
15047
15089
|
range = range || graphie.range;
|
|
@@ -15932,15 +15974,15 @@ const constraints = {
|
|
|
15932
15974
|
});
|
|
15933
15975
|
|
|
15934
15976
|
// bound the delta by the calculated bounds
|
|
15935
|
-
const boundedDelta = _.reduce(deltaBounds,
|
|
15977
|
+
const boundedDelta = _.reduce(deltaBounds,
|
|
15978
|
+
// @ts-expect-error - TS2345 - Type 'number[]' is not assignable to type 'Coord'.
|
|
15979
|
+
function (delta, bound) {
|
|
15936
15980
|
const lower = bound[0];
|
|
15937
15981
|
const upper = bound[1];
|
|
15938
15982
|
const deltaX = Math.max(lower[0], Math.min(upper[0], delta[0]));
|
|
15939
15983
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
15940
15984
|
return [deltaX, deltaY];
|
|
15941
15985
|
}, delta);
|
|
15942
|
-
|
|
15943
|
-
// @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'Coord'.
|
|
15944
15986
|
return vector$1.add(prevCoord, boundedDelta);
|
|
15945
15987
|
};
|
|
15946
15988
|
}
|
|
@@ -16826,6 +16868,7 @@ class ImageLoader extends React.Component {
|
|
|
16826
16868
|
|
|
16827
16869
|
this.img = new Image();
|
|
16828
16870
|
this.img.onload = this.handleLoad;
|
|
16871
|
+
// @ts-expect-error: Type 'string | Event' is not assignable to type 'Event'.
|
|
16829
16872
|
this.img.onerror = this.handleError;
|
|
16830
16873
|
this.img.src = this.props.src;
|
|
16831
16874
|
};
|
|
@@ -17916,6 +17959,7 @@ class InteractionTracker {
|
|
|
17916
17959
|
this.widgetType = widgetType;
|
|
17917
17960
|
this.widgetID = widgetID;
|
|
17918
17961
|
this.setting = setting;
|
|
17962
|
+
// @ts-expect-error: Type 'T | undefined' is not assignable to type 'T'.
|
|
17919
17963
|
this.track = this._track;
|
|
17920
17964
|
}
|
|
17921
17965
|
}
|
|
@@ -17926,7 +17970,7 @@ class InteractionTracker {
|
|
|
17926
17970
|
*/
|
|
17927
17971
|
const arrayRules = {
|
|
17928
17972
|
fence: {
|
|
17929
|
-
match: SimpleMarkdown
|
|
17973
|
+
match: SimpleMarkdown.defaultRules.fence.match,
|
|
17930
17974
|
order: 1,
|
|
17931
17975
|
parse: (capture, state, parse) => ({
|
|
17932
17976
|
type: "codeBlock",
|
|
@@ -17935,14 +17979,14 @@ const arrayRules = {
|
|
|
17935
17979
|
})
|
|
17936
17980
|
},
|
|
17937
17981
|
paragraph: {
|
|
17938
|
-
match: SimpleMarkdown
|
|
17982
|
+
match: SimpleMarkdown.defaultRules.paragraph.match,
|
|
17939
17983
|
order: 2,
|
|
17940
17984
|
parse: (capture, state, parse) => ({
|
|
17941
17985
|
content: capture[1]
|
|
17942
17986
|
})
|
|
17943
17987
|
}
|
|
17944
17988
|
};
|
|
17945
|
-
const builtArrayParser = SimpleMarkdown
|
|
17989
|
+
const builtArrayParser = SimpleMarkdown.parserFor(arrayRules);
|
|
17946
17990
|
|
|
17947
17991
|
// This should just return an array of strings! magick!
|
|
17948
17992
|
const parseToArray = source => {
|
|
@@ -18367,7 +18411,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18367
18411
|
// iOS 14.4, in order for VoiceOver to work.
|
|
18368
18412
|
table: _extends({}, pureMarkdownRules.table, {
|
|
18369
18413
|
react: function (node, output, state) {
|
|
18370
|
-
const table = SimpleMarkdown
|
|
18414
|
+
const table = SimpleMarkdown.defaultRules.table.react(node, output, state);
|
|
18371
18415
|
if (!state.isMobile) {
|
|
18372
18416
|
return table;
|
|
18373
18417
|
}
|
|
@@ -18416,7 +18460,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18416
18460
|
contents = rules$1.lint.react(node.table, output, state);
|
|
18417
18461
|
}
|
|
18418
18462
|
} else {
|
|
18419
|
-
const tableOutput = SimpleMarkdown
|
|
18463
|
+
const tableOutput = SimpleMarkdown.defaultRules.table.react(node.table, output, state);
|
|
18420
18464
|
const caption = /*#__PURE__*/React.createElement("caption", {
|
|
18421
18465
|
key: "caption",
|
|
18422
18466
|
className: "perseus-table-title"
|
|
@@ -18486,7 +18530,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18486
18530
|
// window and handle reverse tabnapping phishing attacks
|
|
18487
18531
|
link: _extends({}, pureMarkdownRules.link, {
|
|
18488
18532
|
react: function (node, output, state) {
|
|
18489
|
-
const link = SimpleMarkdown
|
|
18533
|
+
const link = SimpleMarkdown.defaultRules.link.react(node, output, state);
|
|
18490
18534
|
const href = link.props.href;
|
|
18491
18535
|
let rel = null;
|
|
18492
18536
|
// The href prop should always be here, but just in case somehow the article's
|
|
@@ -18519,7 +18563,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
|
|
|
18519
18563
|
className: "perseus-markdown-alt perseus-sr-only"
|
|
18520
18564
|
}, output(node.content, state));
|
|
18521
18565
|
}
|
|
18522
|
-
return SimpleMarkdown
|
|
18566
|
+
return SimpleMarkdown.defaultRules.codeBlock.react(node, output, state);
|
|
18523
18567
|
}
|
|
18524
18568
|
}),
|
|
18525
18569
|
// The lint rule never actually matches anything.
|
|
@@ -18561,7 +18605,7 @@ const inlineNodeTypes = {
|
|
|
18561
18605
|
};
|
|
18562
18606
|
|
|
18563
18607
|
// @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
|
|
18564
|
-
const builtParser$1 = SimpleMarkdown
|
|
18608
|
+
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
18565
18609
|
const parse$3 = (source, state) => {
|
|
18566
18610
|
const paragraphedSource = source + "\n\n";
|
|
18567
18611
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
@@ -18654,10 +18698,10 @@ var PerseusMarkdown = {
|
|
|
18654
18698
|
traverseContent: traverseContent,
|
|
18655
18699
|
parse: parse$3,
|
|
18656
18700
|
parseInline: inlineParser,
|
|
18657
|
-
reactFor: SimpleMarkdown
|
|
18658
|
-
ruleOutput: SimpleMarkdown
|
|
18659
|
-
basicOutput: SimpleMarkdown
|
|
18660
|
-
sanitizeUrl: SimpleMarkdown
|
|
18701
|
+
reactFor: SimpleMarkdown.reactFor,
|
|
18702
|
+
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
18703
|
+
basicOutput: SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules$1, "react")),
|
|
18704
|
+
sanitizeUrl: SimpleMarkdown.sanitizeUrl
|
|
18661
18705
|
};
|
|
18662
18706
|
|
|
18663
18707
|
class QuestionParagraph extends React.Component {
|
|
@@ -18674,337 +18718,6 @@ class QuestionParagraph extends React.Component {
|
|
|
18674
18718
|
}
|
|
18675
18719
|
}
|
|
18676
18720
|
|
|
18677
|
-
// Methods to convert input-number widgets to numeric-input widgets
|
|
18678
|
-
|
|
18679
|
-
const inputNumberToNumericInput = json => {
|
|
18680
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
18681
|
-
// so that we can ensure we update the content and widgets accordingly
|
|
18682
|
-
const renameMap = getInputNumberRenameMap(json);
|
|
18683
|
-
|
|
18684
|
-
// Then we can use this to update the JSON
|
|
18685
|
-
return convertInputNumberJson(json, renameMap);
|
|
18686
|
-
};
|
|
18687
|
-
|
|
18688
|
-
// We need to be able to run this code recursively in order to convert nested input-number widgets
|
|
18689
|
-
const convertInputNumberJson = (json, renameMap) => {
|
|
18690
|
-
const updatedContent = convertDeprecatedWidgetsInContent(json, renameMap);
|
|
18691
|
-
const updatedWidgets = convertInputNumberWidgetOptions(json, renameMap);
|
|
18692
|
-
const modernizedJson = _extends({}, json, {
|
|
18693
|
-
content: updatedContent,
|
|
18694
|
-
widgets: updatedWidgets
|
|
18695
|
-
});
|
|
18696
|
-
return modernizedJson;
|
|
18697
|
-
};
|
|
18698
|
-
|
|
18699
|
-
// Convert the input-number json in the widgets of a PerseusRenderer object
|
|
18700
|
-
const convertInputNumberWidgetOptions = (json, renameMap) => {
|
|
18701
|
-
const widgets = _extends({}, json.widgets);
|
|
18702
|
-
// The question.widgets is a dictionary map of widgets, so we need to loop through in order to convert input-number to numeric-input
|
|
18703
|
-
// which can exist as both the key or as a value on widget.type.
|
|
18704
|
-
for (const key of Object.keys(widgets)) {
|
|
18705
|
-
// Loop through the keys of the widgets dictionary
|
|
18706
|
-
if (widgets[key].options.widgets) {
|
|
18707
|
-
widgets[key].options = _extends({}, inputNumberToNumericInput(widgets[key].options));
|
|
18708
|
-
}
|
|
18709
|
-
// Check if the widget is an input-number
|
|
18710
|
-
if (widgets[key].type === "input-number") {
|
|
18711
|
-
// If the answerType is not number or percent, we need to provide
|
|
18712
|
-
// the answer form for the numeric-input widget
|
|
18713
|
-
const provideAnswerForm = widgets[key].options.answerType !== "number" && widgets[key].options.answerType !== "percent";
|
|
18714
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
18715
|
-
const mathFormat = widgets[key].options.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
18716
|
-
: widgets[key].options.answerType; // Otherwise, we can use the answerType directly
|
|
18717
|
-
|
|
18718
|
-
// We need to update the answers prop to match the numeric-input widget format
|
|
18719
|
-
const answers = [{
|
|
18720
|
-
value: widgets[key].options.value,
|
|
18721
|
-
simplify: widgets[key].options.simplify,
|
|
18722
|
-
// Input Number doesn't have a strict prop, so we default to false
|
|
18723
|
-
strict: false,
|
|
18724
|
-
// We only want to set maxError if the inexact prop is true
|
|
18725
|
-
maxError: widgets[key].options.inexact ? widgets[key].options.maxError : 0,
|
|
18726
|
-
status: "correct",
|
|
18727
|
-
// Input-number only allows correct answers
|
|
18728
|
-
message: ""
|
|
18729
|
-
}];
|
|
18730
|
-
|
|
18731
|
-
// Add the required answerForms if provided/applicable
|
|
18732
|
-
if (provideAnswerForm) {
|
|
18733
|
-
answers[0].answerForms = [mathFormat];
|
|
18734
|
-
}
|
|
18735
|
-
|
|
18736
|
-
// Update the options prop to match the numeric-input widget format
|
|
18737
|
-
const upgradedWidgetOptions = {
|
|
18738
|
-
answers,
|
|
18739
|
-
size: widgets[key].options.size,
|
|
18740
|
-
coefficient: false,
|
|
18741
|
-
// input-number doesn't have a coefficient prop
|
|
18742
|
-
labelText: "",
|
|
18743
|
-
// input-number doesn't have a labelText prop
|
|
18744
|
-
static: false,
|
|
18745
|
-
// static is always false for numeric-input
|
|
18746
|
-
rightAlign: widgets[key].options.rightAlign || false
|
|
18747
|
-
};
|
|
18748
|
-
const upgradedWidget = {
|
|
18749
|
-
options: upgradedWidgetOptions,
|
|
18750
|
-
type: "numeric-input"
|
|
18751
|
-
};
|
|
18752
|
-
const newWidgetName = renameMap[key];
|
|
18753
|
-
// Create the new key entry
|
|
18754
|
-
widgets[newWidgetName] = upgradedWidget;
|
|
18755
|
-
|
|
18756
|
-
// We need to delete the old widget key
|
|
18757
|
-
delete widgets[key];
|
|
18758
|
-
}
|
|
18759
|
-
}
|
|
18760
|
-
return widgets;
|
|
18761
|
-
};
|
|
18762
|
-
|
|
18763
|
-
// Convert the deprecated widget refs in the content string
|
|
18764
|
-
// of a PerseusRenderer object to their renamed equivalents
|
|
18765
|
-
const convertDeprecatedWidgetsInContent = (json, renameMap) => {
|
|
18766
|
-
return Object.keys(renameMap).reduce((newContent, oldKey) => {
|
|
18767
|
-
const newKey = renameMap[oldKey];
|
|
18768
|
-
return newKey ? newContent.replace(oldKey, newKey) : newContent;
|
|
18769
|
-
}, json.content);
|
|
18770
|
-
};
|
|
18771
|
-
|
|
18772
|
-
// Create a map of the old input-number keys to the new numeric-input keys
|
|
18773
|
-
const getInputNumberRenameMap = json => {
|
|
18774
|
-
const numericRegex = /(?<=\[\[\u2603 )(numeric-input \d+)(?=\]\])/g;
|
|
18775
|
-
const inputNumberRegex = /(?<=\[\[\u2603 )(input-number \d+)(?=\]\])/g;
|
|
18776
|
-
|
|
18777
|
-
// Get all the content strings within the json, which might be nested within widgets
|
|
18778
|
-
const allContentStrings = json.content;
|
|
18779
|
-
|
|
18780
|
-
// Loop through the content strings to get all the input-number widgets
|
|
18781
|
-
const renameMap = {};
|
|
18782
|
-
const inputNumberMatches = [...(allContentStrings.match(inputNumberRegex) || [])];
|
|
18783
|
-
|
|
18784
|
-
// We want to count any pre-existing numeric-input widgets
|
|
18785
|
-
// so that we can start the new ids at the next number
|
|
18786
|
-
const numericMatches = [...(allContentStrings.match(numericRegex) || [])];
|
|
18787
|
-
let currentNumericCount = numericMatches.reduce((count, match) => {
|
|
18788
|
-
const id = parseInt(match.split(" ")[1], 10);
|
|
18789
|
-
return id >= count ? id + 1 : count; // We want to start at the next highest number
|
|
18790
|
-
}, 1);
|
|
18791
|
-
|
|
18792
|
-
// Now that we have all the necessary information, we can create the renameMap
|
|
18793
|
-
for (const match of inputNumberMatches) {
|
|
18794
|
-
const oldKey = match;
|
|
18795
|
-
const newKey = `numeric-input ${currentNumericCount}`;
|
|
18796
|
-
renameMap[oldKey] = newKey;
|
|
18797
|
-
currentNumericCount++;
|
|
18798
|
-
}
|
|
18799
|
-
return renameMap;
|
|
18800
|
-
};
|
|
18801
|
-
|
|
18802
|
-
// Convert the user input data keys from input-number to numeric-input
|
|
18803
|
-
const convertUserInputNumberData = (userInputMap, renameMap) => {
|
|
18804
|
-
const updatedUserInputMap = _extends({}, userInputMap);
|
|
18805
|
-
for (const key of Object.keys(userInputMap)) {
|
|
18806
|
-
if (key.includes("input-number")) {
|
|
18807
|
-
const updatedKey = renameMap[key];
|
|
18808
|
-
updatedUserInputMap[updatedKey] = userInputMap[key];
|
|
18809
|
-
delete updatedUserInputMap[key];
|
|
18810
|
-
}
|
|
18811
|
-
}
|
|
18812
|
-
return updatedUserInputMap;
|
|
18813
|
-
};
|
|
18814
|
-
|
|
18815
|
-
// Used to convert InputNumber widgets to NumericInput widgets for scoring
|
|
18816
|
-
const convertInputNumberForScoring = (rubric, userInputMap) => {
|
|
18817
|
-
// First we need to create a map of the old input-number keys to the new numeric-input keys
|
|
18818
|
-
// so that we can ensure we update the content, widgets, AND userInput accordingly
|
|
18819
|
-
const renameMap = getInputNumberRenameMap(rubric);
|
|
18820
|
-
const convertedRubric = convertInputNumberJson(rubric, renameMap);
|
|
18821
|
-
const convertedUserData = convertUserInputNumberData(userInputMap, renameMap);
|
|
18822
|
-
return {
|
|
18823
|
-
convertedRubric,
|
|
18824
|
-
convertedUserData
|
|
18825
|
-
};
|
|
18826
|
-
};
|
|
18827
|
-
|
|
18828
|
-
const widgetRegExes = [/input-number \d+/]; // We can add more regexes here in the future
|
|
18829
|
-
|
|
18830
|
-
// This utils in this file are used to modernize our Perseus JSON structure,
|
|
18831
|
-
// so that we can convert deprecated widgets to their modern equivalents when
|
|
18832
|
-
// content creators use the Editor Page to update content containing these widgets.
|
|
18833
|
-
// Currently, we're only converting input-number to numeric-input,
|
|
18834
|
-
// but we can add more conversions here in the future.
|
|
18835
|
-
|
|
18836
|
-
// Modernize the json content of a PerseusRenderer object
|
|
18837
|
-
// by converting deprecated widgets to their modern equivalents
|
|
18838
|
-
const convertDeprecatedWidgets = json => {
|
|
18839
|
-
// Currently we're only converting input-number to numeric-input,
|
|
18840
|
-
// But we can add more conversions here in the future
|
|
18841
|
-
return inputNumberToNumericInput(json);
|
|
18842
|
-
};
|
|
18843
|
-
const conversionRequired = json => {
|
|
18844
|
-
// If there's no content, then there's no conversion required
|
|
18845
|
-
if (!json.content) {
|
|
18846
|
-
return false;
|
|
18847
|
-
}
|
|
18848
|
-
// Check the content string for any top-level input-number widgets
|
|
18849
|
-
if (widgetRegExes.some(regex => regex.test(json.content))) {
|
|
18850
|
-
return true;
|
|
18851
|
-
}
|
|
18852
|
-
|
|
18853
|
-
// If there's no deprecated widget in the top-level, then check for any nested widgets
|
|
18854
|
-
for (const key of Object.keys(json.widgets)) {
|
|
18855
|
-
if (json.widgets[key].options.widgets) {
|
|
18856
|
-
const nestedJson = json.widgets[key].options;
|
|
18857
|
-
if (conversionRequired(nestedJson)) {
|
|
18858
|
-
return true;
|
|
18859
|
-
}
|
|
18860
|
-
}
|
|
18861
|
-
}
|
|
18862
|
-
return false;
|
|
18863
|
-
};
|
|
18864
|
-
|
|
18865
|
-
/**
|
|
18866
|
-
* Convert the deprecated widgets in the rubric and user data to their
|
|
18867
|
-
* modern equivalents for scoring. These need to be updated together
|
|
18868
|
-
* in order to ensure that the widgetKeys match between the rubric and user data.
|
|
18869
|
-
*/
|
|
18870
|
-
const convertDeprecatedWidgetsForScoring = (rubric, userInputMap) => {
|
|
18871
|
-
return convertInputNumberForScoring(rubric, userInputMap);
|
|
18872
|
-
};
|
|
18873
|
-
|
|
18874
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18875
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18876
|
-
|
|
18877
|
-
/**
|
|
18878
|
-
* Regex for widget placeholders in a string.
|
|
18879
|
-
*
|
|
18880
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
18881
|
-
* Second capture group is the widget type (ex. "radio)
|
|
18882
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18883
|
-
*/
|
|
18884
|
-
function getWidgetRegex() {
|
|
18885
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18886
|
-
}
|
|
18887
|
-
|
|
18888
|
-
/**
|
|
18889
|
-
* Add a widget placeholder using the widget ID.
|
|
18890
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18891
|
-
*
|
|
18892
|
-
* @param {string} id
|
|
18893
|
-
* @returns {string}
|
|
18894
|
-
*/
|
|
18895
|
-
function addWidget(id) {
|
|
18896
|
-
return `[[☃ ${id}]]`;
|
|
18897
|
-
}
|
|
18898
|
-
|
|
18899
|
-
/**
|
|
18900
|
-
* Get a widget type by a widget's ID
|
|
18901
|
-
* (please don't derive type from ID)
|
|
18902
|
-
*
|
|
18903
|
-
* @param {string} widgetId the ID of the widget
|
|
18904
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18905
|
-
* @returns {string} the widget type (ie "radio")
|
|
18906
|
-
*/
|
|
18907
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18908
|
-
var _widget$type;
|
|
18909
|
-
const widget = widgetMap[WidgetId];
|
|
18910
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18911
|
-
}
|
|
18912
|
-
|
|
18913
|
-
/**
|
|
18914
|
-
* Does the content have a specific type of widget?
|
|
18915
|
-
*
|
|
18916
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
18917
|
-
* @param {string} content the string to search through
|
|
18918
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18919
|
-
* @returns {boolean} if the content includes the widget type
|
|
18920
|
-
*/
|
|
18921
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
18922
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18923
|
-
}
|
|
18924
|
-
|
|
18925
|
-
/**
|
|
18926
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18927
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18928
|
-
*
|
|
18929
|
-
* Content should contain Perseus widget placeholders,
|
|
18930
|
-
* which look like: '[[☃ radio 1]]'.
|
|
18931
|
-
*
|
|
18932
|
-
* @param {string} content
|
|
18933
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
18934
|
-
*/
|
|
18935
|
-
function getWidgetIdsFromContent(content) {
|
|
18936
|
-
const widgets = [];
|
|
18937
|
-
const localWidgetRegex = getWidgetRegex();
|
|
18938
|
-
let match = localWidgetRegex.exec(content);
|
|
18939
|
-
while (match !== null) {
|
|
18940
|
-
widgets.push(match[1]);
|
|
18941
|
-
match = localWidgetRegex.exec(content);
|
|
18942
|
-
}
|
|
18943
|
-
return widgets;
|
|
18944
|
-
}
|
|
18945
|
-
|
|
18946
|
-
/**
|
|
18947
|
-
* Get a list of widget IDs from content,
|
|
18948
|
-
* but only for specific widget types
|
|
18949
|
-
*
|
|
18950
|
-
* @param {string} type the type of widget (ie "radio")
|
|
18951
|
-
* @param {string} content the string to parse
|
|
18952
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18953
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18954
|
-
*/
|
|
18955
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18956
|
-
const rv = [];
|
|
18957
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18958
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
18959
|
-
const widget = widgetMap[widgetId];
|
|
18960
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18961
|
-
rv.push(widgetId);
|
|
18962
|
-
}
|
|
18963
|
-
});
|
|
18964
|
-
return rv;
|
|
18965
|
-
}
|
|
18966
|
-
|
|
18967
|
-
/**
|
|
18968
|
-
* Pull the widget map out of ItemData
|
|
18969
|
-
*
|
|
18970
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18971
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18972
|
-
*/
|
|
18973
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
18974
|
-
return itemData.question.widgets;
|
|
18975
|
-
}
|
|
18976
|
-
|
|
18977
|
-
/**
|
|
18978
|
-
* Get the widget information from a WidgetMap
|
|
18979
|
-
*
|
|
18980
|
-
* @param {string} widgetId the ID of the widget
|
|
18981
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18982
|
-
*/
|
|
18983
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18984
|
-
var _widgetMap$widgetId;
|
|
18985
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18986
|
-
}
|
|
18987
|
-
|
|
18988
|
-
/**
|
|
18989
|
-
* Get select widgets from a widget map.
|
|
18990
|
-
* Useful for multi-items that needs to split
|
|
18991
|
-
* one PerseusItem into several
|
|
18992
|
-
*
|
|
18993
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18994
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18995
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18996
|
-
*/
|
|
18997
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18998
|
-
const widgets = {};
|
|
18999
|
-
widgetIds.forEach(widgetId => {
|
|
19000
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
19001
|
-
if (widget) {
|
|
19002
|
-
widgets[widgetId] = widget;
|
|
19003
|
-
}
|
|
19004
|
-
});
|
|
19005
|
-
return widgets;
|
|
19006
|
-
}
|
|
19007
|
-
|
|
19008
18721
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
19009
18722
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
19010
18723
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19055,9 +18768,6 @@ const replaceDeprecatedWidgets = () => {
|
|
|
19055
18768
|
replaceWidget("sequence", "deprecated-standin");
|
|
19056
18769
|
replaceWidget("simulator", "deprecated-standin");
|
|
19057
18770
|
replaceWidget("unit-input", "deprecated-standin");
|
|
19058
|
-
|
|
19059
|
-
// Input-Number is a special case as it is being replaced by Numeric-Input
|
|
19060
|
-
replaceWidget("input-number", "numeric-input");
|
|
19061
18771
|
};
|
|
19062
18772
|
const registerEditors = editorsToRegister => {
|
|
19063
18773
|
editorsToRegister.forEach(editor => {
|
|
@@ -19092,11 +18802,6 @@ const replaceDeprecatedEditors = () => {
|
|
|
19092
18802
|
replaceEditor("sequence", "deprecated-standin");
|
|
19093
18803
|
replaceEditor("simulator", "deprecated-standin");
|
|
19094
18804
|
replaceEditor("unit-input", "deprecated-standin");
|
|
19095
|
-
|
|
19096
|
-
// We're replacing the input-number editor with the numeric-input editor here,
|
|
19097
|
-
// but we're also modifying the JSON content to convert input-number into the
|
|
19098
|
-
// numeric-input format over in editor-page.tsx on line 92.
|
|
19099
|
-
replaceEditor("input-number", "numeric-input");
|
|
19100
18805
|
};
|
|
19101
18806
|
const getWidget = name => {
|
|
19102
18807
|
var _widgets$name;
|
|
@@ -19147,8 +18852,10 @@ const getVersionVector = () => {
|
|
|
19147
18852
|
const getPublicWidgets = () => {
|
|
19148
18853
|
// TODO(alex): Update underscore.js so that _.pick can take a function.
|
|
19149
18854
|
// @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.
|
|
19150
|
-
return _.pick(widgets,
|
|
19151
|
-
|
|
18855
|
+
return _.pick(widgets,
|
|
18856
|
+
// @ts-expect-error - TS2345 - Argument of type '(name: string) => boolean | undefined' is not assignable to parameter of type 'Iteratee<string[], boolean, string>'.
|
|
18857
|
+
_.reject(_.keys(widgets), function (name) {
|
|
18858
|
+
return widgets[name].hidden;
|
|
19152
18859
|
}));
|
|
19153
18860
|
};
|
|
19154
18861
|
const isAccessible = widgetInfo => {
|
|
@@ -19466,34 +19173,6 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19466
19173
|
}
|
|
19467
19174
|
});
|
|
19468
19175
|
}
|
|
19469
|
-
|
|
19470
|
-
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19471
|
-
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19472
|
-
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19473
|
-
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19474
|
-
// need to be removed before we move this to the server
|
|
19475
|
-
strings, locale) {
|
|
19476
|
-
let convertedRenderData = perseusRenderData;
|
|
19477
|
-
let convertedUserInputMap = userInputMap;
|
|
19478
|
-
|
|
19479
|
-
// Check if the PerseusRenderer object contains any deprecated widgets that need to be converted
|
|
19480
|
-
const mustConvertData = conversionRequired(perseusRenderData);
|
|
19481
|
-
if (mustConvertData) {
|
|
19482
|
-
const {
|
|
19483
|
-
convertedRubric,
|
|
19484
|
-
convertedUserData
|
|
19485
|
-
} = convertDeprecatedWidgetsForScoring(perseusRenderData, userInputMap);
|
|
19486
|
-
convertedRenderData = convertedRubric;
|
|
19487
|
-
convertedUserInputMap = convertedUserData;
|
|
19488
|
-
}
|
|
19489
|
-
|
|
19490
|
-
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19491
|
-
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19492
|
-
// so this checks that the widgets are being used before scoring them
|
|
19493
|
-
const usedWidgetIds = getWidgetIdsFromContent(convertedRenderData.content);
|
|
19494
|
-
const scores = scoreWidgetsFunctional(convertedRenderData.widgets, usedWidgetIds, convertedUserInputMap, strings, locale);
|
|
19495
|
-
return Util.flattenScores(scores);
|
|
19496
|
-
}
|
|
19497
19176
|
function scoreWidgetsFunctional(widgets,
|
|
19498
19177
|
// This is a port of old code, I'm not sure why
|
|
19499
19178
|
// we need widgetIds vs the keys of the widgets object
|
|
@@ -19514,7 +19193,7 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19514
19193
|
const userInput = userInputMap[id];
|
|
19515
19194
|
const scorer = getWidgetScorer(widget.type);
|
|
19516
19195
|
if (widget.type === "group") {
|
|
19517
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19196
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, Object.keys(widget.options.widgets), userInputMap[id], strings, locale);
|
|
19518
19197
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19519
19198
|
} else if (scorer) {
|
|
19520
19199
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -20346,7 +20025,7 @@ class Renderer extends React.Component {
|
|
|
20346
20025
|
// Instead, we center them with css magic in styles.less
|
|
20347
20026
|
// /cry(aria)
|
|
20348
20027
|
this._foundTextNodes = true;
|
|
20349
|
-
if (this.widgetIds
|
|
20028
|
+
if (_.contains(this.widgetIds, node.id)) {
|
|
20350
20029
|
// We don't want to render a duplicate widget key/ref,
|
|
20351
20030
|
// as this causes problems with react (for obvious
|
|
20352
20031
|
// reasons). Instead we just notify the
|
|
@@ -20650,7 +20329,7 @@ class Renderer extends React.Component {
|
|
|
20650
20329
|
};
|
|
20651
20330
|
this.getInputPaths = () => {
|
|
20652
20331
|
const inputPaths = [];
|
|
20653
|
-
this.widgetIds
|
|
20332
|
+
_.each(this.widgetIds, widgetId => {
|
|
20654
20333
|
const widget = this.getWidgetInstance(widgetId);
|
|
20655
20334
|
if (widget && widget.getInputPaths) {
|
|
20656
20335
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20658,7 +20337,7 @@ class Renderer extends React.Component {
|
|
|
20658
20337
|
// Prefix paths with their widgetID and add to collective
|
|
20659
20338
|
// list of paths.
|
|
20660
20339
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20661
|
-
|
|
20340
|
+
_.each(widgetInputPaths, inputPath => {
|
|
20662
20341
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20663
20342
|
inputPaths.push(relativeInputPath);
|
|
20664
20343
|
});
|
|
@@ -20787,20 +20466,12 @@ class Renderer extends React.Component {
|
|
|
20787
20466
|
this.getWidgetIds = () => {
|
|
20788
20467
|
return this.widgetIds;
|
|
20789
20468
|
};
|
|
20790
|
-
/**
|
|
20791
|
-
* @deprecated use scorePerseusItem
|
|
20792
|
-
*/
|
|
20793
|
-
this.guessAndScore = () => {
|
|
20794
|
-
const totalGuess = this.getUserInput();
|
|
20795
|
-
const totalScore = this.score();
|
|
20796
|
-
return [totalGuess, totalScore];
|
|
20797
|
-
};
|
|
20798
20469
|
this.examples = () => {
|
|
20799
20470
|
const widgetIds = this.widgetIds;
|
|
20800
|
-
const examples =
|
|
20471
|
+
const examples = _.compact(_.map(widgetIds, widgetId => {
|
|
20801
20472
|
const widget = this.getWidgetInstance(widgetId);
|
|
20802
20473
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20803
|
-
})
|
|
20474
|
+
}));
|
|
20804
20475
|
|
|
20805
20476
|
// no widgets with examples
|
|
20806
20477
|
if (!examples.length) {
|
|
@@ -21055,15 +20726,27 @@ class Renderer extends React.Component {
|
|
|
21055
20726
|
}
|
|
21056
20727
|
|
|
21057
20728
|
/**
|
|
21058
|
-
*
|
|
21059
|
-
*
|
|
21060
|
-
*
|
|
20729
|
+
* Returns an object mapping from widget ID to perseus-style score.
|
|
20730
|
+
* The keys of this object are the values of the array returned
|
|
20731
|
+
* from `getWidgetIds`.
|
|
20732
|
+
*/
|
|
20733
|
+
scoreWidgets() {
|
|
20734
|
+
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20735
|
+
}
|
|
20736
|
+
|
|
20737
|
+
/**
|
|
20738
|
+
* Grades the content.
|
|
21061
20739
|
*/
|
|
21062
20740
|
score() {
|
|
21063
|
-
const scores =
|
|
20741
|
+
const scores = this.scoreWidgets();
|
|
21064
20742
|
const combinedScore = Util.flattenScores(scores);
|
|
21065
20743
|
return combinedScore;
|
|
21066
20744
|
}
|
|
20745
|
+
guessAndScore() {
|
|
20746
|
+
const totalGuess = this.getUserInput();
|
|
20747
|
+
const totalScore = this.score();
|
|
20748
|
+
return [totalGuess, totalScore];
|
|
20749
|
+
}
|
|
21067
20750
|
render() {
|
|
21068
20751
|
const apiOptions = this.getApiOptions();
|
|
21069
20752
|
if (this.reuseMarkdown) {
|
|
@@ -22073,11 +21756,15 @@ class InputNumber extends React.Component {
|
|
|
22073
21756
|
this.refs.input.focus();
|
|
22074
21757
|
return true;
|
|
22075
21758
|
};
|
|
21759
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21760
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22076
21761
|
this.focusInputPath = inputPath => {
|
|
22077
21762
|
// eslint-disable-next-line react/no-string-refs
|
|
22078
21763
|
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
22079
21764
|
this.refs.input.focus();
|
|
22080
21765
|
};
|
|
21766
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21767
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22081
21768
|
this.blurInputPath = inputPath => {
|
|
22082
21769
|
var _this$refs$input;
|
|
22083
21770
|
// eslint-disable-next-line react/no-string-refs
|
|
@@ -22095,6 +21782,8 @@ class InputNumber extends React.Component {
|
|
|
22095
21782
|
/* c8 ignore next */
|
|
22096
21783
|
return [[]];
|
|
22097
21784
|
};
|
|
21785
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21786
|
+
// @ts-expect-error: Type 'FocusPath' is not assignable to type 'Path'.
|
|
22098
21787
|
this.setInputValue = (path, newValue, cb) => {
|
|
22099
21788
|
this.props.onChange({
|
|
22100
21789
|
currentValue: newValue
|
|
@@ -22222,6 +21911,8 @@ var InputNumber$1 = {
|
|
|
22222
21911
|
widget: InputNumber,
|
|
22223
21912
|
transform: propTransform$3,
|
|
22224
21913
|
isLintable: true,
|
|
21914
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
21915
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
22225
21916
|
scorer: scoreInputNumber,
|
|
22226
21917
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22227
21918
|
if (rubric.value == null) {
|
|
@@ -22512,7 +22203,7 @@ class NumericInput extends React.Component {
|
|
|
22512
22203
|
height: "auto"
|
|
22513
22204
|
}
|
|
22514
22205
|
});
|
|
22515
|
-
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22206
|
+
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
22516
22207
|
ref: ref => this.inputRef = ref,
|
|
22517
22208
|
value: this.props.currentValue,
|
|
22518
22209
|
onChange: this.handleChange,
|
|
@@ -22524,7 +22215,7 @@ class NumericInput extends React.Component {
|
|
|
22524
22215
|
id: this.props.widgetId,
|
|
22525
22216
|
disabled: this.props.apiOptions.readOnly,
|
|
22526
22217
|
style: styles.input
|
|
22527
|
-
});
|
|
22218
|
+
}));
|
|
22528
22219
|
}
|
|
22529
22220
|
}
|
|
22530
22221
|
|
|
@@ -22593,69 +22284,19 @@ const propsTransform = function propsTransform(widgetOptions) {
|
|
|
22593
22284
|
});
|
|
22594
22285
|
return rendererProps;
|
|
22595
22286
|
};
|
|
22596
|
-
|
|
22597
|
-
// This function is being used to replace the input-number widget
|
|
22598
|
-
// with the numeric-input widget
|
|
22599
|
-
const propUpgrades$2 = {
|
|
22600
|
-
/* c8 ignore next */
|
|
22601
|
-
"1": initialProps => {
|
|
22602
|
-
// If the initialProps has simplify, it means we're upgrading from
|
|
22603
|
-
// input-number to numeric-input. In this case, we need to upgrade
|
|
22604
|
-
// the widget options accordingly.
|
|
22605
|
-
if (initialProps.simplify !== undefined) {
|
|
22606
|
-
// If the answerType is not number or percent, we need to provide
|
|
22607
|
-
// the answer form for the numeric-input widget
|
|
22608
|
-
const provideAnswerForm = initialProps.answerType !== "number" && initialProps.answerType !== "percent";
|
|
22609
|
-
|
|
22610
|
-
// We need to determine the mathFormat for the numeric-input widget
|
|
22611
|
-
const mathFormat = initialProps.answerType === "rational" ? "proper" // input-number uses "rational" for proper fractions
|
|
22612
|
-
: initialProps.answerType; // Otherwise, we can use the answerType directly
|
|
22613
|
-
|
|
22614
|
-
// If adjusting this logic, also adjust the logic in the convertInputNumberWidgetOptions
|
|
22615
|
-
// function in input-number.ts in the Perseus Editor package's util folder
|
|
22616
|
-
const answers = [{
|
|
22617
|
-
value: initialProps.value,
|
|
22618
|
-
simplify: initialProps.simplify,
|
|
22619
|
-
answerForms: provideAnswerForm ? [mathFormat] : undefined,
|
|
22620
|
-
strict: initialProps.inexact,
|
|
22621
|
-
// We only want to set maxError if the inexact prop is true
|
|
22622
|
-
maxError: initialProps.inexact ? initialProps.maxError : 0,
|
|
22623
|
-
status: "correct",
|
|
22624
|
-
// Input-number only allows correct answers
|
|
22625
|
-
message: ""
|
|
22626
|
-
}];
|
|
22627
|
-
return {
|
|
22628
|
-
answers,
|
|
22629
|
-
size: initialProps.size,
|
|
22630
|
-
coefficient: false,
|
|
22631
|
-
// input-number doesn't have a coefficient prop
|
|
22632
|
-
labelText: "",
|
|
22633
|
-
// input-number doesn't have a labelText prop
|
|
22634
|
-
static: false,
|
|
22635
|
-
// static is always false for numeric-input
|
|
22636
|
-
rightAlign: initialProps.rightAlign || false
|
|
22637
|
-
};
|
|
22638
|
-
} else {
|
|
22639
|
-
// Otherwise simply return the initialProps as there's no differences
|
|
22640
|
-
// between v0 and v1 for numeric-input
|
|
22641
|
-
return initialProps;
|
|
22642
|
-
}
|
|
22643
|
-
}
|
|
22644
|
-
};
|
|
22645
22287
|
var NumericInput$1 = {
|
|
22646
22288
|
name: "numeric-input",
|
|
22647
22289
|
displayName: "Numeric input",
|
|
22648
22290
|
defaultAlignment: "inline-block",
|
|
22649
22291
|
accessible: true,
|
|
22650
22292
|
widget: NumericInput,
|
|
22651
|
-
version: {
|
|
22652
|
-
major: 1,
|
|
22653
|
-
minor: 0
|
|
22654
|
-
},
|
|
22655
22293
|
transform: propsTransform,
|
|
22656
|
-
propUpgrades: propUpgrades$2,
|
|
22657
22294
|
isLintable: true,
|
|
22295
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22296
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
22658
22297
|
scorer: scoreNumericInput,
|
|
22298
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
22299
|
+
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
22659
22300
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
22660
22301
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
22661
22302
|
const answerStrings = correctAnswers.map(answer => {
|
|
@@ -24166,12 +23807,12 @@ const AltText = ({
|
|
|
24166
23807
|
}));
|
|
24167
23808
|
};
|
|
24168
23809
|
const rules = {
|
|
24169
|
-
newline: SimpleMarkdown
|
|
24170
|
-
paragraph: SimpleMarkdown
|
|
24171
|
-
escape: SimpleMarkdown
|
|
23810
|
+
newline: SimpleMarkdown.defaultRules.newline,
|
|
23811
|
+
paragraph: SimpleMarkdown.defaultRules.paragraph,
|
|
23812
|
+
escape: SimpleMarkdown.defaultRules.escape,
|
|
24172
23813
|
passageFootnote: {
|
|
24173
|
-
order: SimpleMarkdown
|
|
24174
|
-
match: SimpleMarkdown
|
|
23814
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.1,
|
|
23815
|
+
match: SimpleMarkdown.inlineRegex(/^\^/),
|
|
24175
23816
|
parse: (capture, parse, state) => {
|
|
24176
23817
|
// if no footnotes have been seen, we're id 1. otherwise,
|
|
24177
23818
|
// we're the next subsequent id
|
|
@@ -24200,7 +23841,7 @@ const rules = {
|
|
|
24200
23841
|
}
|
|
24201
23842
|
},
|
|
24202
23843
|
refStart: {
|
|
24203
|
-
order: SimpleMarkdown
|
|
23844
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.2,
|
|
24204
23845
|
match: function (source) {
|
|
24205
23846
|
const capture = /^\{\{/.exec(source);
|
|
24206
23847
|
if (capture) {
|
|
@@ -24286,8 +23927,8 @@ const rules = {
|
|
|
24286
23927
|
}
|
|
24287
23928
|
},
|
|
24288
23929
|
refEnd: {
|
|
24289
|
-
order: SimpleMarkdown
|
|
24290
|
-
match: SimpleMarkdown
|
|
23930
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.3,
|
|
23931
|
+
match: SimpleMarkdown.inlineRegex(/^\}\}/),
|
|
24291
23932
|
parse: (capture, parse, state) => {
|
|
24292
23933
|
if (!state.useRefs) {
|
|
24293
23934
|
return {
|
|
@@ -24315,8 +23956,8 @@ const rules = {
|
|
|
24315
23956
|
}
|
|
24316
23957
|
},
|
|
24317
23958
|
squareLabel: {
|
|
24318
|
-
order: SimpleMarkdown
|
|
24319
|
-
match: SimpleMarkdown
|
|
23959
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.4,
|
|
23960
|
+
match: SimpleMarkdown.inlineRegex(/^\[\[(\w+)\]\]( *)/),
|
|
24320
23961
|
parse: (capture, parse, state) => {
|
|
24321
23962
|
if (!state.firstQuestionRef) {
|
|
24322
23963
|
state.firstQuestionRef = capture[1];
|
|
@@ -24342,8 +23983,8 @@ const rules = {
|
|
|
24342
23983
|
}
|
|
24343
23984
|
},
|
|
24344
23985
|
circleLabel: {
|
|
24345
|
-
order: SimpleMarkdown
|
|
24346
|
-
match: SimpleMarkdown
|
|
23986
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.5,
|
|
23987
|
+
match: SimpleMarkdown.inlineRegex(/^\(\((\w+)\)\)( *)/),
|
|
24347
23988
|
parse: (capture, parse, state) => {
|
|
24348
23989
|
return {
|
|
24349
23990
|
content: capture[1],
|
|
@@ -24367,8 +24008,8 @@ const rules = {
|
|
|
24367
24008
|
}
|
|
24368
24009
|
},
|
|
24369
24010
|
squareBracketRef: {
|
|
24370
|
-
order: SimpleMarkdown
|
|
24371
|
-
match: SimpleMarkdown
|
|
24011
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.6,
|
|
24012
|
+
match: SimpleMarkdown.inlineRegex(/^\[(\d+)\]( *)/),
|
|
24372
24013
|
parse: (capture, parse, state) => {
|
|
24373
24014
|
if (!state.firstSentenceRef) {
|
|
24374
24015
|
state.firstSentenceRef = capture[1];
|
|
@@ -24391,8 +24032,8 @@ const rules = {
|
|
|
24391
24032
|
}
|
|
24392
24033
|
},
|
|
24393
24034
|
highlight: {
|
|
24394
|
-
order: SimpleMarkdown
|
|
24395
|
-
match: SimpleMarkdown
|
|
24035
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
24036
|
+
match: SimpleMarkdown.inlineRegex(/^{highlighting.start}(.+?){highlighting.end}/),
|
|
24396
24037
|
parse: (capture, parse, state) => {
|
|
24397
24038
|
return {
|
|
24398
24039
|
content: capture[1]
|
|
@@ -24406,8 +24047,8 @@ const rules = {
|
|
|
24406
24047
|
}
|
|
24407
24048
|
},
|
|
24408
24049
|
reviewHighlight: {
|
|
24409
|
-
order: SimpleMarkdown
|
|
24410
|
-
match: SimpleMarkdown
|
|
24050
|
+
order: SimpleMarkdown.defaultRules.escape.order + 0.7,
|
|
24051
|
+
match: SimpleMarkdown.inlineRegex(/^{review-highlighting.start}(.+?){review-highlighting.end}/),
|
|
24411
24052
|
parse: (capture, parse, state) => {
|
|
24412
24053
|
return {
|
|
24413
24054
|
content: capture[1]
|
|
@@ -24420,11 +24061,11 @@ const rules = {
|
|
|
24420
24061
|
}, node.content)];
|
|
24421
24062
|
}
|
|
24422
24063
|
},
|
|
24423
|
-
strong: SimpleMarkdown
|
|
24424
|
-
u: SimpleMarkdown
|
|
24425
|
-
em: SimpleMarkdown
|
|
24426
|
-
del: SimpleMarkdown
|
|
24427
|
-
text: _extends({}, SimpleMarkdown
|
|
24064
|
+
strong: SimpleMarkdown.defaultRules.strong,
|
|
24065
|
+
u: SimpleMarkdown.defaultRules.u,
|
|
24066
|
+
em: SimpleMarkdown.defaultRules.em,
|
|
24067
|
+
del: SimpleMarkdown.defaultRules.del,
|
|
24068
|
+
text: _extends({}, SimpleMarkdown.defaultRules.text, {
|
|
24428
24069
|
react(node, output, state) {
|
|
24429
24070
|
return /*#__PURE__*/React.createElement("span", {
|
|
24430
24071
|
key: state.key
|
|
@@ -24465,7 +24106,10 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
24465
24106
|
borderRadius: "50%",
|
|
24466
24107
|
textAlign: "center"
|
|
24467
24108
|
};
|
|
24468
|
-
|
|
24109
|
+
|
|
24110
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
24111
|
+
// @ts-expect-error: State is not assignable to ParseState
|
|
24112
|
+
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
24469
24113
|
const parse$2 = (source, state) => {
|
|
24470
24114
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
24471
24115
|
state = state || {};
|
|
@@ -24474,7 +24118,7 @@ const parse$2 = (source, state) => {
|
|
|
24474
24118
|
};
|
|
24475
24119
|
|
|
24476
24120
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
24477
|
-
const output = SimpleMarkdown
|
|
24121
|
+
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
24478
24122
|
var PassageMarkdown = {
|
|
24479
24123
|
parse: parse$2,
|
|
24480
24124
|
output,
|
|
@@ -26897,6 +26541,8 @@ var Radio = {
|
|
|
26897
26541
|
},
|
|
26898
26542
|
propUpgrades: propUpgrades$1,
|
|
26899
26543
|
isLintable: true,
|
|
26544
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26545
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
26900
26546
|
scorer: scoreRadio
|
|
26901
26547
|
};
|
|
26902
26548
|
|
|
@@ -27117,6 +26763,8 @@ var Categorizer$1 = {
|
|
|
27117
26763
|
return _.pick(editorProps, "items", "categories", "values", "randomizeItems");
|
|
27118
26764
|
},
|
|
27119
26765
|
isLintable: true,
|
|
26766
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26767
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27120
26768
|
scorer: scoreCategorizer
|
|
27121
26769
|
};
|
|
27122
26770
|
|
|
@@ -27350,6 +26998,8 @@ var CSProgram$1 = {
|
|
|
27350
26998
|
supportedAlignments: ["block", "full-width"],
|
|
27351
26999
|
widget: CSProgram,
|
|
27352
27000
|
hidden: true,
|
|
27001
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27002
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
27353
27003
|
scorer: scoreCSProgram
|
|
27354
27004
|
};
|
|
27355
27005
|
|
|
@@ -27567,6 +27217,8 @@ var Dropdown$1 = {
|
|
|
27567
27217
|
accessible: true,
|
|
27568
27218
|
widget: Dropdown,
|
|
27569
27219
|
transform: optionsTransform,
|
|
27220
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27221
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
27570
27222
|
scorer: scoreDropdown
|
|
27571
27223
|
};
|
|
27572
27224
|
|
|
@@ -29129,6 +28781,8 @@ class FunctionGrapher extends React.Component {
|
|
|
29129
28781
|
return this.props.asymptote;
|
|
29130
28782
|
};
|
|
29131
28783
|
this.change = (...args) => {
|
|
28784
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
28785
|
+
// @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.
|
|
29132
28786
|
return change.apply(this, args);
|
|
29133
28787
|
};
|
|
29134
28788
|
this.renderPlot = () => {
|
|
@@ -29492,6 +29146,8 @@ var Grapher$1 = {
|
|
|
29492
29146
|
widget: Grapher,
|
|
29493
29147
|
transform: propTransform$2,
|
|
29494
29148
|
staticTransform: staticTransform$4,
|
|
29149
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29150
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
29495
29151
|
scorer: scoreGrapher
|
|
29496
29152
|
};
|
|
29497
29153
|
|
|
@@ -29613,9 +29269,6 @@ class Group extends React.Component {
|
|
|
29613
29269
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29614
29270
|
}
|
|
29615
29271
|
};
|
|
29616
|
-
|
|
29617
|
-
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29618
|
-
// for valid/invalid state
|
|
29619
29272
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29620
29273
|
const isValid = score && score.type !== "invalid";
|
|
29621
29274
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -29823,6 +29476,8 @@ var Iframe$1 = {
|
|
|
29823
29476
|
widget: Iframe,
|
|
29824
29477
|
// Let's not expose it to all content creators yet
|
|
29825
29478
|
hidden: true,
|
|
29479
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
29480
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
29826
29481
|
scorer: scoreIframe
|
|
29827
29482
|
};
|
|
29828
29483
|
|
|
@@ -31474,6 +31129,37 @@ function isUnlimitedGraphState(state) {
|
|
|
31474
31129
|
return state.type === "point" && state.numPoints === "unlimited" || state.type === "polygon" && state.numSides === "unlimited";
|
|
31475
31130
|
}
|
|
31476
31131
|
|
|
31132
|
+
/**
|
|
31133
|
+
* Parse a string of text and math into a list of objects with type and content
|
|
31134
|
+
*
|
|
31135
|
+
* Example: "Pi is about $\frac{22}{7}$" ==>
|
|
31136
|
+
* [
|
|
31137
|
+
* {type: "text", content: "Pi is about "},
|
|
31138
|
+
* {type: "math", content: "\\frac{22}{7}"},
|
|
31139
|
+
* ]
|
|
31140
|
+
*/
|
|
31141
|
+
const mathOnlyParser = SimpleMarkdown.parserFor({
|
|
31142
|
+
math: _extends({}, pureMarkdownRules.math, {
|
|
31143
|
+
order: 0
|
|
31144
|
+
}),
|
|
31145
|
+
text: {
|
|
31146
|
+
order: 1,
|
|
31147
|
+
match: SimpleMarkdown.anyScopeRegex(/^([^$\\{}]+)/),
|
|
31148
|
+
parse: capture => ({
|
|
31149
|
+
content: capture[0]
|
|
31150
|
+
})
|
|
31151
|
+
},
|
|
31152
|
+
specialCharacter: {
|
|
31153
|
+
order: 2,
|
|
31154
|
+
match: SimpleMarkdown.anyScopeRegex(/^(\\[\S\s]|\$|\\$|{|})/),
|
|
31155
|
+
parse: capture => ({
|
|
31156
|
+
content: capture[0]
|
|
31157
|
+
})
|
|
31158
|
+
}
|
|
31159
|
+
}, {
|
|
31160
|
+
inline: true
|
|
31161
|
+
});
|
|
31162
|
+
|
|
31477
31163
|
/**
|
|
31478
31164
|
* Replace all text outside of the $ TeX blocks with `\\text{...}`
|
|
31479
31165
|
* This way, the entire resulting string can be rendered within <TeX>
|
|
@@ -31482,57 +31168,19 @@ function isUnlimitedGraphState(state) {
|
|
|
31482
31168
|
function replaceOutsideTeX(mathString) {
|
|
31483
31169
|
// All the information we need is in the first section,
|
|
31484
31170
|
// whether it's typed as "blockmath" or "paragraph"
|
|
31485
|
-
const
|
|
31486
|
-
|
|
31487
|
-
// If it's blockMath, the outer level has the full math content.
|
|
31488
|
-
if (firstSection.type === "blockMath") {
|
|
31489
|
-
return firstSection.content;
|
|
31490
|
-
}
|
|
31491
|
-
|
|
31492
|
-
// If it's a paragraph, we need to iterate through the sections
|
|
31493
|
-
// to look for individual math blocks.
|
|
31494
|
-
const condensedNodes = condenseTextNodes(firstSection.content);
|
|
31171
|
+
const parsed = mathOnlyParser(mathString);
|
|
31495
31172
|
let result = "";
|
|
31496
|
-
for (const piece of
|
|
31497
|
-
piece.type === "math" ? result += piece.content : result +=
|
|
31173
|
+
for (const piece of parsed) {
|
|
31174
|
+
piece.type === "math" ? result += "$" + piece.content + "$" : piece.type === "specialCharacter" ? result += escapeIfUnescaped(piece.content) : result += piece.content;
|
|
31498
31175
|
}
|
|
31499
|
-
return result
|
|
31176
|
+
return `\\text{${result}}`;
|
|
31500
31177
|
}
|
|
31501
|
-
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
|
|
31506
|
-
return result;
|
|
31507
|
-
}
|
|
31508
|
-
let currentText = "";
|
|
31509
|
-
for (const node of nodes) {
|
|
31510
|
-
if (node.type === "math") {
|
|
31511
|
-
if (currentText) {
|
|
31512
|
-
result.push({
|
|
31513
|
-
type: "text",
|
|
31514
|
-
content: currentText
|
|
31515
|
-
});
|
|
31516
|
-
currentText = "";
|
|
31517
|
-
}
|
|
31518
|
-
result.push(node);
|
|
31519
|
-
} else {
|
|
31520
|
-
currentText += node.content;
|
|
31521
|
-
}
|
|
31522
|
-
}
|
|
31523
|
-
if (currentText) {
|
|
31524
|
-
result.push({
|
|
31525
|
-
type: "text",
|
|
31526
|
-
content: currentText
|
|
31527
|
-
});
|
|
31178
|
+
function escapeIfUnescaped(character) {
|
|
31179
|
+
if (character.length === 1) {
|
|
31180
|
+
return "\\" + character;
|
|
31181
|
+
} else {
|
|
31182
|
+
return character;
|
|
31528
31183
|
}
|
|
31529
|
-
return result;
|
|
31530
|
-
}
|
|
31531
|
-
|
|
31532
|
-
// Helper function for replaceOutsideTeX()
|
|
31533
|
-
function escapeSpecialChars(str) {
|
|
31534
|
-
// Escape $, \, {, and } characters
|
|
31535
|
-
return str.replace(/([$\\{}])/g, "\\$1");
|
|
31536
31184
|
}
|
|
31537
31185
|
|
|
31538
31186
|
function AxisLabels() {
|
|
@@ -32477,12 +32125,17 @@ const ItemExtras = [
|
|
|
32477
32125
|
// prettier-ignore
|
|
32478
32126
|
|
|
32479
32127
|
// prettier-ignore
|
|
32480
|
-
|
|
32128
|
+
|
|
32481
32129
|
// prettier-ignore
|
|
32130
|
+
|
|
32482
32131
|
// prettier-ignore
|
|
32132
|
+
|
|
32483
32133
|
// prettier-ignore
|
|
32134
|
+
|
|
32484
32135
|
//prettier-ignore
|
|
32136
|
+
|
|
32485
32137
|
// A background image applied to various widgets.
|
|
32138
|
+
|
|
32486
32139
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
32487
32140
|
|
|
32488
32141
|
// 2D range: xMin, xMax, yMin, yMax
|
|
@@ -32992,7 +32645,9 @@ const actions = {
|
|
|
32992
32645
|
removePoint,
|
|
32993
32646
|
focusPoint,
|
|
32994
32647
|
blurPoint,
|
|
32995
|
-
clickPoint
|
|
32648
|
+
clickPoint,
|
|
32649
|
+
closePolygon,
|
|
32650
|
+
openPolygon
|
|
32996
32651
|
},
|
|
32997
32652
|
quadratic: {
|
|
32998
32653
|
movePoint
|
|
@@ -33071,6 +32726,18 @@ function changeKeyboardInvitationVisibility(shouldShow) {
|
|
|
33071
32726
|
shouldShow
|
|
33072
32727
|
};
|
|
33073
32728
|
}
|
|
32729
|
+
const CLOSE_POLYGON = "close-polygon";
|
|
32730
|
+
function closePolygon() {
|
|
32731
|
+
return {
|
|
32732
|
+
type: CLOSE_POLYGON
|
|
32733
|
+
};
|
|
32734
|
+
}
|
|
32735
|
+
const OPEN_POLYGON = "open-polygon";
|
|
32736
|
+
function openPolygon() {
|
|
32737
|
+
return {
|
|
32738
|
+
type: OPEN_POLYGON
|
|
32739
|
+
};
|
|
32740
|
+
}
|
|
33074
32741
|
const MOVE_ALL = "move-all";
|
|
33075
32742
|
function moveAll(delta) {
|
|
33076
32743
|
return {
|
|
@@ -34584,33 +34251,17 @@ const LimitedPolygonGraph = props => {
|
|
|
34584
34251
|
}
|
|
34585
34252
|
})));
|
|
34586
34253
|
};
|
|
34587
|
-
|
|
34588
|
-
// TODO(catjohnson): reduce redundancy between LimitedPolygonGraph and UnlimitedPolygonGraph
|
|
34589
|
-
// both components are vary similar, however more implementation is needed to be added before
|
|
34590
|
-
// it is clear what can and can't be shared between components.
|
|
34591
34254
|
const UnlimitedPolygonGraph = props => {
|
|
34592
|
-
const [hovered, setHovered] = React.useState(false);
|
|
34593
|
-
// This is more so required for the re-rendering that occurs when state
|
|
34594
|
-
// updates; specifically with regard to line weighting and polygon focus.
|
|
34595
|
-
const [focusVisible, setFocusVisible] = React.useState(false);
|
|
34596
34255
|
const {
|
|
34597
34256
|
dispatch
|
|
34598
34257
|
} = props;
|
|
34599
34258
|
const {
|
|
34600
34259
|
coords,
|
|
34601
|
-
|
|
34602
|
-
showSides,
|
|
34603
|
-
range,
|
|
34604
|
-
snapStep,
|
|
34605
|
-
snapTo = "grid"
|
|
34260
|
+
closedPolygon
|
|
34606
34261
|
} = props.graphState;
|
|
34607
34262
|
const graphConfig = useGraphConfig();
|
|
34608
|
-
|
|
34609
|
-
// TODO(catjohnson): Explore abstracting this code as it is similar to point.tsx
|
|
34610
|
-
// and hopefully we can cut down ont the unlimited graph redundancy.
|
|
34611
34263
|
const {
|
|
34612
34264
|
range: [x, y],
|
|
34613
|
-
disableKeyboardInteraction,
|
|
34614
34265
|
graphDimensionsInPixels
|
|
34615
34266
|
} = graphConfig;
|
|
34616
34267
|
const widthPx = graphDimensionsInPixels[0];
|
|
@@ -34621,21 +34272,6 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34621
34272
|
// TODO(benchristel): can the default set of points be removed here? I don't
|
|
34622
34273
|
// think coords can be null.
|
|
34623
34274
|
const points = coords != null ? coords : [[0, 0]];
|
|
34624
|
-
const polygonRef = React.useRef(null);
|
|
34625
|
-
const dragReferencePoint = points[0];
|
|
34626
|
-
const constrain = ["angles", "sides"].includes(snapTo) ? p => p : p => snap(snapStep, p);
|
|
34627
|
-
const {
|
|
34628
|
-
dragging
|
|
34629
|
-
} = useDraggable({
|
|
34630
|
-
gestureTarget: polygonRef,
|
|
34631
|
-
point: dragReferencePoint,
|
|
34632
|
-
onMove: newPoint => {
|
|
34633
|
-
const delta = vec.sub(newPoint, dragReferencePoint);
|
|
34634
|
-
dispatch(actions.polygon.moveAll(delta));
|
|
34635
|
-
},
|
|
34636
|
-
constrainKeyboardMovement: constrain
|
|
34637
|
-
});
|
|
34638
|
-
const lines = getLines(points);
|
|
34639
34275
|
React.useEffect(() => {
|
|
34640
34276
|
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34641
34277
|
if (focusedIndex != null) {
|
|
@@ -34643,94 +34279,59 @@ const UnlimitedPolygonGraph = props => {
|
|
|
34643
34279
|
(_pointRef$current$foc = pointRef.current[focusedIndex]) == null || _pointRef$current$foc.focus();
|
|
34644
34280
|
}
|
|
34645
34281
|
}, [props.graphState.focusedPointIndex, pointRef]);
|
|
34646
|
-
|
|
34647
|
-
|
|
34648
|
-
|
|
34649
|
-
cursor: "crosshair"
|
|
34650
|
-
},
|
|
34651
|
-
width: widthPx,
|
|
34652
|
-
height: heightPx,
|
|
34653
|
-
x: left,
|
|
34654
|
-
y: top,
|
|
34655
|
-
onClick: event => {
|
|
34656
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34657
|
-
const x = event.clientX - elementRect.x;
|
|
34658
|
-
const y = event.clientY - elementRect.y;
|
|
34659
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34660
|
-
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34661
|
-
}
|
|
34662
|
-
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
34663
|
-
points: [...points],
|
|
34664
|
-
color: "var(--movable-line-stroke-color)",
|
|
34665
|
-
svgPolygonProps: {
|
|
34666
|
-
strokeWidth: focusVisible ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
|
|
34667
|
-
style: {
|
|
34668
|
-
fill: "transparent"
|
|
34669
|
-
}
|
|
34670
|
-
}
|
|
34671
|
-
}), props.graphState.coords.map((point, i) => {
|
|
34672
|
-
const pt1 = points.at(i - 1);
|
|
34673
|
-
const pt2 = points[(i + 1) % points.length];
|
|
34674
|
-
if (!pt1 || !pt2) {
|
|
34675
|
-
return null;
|
|
34676
|
-
}
|
|
34677
|
-
return /*#__PURE__*/React.createElement(PolygonAngle, {
|
|
34678
|
-
key: "angle-" + i,
|
|
34679
|
-
centerPoint: point,
|
|
34680
|
-
endPoints: [pt1, pt2],
|
|
34681
|
-
range: range,
|
|
34682
|
-
polygonLines: lines,
|
|
34683
|
-
showAngles: !!showAngles,
|
|
34684
|
-
snapTo: snapTo
|
|
34282
|
+
if (closedPolygon) {
|
|
34283
|
+
const closedPolygonProps = _extends({}, props, {
|
|
34284
|
+
numSides: coords.length
|
|
34685
34285
|
});
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
return /*#__PURE__*/React.createElement(TextLabel, {
|
|
34690
|
-
key: "side-" + i,
|
|
34691
|
-
x: x,
|
|
34692
|
-
y: y
|
|
34693
|
-
}, !Number.isInteger(length) && "≈ ", length);
|
|
34694
|
-
}), /*#__PURE__*/React.createElement(Polygon, {
|
|
34695
|
-
points: [...points],
|
|
34696
|
-
color: "transparent",
|
|
34697
|
-
svgPolygonProps: {
|
|
34698
|
-
ref: polygonRef,
|
|
34699
|
-
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
34700
|
-
strokeWidth: TARGET_SIZE,
|
|
34286
|
+
return /*#__PURE__*/React.createElement(LimitedPolygonGraph, closedPolygonProps);
|
|
34287
|
+
} else {
|
|
34288
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
34701
34289
|
style: {
|
|
34702
|
-
|
|
34703
|
-
|
|
34290
|
+
fill: "rgba(0,0,0,0)",
|
|
34291
|
+
cursor: "crosshair"
|
|
34704
34292
|
},
|
|
34705
|
-
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
34709
|
-
|
|
34710
|
-
|
|
34293
|
+
width: widthPx,
|
|
34294
|
+
height: heightPx,
|
|
34295
|
+
x: left,
|
|
34296
|
+
y: top,
|
|
34297
|
+
onClick: event => {
|
|
34298
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34299
|
+
const x = event.clientX - elementRect.x;
|
|
34300
|
+
const y = event.clientY - elementRect.y;
|
|
34301
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34302
|
+
dispatch(actions.polygon.addPoint(graphCoordinates[0]));
|
|
34303
|
+
}
|
|
34304
|
+
}), /*#__PURE__*/React.createElement(Polyline, {
|
|
34305
|
+
points: [...points],
|
|
34306
|
+
color: "var(--movable-line-stroke-color)",
|
|
34307
|
+
svgPolylineProps: {
|
|
34308
|
+
strokeWidth: "var(--movable-line-stroke-weight)",
|
|
34309
|
+
style: {
|
|
34310
|
+
fill: "transparent"
|
|
34311
|
+
}
|
|
34312
|
+
}
|
|
34313
|
+
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
34314
|
+
key: i,
|
|
34315
|
+
point: point,
|
|
34316
|
+
sequenceNumber: i + 1,
|
|
34317
|
+
onMove: destination => dispatch(actions.polygon.movePoint(i, destination)),
|
|
34318
|
+
ref: ref => {
|
|
34319
|
+
pointRef.current[i] = ref;
|
|
34711
34320
|
},
|
|
34712
|
-
|
|
34713
|
-
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
|
|
34724
|
-
|
|
34725
|
-
|
|
34726
|
-
},
|
|
34727
|
-
onFocus: () => {
|
|
34728
|
-
dispatch(actions.pointGraph.focusPoint(i));
|
|
34729
|
-
},
|
|
34730
|
-
onClick: () => {
|
|
34731
|
-
dispatch(actions.pointGraph.clickPoint(i));
|
|
34732
|
-
}
|
|
34733
|
-
})));
|
|
34321
|
+
onFocus: () => {
|
|
34322
|
+
dispatch(actions.polygon.focusPoint(i));
|
|
34323
|
+
},
|
|
34324
|
+
onClick: () => {
|
|
34325
|
+
// If the point being clicked is the first point and
|
|
34326
|
+
// there's enough points to form a polygon (3 or more)
|
|
34327
|
+
// Close the shape before setting focus.
|
|
34328
|
+
if (i === 0 && props.graphState.coords.length >= 3) {
|
|
34329
|
+
dispatch(actions.polygon.closePolygon());
|
|
34330
|
+
}
|
|
34331
|
+
dispatch(actions.polygon.clickPoint(i));
|
|
34332
|
+
}
|
|
34333
|
+
})));
|
|
34334
|
+
}
|
|
34734
34335
|
};
|
|
34735
34336
|
function getLines(points) {
|
|
34736
34337
|
return points.map((point, i) => {
|
|
@@ -35130,6 +34731,7 @@ const MafsGraph = props => {
|
|
|
35130
34731
|
const uniqueId = React.useId();
|
|
35131
34732
|
const descriptionId = `interactive-graph-description-${uniqueId}`;
|
|
35132
34733
|
const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
|
|
34734
|
+
const unlimitedGraphKeyboardPromptId = `unlimited-graph-keyboard-prompt-${uniqueId}`;
|
|
35133
34735
|
const graphRef = React.useRef(null);
|
|
35134
34736
|
const {
|
|
35135
34737
|
analytics
|
|
@@ -35202,7 +34804,7 @@ const MafsGraph = props => {
|
|
|
35202
34804
|
handleKeyboardEvent(event, state, dispatch);
|
|
35203
34805
|
},
|
|
35204
34806
|
"aria-label": fullGraphAriaLabel,
|
|
35205
|
-
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId),
|
|
34807
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId, isUnlimitedGraphState(state) && "unlimited-graph-keyboard-prompt"),
|
|
35206
34808
|
ref: graphRef,
|
|
35207
34809
|
tabIndex: 0,
|
|
35208
34810
|
onFocus: event => {
|
|
@@ -35272,6 +34874,7 @@ const MafsGraph = props => {
|
|
|
35272
34874
|
height: height
|
|
35273
34875
|
}, /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), graph)))), interactionPrompt && /*#__PURE__*/React.createElement(View, {
|
|
35274
34876
|
style: {
|
|
34877
|
+
display: interactionPrompt ? undefined : "hidden",
|
|
35275
34878
|
textAlign: "center",
|
|
35276
34879
|
backgroundColor: "white",
|
|
35277
34880
|
border: "1px solid #21242C52",
|
|
@@ -35283,7 +34886,9 @@ const MafsGraph = props => {
|
|
|
35283
34886
|
top: "50%",
|
|
35284
34887
|
transform: "translateY(-50%)"
|
|
35285
34888
|
}
|
|
35286
|
-
}, /*#__PURE__*/React.createElement(LabelMedium,
|
|
34889
|
+
}, /*#__PURE__*/React.createElement(LabelMedium, {
|
|
34890
|
+
id: unlimitedGraphKeyboardPromptId
|
|
34891
|
+
}, strings.graphKeyboardPrompt))), renderGraphControls({
|
|
35287
34892
|
state,
|
|
35288
34893
|
dispatch,
|
|
35289
34894
|
width,
|
|
@@ -35333,23 +34938,51 @@ const renderPointGraphControls = props => {
|
|
|
35333
34938
|
}
|
|
35334
34939
|
}, perseusStrings.removePoint));
|
|
35335
34940
|
};
|
|
35336
|
-
|
|
35337
|
-
/**
|
|
35338
|
-
* TODO(catjohnson): The polygon controls will be vastly different from the point controls.
|
|
35339
|
-
* Will be keeping this function as is for the time being, even if it's a copy of the point graph
|
|
35340
|
-
* controls.
|
|
35341
|
-
*/
|
|
35342
34941
|
const renderPolygonGraphControls = props => {
|
|
35343
34942
|
const {
|
|
35344
34943
|
interactionMode,
|
|
35345
34944
|
showRemovePointButton,
|
|
35346
|
-
focusedPointIndex
|
|
34945
|
+
focusedPointIndex,
|
|
34946
|
+
closedPolygon,
|
|
34947
|
+
coords
|
|
35347
34948
|
} = props.state;
|
|
35348
34949
|
const {
|
|
35349
34950
|
perseusStrings
|
|
35350
34951
|
} = props;
|
|
35351
34952
|
const shouldShowRemoveButton = showRemovePointButton && focusedPointIndex !== null;
|
|
35352
|
-
|
|
34953
|
+
|
|
34954
|
+
// We want to disable the closePolygon button when
|
|
34955
|
+
// there are not enough coords to make a polygon
|
|
34956
|
+
const disableCloseButton = coords.length < 3;
|
|
34957
|
+
|
|
34958
|
+
// If polygon is closed, show open button.
|
|
34959
|
+
// If polygon is open, show close button.
|
|
34960
|
+
const polygonButton = closedPolygon ? /*#__PURE__*/React.createElement(Button, {
|
|
34961
|
+
kind: "secondary",
|
|
34962
|
+
style: {
|
|
34963
|
+
width: "100%",
|
|
34964
|
+
marginLeft: "20px"
|
|
34965
|
+
},
|
|
34966
|
+
tabIndex: 0,
|
|
34967
|
+
onClick: () => {
|
|
34968
|
+
props.dispatch(actions.polygon.openPolygon());
|
|
34969
|
+
}
|
|
34970
|
+
}, perseusStrings.openPolygon) : /*#__PURE__*/React.createElement(Button, {
|
|
34971
|
+
kind: "secondary"
|
|
34972
|
+
// Conditional disable when there are less than 3 points in
|
|
34973
|
+
// the graph
|
|
34974
|
+
,
|
|
34975
|
+
disabled: disableCloseButton,
|
|
34976
|
+
style: {
|
|
34977
|
+
width: "100%",
|
|
34978
|
+
marginLeft: "20px"
|
|
34979
|
+
},
|
|
34980
|
+
tabIndex: disableCloseButton ? -1 : 0,
|
|
34981
|
+
onClick: () => {
|
|
34982
|
+
props.dispatch(actions.polygon.closePolygon());
|
|
34983
|
+
}
|
|
34984
|
+
}, perseusStrings.closePolygon);
|
|
34985
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
|
|
35353
34986
|
style: {
|
|
35354
34987
|
flexDirection: "row",
|
|
35355
34988
|
width: props.width
|
|
@@ -35359,8 +34992,13 @@ const renderPolygonGraphControls = props => {
|
|
|
35359
34992
|
style: {
|
|
35360
34993
|
width: "100%",
|
|
35361
34994
|
marginLeft: "20px"
|
|
35362
|
-
}
|
|
35363
|
-
|
|
34995
|
+
}
|
|
34996
|
+
// Disable button when polygon is closed.
|
|
34997
|
+
,
|
|
34998
|
+
disabled: closedPolygon
|
|
34999
|
+
// Do not make the button tabbable when it is disabled.
|
|
35000
|
+
,
|
|
35001
|
+
tabIndex: closedPolygon ? -1 : 0,
|
|
35364
35002
|
onClick: () => {
|
|
35365
35003
|
props.dispatch(actions.polygon.addPoint([0, 0]));
|
|
35366
35004
|
}
|
|
@@ -35368,19 +35006,21 @@ const renderPolygonGraphControls = props => {
|
|
|
35368
35006
|
id: REMOVE_BUTTON_ID,
|
|
35369
35007
|
kind: "secondary",
|
|
35370
35008
|
color: "destructive"
|
|
35009
|
+
// Disable button when polygon is closed.
|
|
35010
|
+
,
|
|
35011
|
+
disabled: closedPolygon || !shouldShowRemoveButton
|
|
35371
35012
|
// This button is meant to be interacted with by the mouse only
|
|
35372
35013
|
// Never allow learners to tab to this button
|
|
35373
35014
|
,
|
|
35374
35015
|
tabIndex: -1,
|
|
35375
35016
|
style: {
|
|
35376
35017
|
width: "100%",
|
|
35377
|
-
marginLeft: "20px"
|
|
35378
|
-
visibility: shouldShowRemoveButton ? "visible" : "hidden"
|
|
35018
|
+
marginLeft: "20px"
|
|
35379
35019
|
},
|
|
35380
35020
|
onClick: _event => {
|
|
35381
35021
|
props.dispatch(actions.polygon.removePoint(props.state.focusedPointIndex));
|
|
35382
35022
|
}
|
|
35383
|
-
}, perseusStrings.removePoint));
|
|
35023
|
+
}, perseusStrings.removePoint), polygonButton));
|
|
35384
35024
|
};
|
|
35385
35025
|
const renderGraphControls = props => {
|
|
35386
35026
|
const {
|
|
@@ -35431,7 +35071,7 @@ function handleBlurEvent(_event, state, dispatch) {
|
|
|
35431
35071
|
}
|
|
35432
35072
|
function handleKeyboardEvent(event, state, dispatch) {
|
|
35433
35073
|
if (isUnlimitedGraphState(state)) {
|
|
35434
|
-
if (event.key === "Backspace") {
|
|
35074
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
35435
35075
|
var _document$activeEleme;
|
|
35436
35076
|
// TODO(benchristel): Checking classList here is a hack to prevent
|
|
35437
35077
|
// points from being deleted if the user presses the backspace key
|
|
@@ -35439,7 +35079,10 @@ function handleKeyboardEvent(event, state, dispatch) {
|
|
|
35439
35079
|
// should move the keyboard event handler to the movable point
|
|
35440
35080
|
// handle element.
|
|
35441
35081
|
if ((_document$activeEleme = document.activeElement) != null && _document$activeEleme.classList.contains("movable-point__focusable-handle")) {
|
|
35442
|
-
|
|
35082
|
+
// Only allow delete if type is point or a polygon that is open.
|
|
35083
|
+
if (state.type === "point" || state.type === "polygon" && !state.closedPolygon) {
|
|
35084
|
+
dispatch(actions.global.deleteIntent());
|
|
35085
|
+
}
|
|
35443
35086
|
}
|
|
35444
35087
|
|
|
35445
35088
|
// After removing a point blur
|
|
@@ -35670,7 +35313,8 @@ function initializeGraphState(params) {
|
|
|
35670
35313
|
focusedPointIndex: null,
|
|
35671
35314
|
showRemovePointButton: false,
|
|
35672
35315
|
interactionMode: "mouse",
|
|
35673
|
-
showKeyboardInteractionInvitation: false
|
|
35316
|
+
showKeyboardInteractionInvitation: false,
|
|
35317
|
+
closedPolygon: false
|
|
35674
35318
|
});
|
|
35675
35319
|
case "point":
|
|
35676
35320
|
return _extends({}, shared, {
|
|
@@ -35950,6 +35594,10 @@ function interactiveGraphReducer(state, action) {
|
|
|
35950
35594
|
return doDeleteIntent(state);
|
|
35951
35595
|
case CLICK_POINT:
|
|
35952
35596
|
return doClickPoint(state, action);
|
|
35597
|
+
case CLOSE_POLYGON:
|
|
35598
|
+
return doClosePolygon(state);
|
|
35599
|
+
case OPEN_POLYGON:
|
|
35600
|
+
return doOpenPolygon(state);
|
|
35953
35601
|
case CHANGE_INTERACTION_MODE:
|
|
35954
35602
|
return doChangeInteractionMode(state, action);
|
|
35955
35603
|
case CHANGE_KEYBOARD_INVITATION_VISIBILITY:
|
|
@@ -36003,6 +35651,22 @@ function doClickPoint(state, action) {
|
|
|
36003
35651
|
}
|
|
36004
35652
|
return state;
|
|
36005
35653
|
}
|
|
35654
|
+
function doClosePolygon(state) {
|
|
35655
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
35656
|
+
return _extends({}, state, {
|
|
35657
|
+
closedPolygon: true
|
|
35658
|
+
});
|
|
35659
|
+
}
|
|
35660
|
+
return state;
|
|
35661
|
+
}
|
|
35662
|
+
function doOpenPolygon(state) {
|
|
35663
|
+
if (isUnlimitedGraphState(state) && state.type === "polygon") {
|
|
35664
|
+
return _extends({}, state, {
|
|
35665
|
+
closedPolygon: false
|
|
35666
|
+
});
|
|
35667
|
+
}
|
|
35668
|
+
return state;
|
|
35669
|
+
}
|
|
36006
35670
|
function doChangeInteractionMode(state, action) {
|
|
36007
35671
|
if (isUnlimitedGraphState(state)) {
|
|
36008
35672
|
const nextKeyboardInvitation = action.mode === "keyboard" ? false : state.showKeyboardInteractionInvitation;
|
|
@@ -36201,8 +35865,12 @@ function doMovePoint(state, action) {
|
|
|
36201
35865
|
newValue: newValue
|
|
36202
35866
|
});
|
|
36203
35867
|
|
|
36204
|
-
//
|
|
36205
|
-
if
|
|
35868
|
+
// Boolean value to track whether we can let the polygon sides interact.
|
|
35869
|
+
// They can interact if it's an unlimited polygon that is open.
|
|
35870
|
+
const polygonSidesCanIntersect = state.numSides === "unlimited" && !state.closedPolygon;
|
|
35871
|
+
|
|
35872
|
+
// Reject the move if it would cause the sides of the polygon to cross.
|
|
35873
|
+
if (!polygonSidesCanIntersect && polygonSidesIntersect(newCoords)) {
|
|
36206
35874
|
return state;
|
|
36207
35875
|
}
|
|
36208
35876
|
return _extends({}, state, {
|
|
@@ -36359,13 +36027,14 @@ function doAddPoint(state, action) {
|
|
|
36359
36027
|
return state;
|
|
36360
36028
|
}
|
|
36361
36029
|
}
|
|
36030
|
+
const newCoords = [...state.coords, snappedPoint];
|
|
36362
36031
|
|
|
36363
36032
|
// If there's no point in spot where we want the new point to go we add it there
|
|
36364
36033
|
return _extends({}, state, {
|
|
36365
36034
|
hasBeenInteractedWith: true,
|
|
36366
|
-
coords:
|
|
36035
|
+
coords: newCoords,
|
|
36367
36036
|
showRemovePointButton: false,
|
|
36368
|
-
focusedPointIndex:
|
|
36037
|
+
focusedPointIndex: newCoords.length - 1
|
|
36369
36038
|
});
|
|
36370
36039
|
}
|
|
36371
36040
|
function doRemovePoint(state, action) {
|
|
@@ -38676,6 +38345,8 @@ var InteractiveGraph$1 = {
|
|
|
38676
38345
|
displayName: "Interactive graph (Assessments only)",
|
|
38677
38346
|
widget: InteractiveGraph,
|
|
38678
38347
|
staticTransform: staticTransform$3,
|
|
38348
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
38349
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
38679
38350
|
scorer: scoreInteractiveGraph
|
|
38680
38351
|
};
|
|
38681
38352
|
|
|
@@ -39669,6 +39340,8 @@ var LabelImage$1 = {
|
|
|
39669
39340
|
widget: LabelImageWithDependencies,
|
|
39670
39341
|
accessible: true,
|
|
39671
39342
|
isLintable: true,
|
|
39343
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39344
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
39672
39345
|
scorer: scoreLabelImage
|
|
39673
39346
|
};
|
|
39674
39347
|
|
|
@@ -40611,6 +40284,8 @@ var Matcher$1 = {
|
|
|
40611
40284
|
displayName: "Matcher (two column)",
|
|
40612
40285
|
widget: Matcher,
|
|
40613
40286
|
isLintable: true,
|
|
40287
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40288
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
40614
40289
|
scorer: scoreMatcher
|
|
40615
40290
|
};
|
|
40616
40291
|
|
|
@@ -40627,7 +40302,35 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40627
40302
|
};
|
|
40628
40303
|
};
|
|
40629
40304
|
|
|
40305
|
+
/**
|
|
40306
|
+
* Checks user input from the matrix widget to see if it is scorable.
|
|
40307
|
+
*
|
|
40308
|
+
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40309
|
+
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40310
|
+
*
|
|
40311
|
+
* @see `scoreMatrix()` for more details.
|
|
40312
|
+
*/
|
|
40313
|
+
function validateMatrix(userInput, validationData, strings) {
|
|
40314
|
+
const supplied = userInput.answers;
|
|
40315
|
+
const suppliedSize = getMatrixSize(supplied);
|
|
40316
|
+
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40317
|
+
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40318
|
+
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40319
|
+
return {
|
|
40320
|
+
type: "invalid",
|
|
40321
|
+
message: strings.fillAllCells
|
|
40322
|
+
};
|
|
40323
|
+
}
|
|
40324
|
+
}
|
|
40325
|
+
}
|
|
40326
|
+
return null;
|
|
40327
|
+
}
|
|
40328
|
+
|
|
40630
40329
|
function scoreMatrix(userInput, rubric, strings) {
|
|
40330
|
+
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40331
|
+
if (validationResult != null) {
|
|
40332
|
+
return validationResult;
|
|
40333
|
+
}
|
|
40631
40334
|
const solution = rubric.answers;
|
|
40632
40335
|
const supplied = userInput.answers;
|
|
40633
40336
|
const solutionSize = getMatrixSize(solution);
|
|
@@ -40635,13 +40338,9 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
40635
40338
|
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
40636
40339
|
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40637
40340
|
let message = null;
|
|
40638
|
-
let hasEmptyCell = false;
|
|
40639
40341
|
let incorrect = false;
|
|
40640
40342
|
_(suppliedSize[0]).times(row => {
|
|
40641
40343
|
_(suppliedSize[1]).times(col => {
|
|
40642
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40643
|
-
hasEmptyCell = true;
|
|
40644
|
-
}
|
|
40645
40344
|
if (!incorrectSize) {
|
|
40646
40345
|
const validator = createValidator(
|
|
40647
40346
|
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
@@ -40659,12 +40358,6 @@ function scoreMatrix(userInput, rubric, strings) {
|
|
|
40659
40358
|
}
|
|
40660
40359
|
});
|
|
40661
40360
|
});
|
|
40662
|
-
if (hasEmptyCell) {
|
|
40663
|
-
return {
|
|
40664
|
-
type: "invalid",
|
|
40665
|
-
message: strings.fillAllCells
|
|
40666
|
-
};
|
|
40667
|
-
}
|
|
40668
40361
|
if (incorrectSize) {
|
|
40669
40362
|
return {
|
|
40670
40363
|
type: "points",
|
|
@@ -41093,6 +40786,8 @@ var Matrix$1 = {
|
|
|
41093
40786
|
transform: propTransform$1,
|
|
41094
40787
|
staticTransform: staticTransform$2,
|
|
41095
40788
|
isLintable: true,
|
|
40789
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40790
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
41096
40791
|
scorer: scoreMatrix
|
|
41097
40792
|
};
|
|
41098
40793
|
|
|
@@ -42724,6 +42419,8 @@ var NumberLine$1 = {
|
|
|
42724
42419
|
widget: NumberLine,
|
|
42725
42420
|
transform: numberLineTransform,
|
|
42726
42421
|
staticTransform: staticTransform$1,
|
|
42422
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42423
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
42727
42424
|
scorer: scoreNumberLine
|
|
42728
42425
|
};
|
|
42729
42426
|
|
|
@@ -43304,6 +43001,8 @@ var Orderer$1 = {
|
|
|
43304
43001
|
hidden: true,
|
|
43305
43002
|
widget: Orderer,
|
|
43306
43003
|
isLintable: true,
|
|
43004
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43005
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
43307
43006
|
scorer: scoreOrderer
|
|
43308
43007
|
};
|
|
43309
43008
|
|
|
@@ -44390,6 +44089,8 @@ var Plotter$1 = {
|
|
|
44390
44089
|
hidden: true,
|
|
44391
44090
|
widget: Plotter,
|
|
44392
44091
|
staticTransform: staticTransform,
|
|
44092
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44093
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
44393
44094
|
scorer: scorePlotter
|
|
44394
44095
|
};
|
|
44395
44096
|
|
|
@@ -44575,6 +44276,8 @@ var Sorter$1 = {
|
|
|
44575
44276
|
displayName: "Sorter",
|
|
44576
44277
|
widget: Sorter,
|
|
44577
44278
|
isLintable: true,
|
|
44279
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44280
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
44578
44281
|
scorer: scoreSorter
|
|
44579
44282
|
};
|
|
44580
44283
|
|
|
@@ -44870,6 +44573,8 @@ var Table$1 = {
|
|
|
44870
44573
|
transform: propTransform,
|
|
44871
44574
|
hidden: true,
|
|
44872
44575
|
isLintable: true,
|
|
44576
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
44577
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
44873
44578
|
scorer: scoreTable
|
|
44874
44579
|
};
|
|
44875
44580
|
|
|
@@ -45053,7 +44758,7 @@ const init = function init() {
|
|
|
45053
44758
|
|
|
45054
44759
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45055
44760
|
const libName = "@khanacademy/perseus";
|
|
45056
|
-
const libVersion = "
|
|
44761
|
+
const libVersion = "43.0.1";
|
|
45057
44762
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45058
44763
|
|
|
45059
44764
|
const apiVersion = {
|
|
@@ -45677,13 +45382,6 @@ class ServerItemRenderer extends React.Component {
|
|
|
45677
45382
|
this.props.onRendered(true);
|
|
45678
45383
|
}
|
|
45679
45384
|
}
|
|
45680
|
-
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45681
|
-
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45682
|
-
this.setState({
|
|
45683
|
-
questionCompleted: this.props.score.correct,
|
|
45684
|
-
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45685
|
-
});
|
|
45686
|
-
}
|
|
45687
45385
|
}
|
|
45688
45386
|
componentWillUnmount() {
|
|
45689
45387
|
if (this.blurTimeoutID != null) {
|
|
@@ -45764,7 +45462,7 @@ class ServerItemRenderer extends React.Component {
|
|
|
45764
45462
|
|
|
45765
45463
|
/**
|
|
45766
45464
|
* Accepts a question area widgetId, or an answer area widgetId of
|
|
45767
|
-
* the form "answer-
|
|
45465
|
+
* the form "answer-input-number 1", or the string "answer-area"
|
|
45768
45466
|
* for the whole answer area (if the answer area is a single widget).
|
|
45769
45467
|
*/
|
|
45770
45468
|
_setWidgetProps(widgetId, newProps, callback) {
|
|
@@ -45801,37 +45499,18 @@ class ServerItemRenderer extends React.Component {
|
|
|
45801
45499
|
return this.questionRenderer.getPromptJSON();
|
|
45802
45500
|
}
|
|
45803
45501
|
|
|
45804
|
-
/**
|
|
45805
|
-
* Returns an array of the widget `.getUserInput()` results
|
|
45806
|
-
*
|
|
45807
|
-
* TODO: can we remove this? Seems to be just for backwards
|
|
45808
|
-
* compatibility with old Perseus Chrome logging
|
|
45809
|
-
* @deprecated use getUserInput
|
|
45810
|
-
*/
|
|
45811
|
-
getUserInputLegacy() {
|
|
45812
|
-
return this.questionRenderer.getUserInput();
|
|
45813
|
-
}
|
|
45814
|
-
|
|
45815
|
-
/**
|
|
45816
|
-
* Returns an object of the widget `.getUserInput()` results
|
|
45817
|
-
*/
|
|
45818
|
-
getUserInput() {
|
|
45819
|
-
return this.questionRenderer.getUserInputMap();
|
|
45820
|
-
}
|
|
45821
|
-
|
|
45822
45502
|
/**
|
|
45823
45503
|
* Grades the item.
|
|
45824
|
-
*
|
|
45825
|
-
* @deprecated use scorePerseusItem
|
|
45826
45504
|
*/
|
|
45827
45505
|
scoreInput() {
|
|
45828
|
-
const
|
|
45829
|
-
const
|
|
45506
|
+
const guessAndScore = this.questionRenderer.guessAndScore();
|
|
45507
|
+
const guess = guessAndScore[0];
|
|
45508
|
+
const score = guessAndScore[1];
|
|
45830
45509
|
|
|
45831
45510
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45832
45511
|
// TODO(alex): Check whether we rely on the format here for
|
|
45833
45512
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45834
|
-
const maxCompatGuess = [
|
|
45513
|
+
const maxCompatGuess = [guess, []];
|
|
45835
45514
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45836
45515
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45837
45516
|
this.setState({
|
|
@@ -47679,6 +47358,140 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47679
47358
|
replaceDeprecatedWidgets();
|
|
47680
47359
|
};
|
|
47681
47360
|
|
|
47361
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47362
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47363
|
+
|
|
47364
|
+
/**
|
|
47365
|
+
* Regex for widget placeholders in a string.
|
|
47366
|
+
*
|
|
47367
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
47368
|
+
* Second capture group is the widget type (ex. "radio)
|
|
47369
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47370
|
+
*/
|
|
47371
|
+
function getWidgetRegex() {
|
|
47372
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47373
|
+
}
|
|
47374
|
+
|
|
47375
|
+
/**
|
|
47376
|
+
* Add a widget placeholder using the widget ID.
|
|
47377
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47378
|
+
*
|
|
47379
|
+
* @param {string} id
|
|
47380
|
+
* @returns {string}
|
|
47381
|
+
*/
|
|
47382
|
+
function addWidget(id) {
|
|
47383
|
+
return `[[☃ ${id}]]`;
|
|
47384
|
+
}
|
|
47385
|
+
|
|
47386
|
+
/**
|
|
47387
|
+
* Get a widget type by a widget's ID
|
|
47388
|
+
* (please don't derive type from ID)
|
|
47389
|
+
*
|
|
47390
|
+
* @param {string} widgetId the ID of the widget
|
|
47391
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47392
|
+
* @returns {string} the widget type (ie "radio")
|
|
47393
|
+
*/
|
|
47394
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47395
|
+
var _widget$type;
|
|
47396
|
+
const widget = widgetMap[WidgetId];
|
|
47397
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
47398
|
+
}
|
|
47399
|
+
|
|
47400
|
+
/**
|
|
47401
|
+
* Does the content have a specific type of widget?
|
|
47402
|
+
*
|
|
47403
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
47404
|
+
* @param {string} content the string to search through
|
|
47405
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47406
|
+
* @returns {boolean} if the content includes the widget type
|
|
47407
|
+
*/
|
|
47408
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
47409
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47410
|
+
}
|
|
47411
|
+
|
|
47412
|
+
/**
|
|
47413
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47414
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47415
|
+
*
|
|
47416
|
+
* Content should contain Perseus widget placeholders,
|
|
47417
|
+
* which look like: '[[☃ radio 1]]'.
|
|
47418
|
+
*
|
|
47419
|
+
* @param {string} content
|
|
47420
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
47421
|
+
*/
|
|
47422
|
+
function getWidgetIdsFromContent(content) {
|
|
47423
|
+
const widgets = [];
|
|
47424
|
+
const localWidgetRegex = getWidgetRegex();
|
|
47425
|
+
let match = localWidgetRegex.exec(content);
|
|
47426
|
+
while (match !== null) {
|
|
47427
|
+
widgets.push(match[1]);
|
|
47428
|
+
match = localWidgetRegex.exec(content);
|
|
47429
|
+
}
|
|
47430
|
+
return widgets;
|
|
47431
|
+
}
|
|
47432
|
+
|
|
47433
|
+
/**
|
|
47434
|
+
* Get a list of widget IDs from content,
|
|
47435
|
+
* but only for specific widget types
|
|
47436
|
+
*
|
|
47437
|
+
* @param {string} type the type of widget (ie "radio")
|
|
47438
|
+
* @param {string} content the string to parse
|
|
47439
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47440
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47441
|
+
*/
|
|
47442
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47443
|
+
const rv = [];
|
|
47444
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47445
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
47446
|
+
const widget = widgetMap[widgetId];
|
|
47447
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
47448
|
+
rv.push(widgetId);
|
|
47449
|
+
}
|
|
47450
|
+
});
|
|
47451
|
+
return rv;
|
|
47452
|
+
}
|
|
47453
|
+
|
|
47454
|
+
/**
|
|
47455
|
+
* Pull the widget map out of ItemData
|
|
47456
|
+
*
|
|
47457
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47458
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47459
|
+
*/
|
|
47460
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
47461
|
+
return itemData.question.widgets;
|
|
47462
|
+
}
|
|
47463
|
+
|
|
47464
|
+
/**
|
|
47465
|
+
* Get the widget information from a WidgetMap
|
|
47466
|
+
*
|
|
47467
|
+
* @param {string} widgetId the ID of the widget
|
|
47468
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47469
|
+
*/
|
|
47470
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47471
|
+
var _widgetMap$widgetId;
|
|
47472
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
47473
|
+
}
|
|
47474
|
+
|
|
47475
|
+
/**
|
|
47476
|
+
* Get select widgets from a widget map.
|
|
47477
|
+
* Useful for multi-items that needs to split
|
|
47478
|
+
* one PerseusItem into several
|
|
47479
|
+
*
|
|
47480
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47481
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47482
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47483
|
+
*/
|
|
47484
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47485
|
+
const widgets = {};
|
|
47486
|
+
widgetIds.forEach(widgetId => {
|
|
47487
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47488
|
+
if (widget) {
|
|
47489
|
+
widgets[widgetId] = widget;
|
|
47490
|
+
}
|
|
47491
|
+
});
|
|
47492
|
+
return widgets;
|
|
47493
|
+
}
|
|
47494
|
+
|
|
47682
47495
|
/**
|
|
47683
47496
|
* This function extracts the answers from the widgets.
|
|
47684
47497
|
*
|
|
@@ -49623,5 +49436,5 @@ const GrapherUtil = {
|
|
|
49623
49436
|
typeToButton
|
|
49624
49437
|
};
|
|
49625
49438
|
|
|
49626
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, 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, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType,
|
|
49439
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, 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, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, buildEmptyItemTreeForShape, buildMapper, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getClockwiseAngle, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, lockedFigureFillStyles, makeSafeUrl, mathOnlyParser, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49627
49440
|
//# sourceMappingURL=index.js.map
|