@khanacademy/perseus 44.0.0 → 45.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +233 -189
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +232 -184
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +4 -2
- package/dist/renderer.d.ts +6 -10
- package/dist/server-item-renderer.d.ts +32 -1
- package/dist/types.d.ts +5 -0
- package/dist/widgets/expression/expression.d.ts +1 -1
- package/dist/widgets/group/group.d.ts +1 -1
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -14609,9 +14609,9 @@ const pluck = function pluck(table, subKey) {
|
|
|
14609
14609
|
* });
|
|
14610
14610
|
* {a: 2, b: 3}
|
|
14611
14611
|
*/
|
|
14612
|
-
const mapObject$
|
|
14612
|
+
const mapObject$2 = function mapObject(obj, lambda) {
|
|
14613
14613
|
const result = {};
|
|
14614
|
-
|
|
14614
|
+
Object.keys(obj).forEach(key => {
|
|
14615
14615
|
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
14616
14616
|
result[key] = lambda(obj[key], key);
|
|
14617
14617
|
});
|
|
@@ -14628,14 +14628,14 @@ const mapObject$1 = function mapObject(obj, lambda) {
|
|
|
14628
14628
|
*/
|
|
14629
14629
|
const mapObjectFromArray = function mapObjectFromArray(arr, lambda) {
|
|
14630
14630
|
const result = {};
|
|
14631
|
-
|
|
14631
|
+
arr.forEach(elem => {
|
|
14632
14632
|
result[elem] = lambda(elem);
|
|
14633
14633
|
});
|
|
14634
14634
|
return result;
|
|
14635
14635
|
};
|
|
14636
14636
|
var objective_ = {
|
|
14637
14637
|
pluck: pluck,
|
|
14638
|
-
mapObject: mapObject$
|
|
14638
|
+
mapObject: mapObject$2,
|
|
14639
14639
|
mapObjectFromArray: mapObjectFromArray
|
|
14640
14640
|
};
|
|
14641
14641
|
|
|
@@ -18719,6 +18719,140 @@ class QuestionParagraph extends React.Component {
|
|
|
18719
18719
|
}
|
|
18720
18720
|
}
|
|
18721
18721
|
|
|
18722
|
+
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
18723
|
+
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
18724
|
+
|
|
18725
|
+
/**
|
|
18726
|
+
* Regex for widget placeholders in a string.
|
|
18727
|
+
*
|
|
18728
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
18729
|
+
* Second capture group is the widget type (ex. "radio)
|
|
18730
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
18731
|
+
*/
|
|
18732
|
+
function getWidgetRegex() {
|
|
18733
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
18734
|
+
}
|
|
18735
|
+
|
|
18736
|
+
/**
|
|
18737
|
+
* Add a widget placeholder using the widget ID.
|
|
18738
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
18739
|
+
*
|
|
18740
|
+
* @param {string} id
|
|
18741
|
+
* @returns {string}
|
|
18742
|
+
*/
|
|
18743
|
+
function addWidget(id) {
|
|
18744
|
+
return `[[☃ ${id}]]`;
|
|
18745
|
+
}
|
|
18746
|
+
|
|
18747
|
+
/**
|
|
18748
|
+
* Get a widget type by a widget's ID
|
|
18749
|
+
* (please don't derive type from ID)
|
|
18750
|
+
*
|
|
18751
|
+
* @param {string} widgetId the ID of the widget
|
|
18752
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18753
|
+
* @returns {string} the widget type (ie "radio")
|
|
18754
|
+
*/
|
|
18755
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
18756
|
+
var _widget$type;
|
|
18757
|
+
const widget = widgetMap[WidgetId];
|
|
18758
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
18759
|
+
}
|
|
18760
|
+
|
|
18761
|
+
/**
|
|
18762
|
+
* Does the content have a specific type of widget?
|
|
18763
|
+
*
|
|
18764
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
18765
|
+
* @param {string} content the string to search through
|
|
18766
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18767
|
+
* @returns {boolean} if the content includes the widget type
|
|
18768
|
+
*/
|
|
18769
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
18770
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
18771
|
+
}
|
|
18772
|
+
|
|
18773
|
+
/**
|
|
18774
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
18775
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
18776
|
+
*
|
|
18777
|
+
* Content should contain Perseus widget placeholders,
|
|
18778
|
+
* which look like: '[[☃ radio 1]]'.
|
|
18779
|
+
*
|
|
18780
|
+
* @param {string} content
|
|
18781
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
18782
|
+
*/
|
|
18783
|
+
function getWidgetIdsFromContent(content) {
|
|
18784
|
+
const widgets = [];
|
|
18785
|
+
const localWidgetRegex = getWidgetRegex();
|
|
18786
|
+
let match = localWidgetRegex.exec(content);
|
|
18787
|
+
while (match !== null) {
|
|
18788
|
+
widgets.push(match[1]);
|
|
18789
|
+
match = localWidgetRegex.exec(content);
|
|
18790
|
+
}
|
|
18791
|
+
return widgets;
|
|
18792
|
+
}
|
|
18793
|
+
|
|
18794
|
+
/**
|
|
18795
|
+
* Get a list of widget IDs from content,
|
|
18796
|
+
* but only for specific widget types
|
|
18797
|
+
*
|
|
18798
|
+
* @param {string} type the type of widget (ie "radio")
|
|
18799
|
+
* @param {string} content the string to parse
|
|
18800
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
18801
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
18802
|
+
*/
|
|
18803
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
18804
|
+
const rv = [];
|
|
18805
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
18806
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
18807
|
+
const widget = widgetMap[widgetId];
|
|
18808
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
18809
|
+
rv.push(widgetId);
|
|
18810
|
+
}
|
|
18811
|
+
});
|
|
18812
|
+
return rv;
|
|
18813
|
+
}
|
|
18814
|
+
|
|
18815
|
+
/**
|
|
18816
|
+
* Pull the widget map out of ItemData
|
|
18817
|
+
*
|
|
18818
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
18819
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
18820
|
+
*/
|
|
18821
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
18822
|
+
return itemData.question.widgets;
|
|
18823
|
+
}
|
|
18824
|
+
|
|
18825
|
+
/**
|
|
18826
|
+
* Get the widget information from a WidgetMap
|
|
18827
|
+
*
|
|
18828
|
+
* @param {string} widgetId the ID of the widget
|
|
18829
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
18830
|
+
*/
|
|
18831
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
18832
|
+
var _widgetMap$widgetId;
|
|
18833
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
18834
|
+
}
|
|
18835
|
+
|
|
18836
|
+
/**
|
|
18837
|
+
* Get select widgets from a widget map.
|
|
18838
|
+
* Useful for multi-items that needs to split
|
|
18839
|
+
* one PerseusItem into several
|
|
18840
|
+
*
|
|
18841
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
18842
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
18843
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
18844
|
+
*/
|
|
18845
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
18846
|
+
const widgets = {};
|
|
18847
|
+
widgetIds.forEach(widgetId => {
|
|
18848
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
18849
|
+
if (widget) {
|
|
18850
|
+
widgets[widgetId] = widget;
|
|
18851
|
+
}
|
|
18852
|
+
});
|
|
18853
|
+
return widgets;
|
|
18854
|
+
}
|
|
18855
|
+
|
|
18722
18856
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
18723
18857
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
18724
18858
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -19150,12 +19284,35 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
19150
19284
|
isLintable: isLintable
|
|
19151
19285
|
});
|
|
19152
19286
|
|
|
19287
|
+
const {
|
|
19288
|
+
mapObject: mapObject$1
|
|
19289
|
+
} = objective_;
|
|
19290
|
+
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
19291
|
+
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19292
|
+
return mapObject$1(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
19293
|
+
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19294
|
+
const newValues = {};
|
|
19295
|
+
if (!widgetInfo.type) {
|
|
19296
|
+
// TODO: why does widget have no type?
|
|
19297
|
+
// We don't want to derive type from widget ID
|
|
19298
|
+
// see: LEMS-1845
|
|
19299
|
+
newValues.type = widgetId.split(" ")[0];
|
|
19300
|
+
}
|
|
19301
|
+
if (!widgetInfo.alignment) {
|
|
19302
|
+
newValues.alignment = "default";
|
|
19303
|
+
}
|
|
19304
|
+
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
19305
|
+
}
|
|
19306
|
+
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
19307
|
+
});
|
|
19308
|
+
}
|
|
19153
19309
|
function emptyWidgetsFunctional(widgets,
|
|
19154
19310
|
// This is a port of old code, I'm not sure why
|
|
19155
19311
|
// we need widgetIds vs the keys of the widgets object
|
|
19156
19312
|
widgetIds, userInputMap, strings, locale) {
|
|
19313
|
+
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
19157
19314
|
return widgetIds.filter(id => {
|
|
19158
|
-
const widget =
|
|
19315
|
+
const widget = upgradedWidgets[id];
|
|
19159
19316
|
if (!widget || widget.static) {
|
|
19160
19317
|
// Static widgets shouldn't count as empty
|
|
19161
19318
|
return false;
|
|
@@ -19174,12 +19331,27 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19174
19331
|
}
|
|
19175
19332
|
});
|
|
19176
19333
|
}
|
|
19334
|
+
|
|
19335
|
+
// TODO: combine scorePerseusItem with scoreWidgetsFunctional
|
|
19336
|
+
// once scorePerseusItem is the only one calling scoreWidgetsFunctional
|
|
19337
|
+
function scorePerseusItem(perseusRenderData, userInputMap,
|
|
19338
|
+
// TODO(LEMS-2461,LEMS-2391): these probably
|
|
19339
|
+
// need to be removed before we move this to the server
|
|
19340
|
+
strings, locale) {
|
|
19341
|
+
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
19342
|
+
// widget data for widgets that are not in PerseusRenderer.content,
|
|
19343
|
+
// so this checks that the widgets are being used before scoring them
|
|
19344
|
+
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
19345
|
+
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
19346
|
+
return Util.flattenScores(scores);
|
|
19347
|
+
}
|
|
19177
19348
|
function scoreWidgetsFunctional(widgets,
|
|
19178
19349
|
// This is a port of old code, I'm not sure why
|
|
19179
19350
|
// we need widgetIds vs the keys of the widgets object
|
|
19180
19351
|
widgetIds, userInputMap, strings, locale) {
|
|
19352
|
+
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
19181
19353
|
const gradedWidgetIds = widgetIds.filter(id => {
|
|
19182
|
-
const props =
|
|
19354
|
+
const props = upgradedWidgets[id];
|
|
19183
19355
|
const widgetIsGraded = (props == null ? void 0 : props.graded) == null || props.graded;
|
|
19184
19356
|
const widgetIsStatic = !!(props != null && props.static);
|
|
19185
19357
|
// Ungraded widgets or widgets set to static shouldn't be graded.
|
|
@@ -19187,14 +19359,14 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
19187
19359
|
});
|
|
19188
19360
|
const widgetScores = {};
|
|
19189
19361
|
gradedWidgetIds.forEach(id => {
|
|
19190
|
-
const widget =
|
|
19362
|
+
const widget = upgradedWidgets[id];
|
|
19191
19363
|
if (!widget) {
|
|
19192
19364
|
return;
|
|
19193
19365
|
}
|
|
19194
19366
|
const userInput = userInputMap[id];
|
|
19195
19367
|
const scorer = getWidgetScorer(widget.type);
|
|
19196
19368
|
if (widget.type === "group") {
|
|
19197
|
-
const scores = scoreWidgetsFunctional(widget.options.widgets,
|
|
19369
|
+
const scores = scoreWidgetsFunctional(widget.options.widgets, getWidgetIdsFromContent(widget.options.content), userInputMap[id], strings, locale);
|
|
19198
19370
|
widgetScores[id] = Util.flattenScores(scores);
|
|
19199
19371
|
} else if (scorer) {
|
|
19200
19372
|
widgetScores[id] = scorer(userInput, widget.options, strings, locale);
|
|
@@ -19556,31 +19728,12 @@ class Renderer extends React.Component {
|
|
|
19556
19728
|
return _extends({}, ApiOptions.defaults, this.props.apiOptions);
|
|
19557
19729
|
};
|
|
19558
19730
|
this._getInitialWidgetState = props => {
|
|
19559
|
-
const allWidgetInfo =
|
|
19731
|
+
const allWidgetInfo = getUpgradedWidgetOptions(props.widgets);
|
|
19560
19732
|
return {
|
|
19561
19733
|
widgetInfo: allWidgetInfo,
|
|
19562
19734
|
widgetProps: this._getAllWidgetsStartProps(allWidgetInfo, props)
|
|
19563
19735
|
};
|
|
19564
19736
|
};
|
|
19565
|
-
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
19566
|
-
this._getAllWidgetsInfo = props => {
|
|
19567
|
-
return mapObject(props.widgets, (widgetInfo, widgetId) => {
|
|
19568
|
-
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
19569
|
-
const newValues = {};
|
|
19570
|
-
if (!widgetInfo.type) {
|
|
19571
|
-
// TODO: why does widget have no type?
|
|
19572
|
-
// We don't want to derive type from widget ID
|
|
19573
|
-
// see: LEMS-1845
|
|
19574
|
-
newValues.type = widgetId.split(" ")[0];
|
|
19575
|
-
}
|
|
19576
|
-
if (!widgetInfo.alignment) {
|
|
19577
|
-
newValues.alignment = "default";
|
|
19578
|
-
}
|
|
19579
|
-
widgetInfo = _.extend({}, widgetInfo, newValues);
|
|
19580
|
-
}
|
|
19581
|
-
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
19582
|
-
});
|
|
19583
|
-
};
|
|
19584
19737
|
this._getAllWidgetsStartProps = (allWidgetInfo, props) => {
|
|
19585
19738
|
const {
|
|
19586
19739
|
apiOptions,
|
|
@@ -20026,7 +20179,7 @@ class Renderer extends React.Component {
|
|
|
20026
20179
|
// Instead, we center them with css magic in styles.less
|
|
20027
20180
|
// /cry(aria)
|
|
20028
20181
|
this._foundTextNodes = true;
|
|
20029
|
-
if (
|
|
20182
|
+
if (this.widgetIds.includes(node.id)) {
|
|
20030
20183
|
// We don't want to render a duplicate widget key/ref,
|
|
20031
20184
|
// as this causes problems with react (for obvious
|
|
20032
20185
|
// reasons). Instead we just notify the
|
|
@@ -20330,7 +20483,7 @@ class Renderer extends React.Component {
|
|
|
20330
20483
|
};
|
|
20331
20484
|
this.getInputPaths = () => {
|
|
20332
20485
|
const inputPaths = [];
|
|
20333
|
-
|
|
20486
|
+
this.widgetIds.forEach(widgetId => {
|
|
20334
20487
|
const widget = this.getWidgetInstance(widgetId);
|
|
20335
20488
|
if (widget && widget.getInputPaths) {
|
|
20336
20489
|
// Grab all input paths and add widgetID to the front
|
|
@@ -20338,7 +20491,7 @@ class Renderer extends React.Component {
|
|
|
20338
20491
|
// Prefix paths with their widgetID and add to collective
|
|
20339
20492
|
// list of paths.
|
|
20340
20493
|
// @ts-expect-error - TS2345 - Argument of type '(inputPath: string) => void' is not assignable to parameter of type 'CollectionIterator<FocusPath, void, readonly FocusPath[]>'.
|
|
20341
|
-
|
|
20494
|
+
widgetInputPaths.forEach(inputPath => {
|
|
20342
20495
|
const relativeInputPath = [widgetId].concat(inputPath);
|
|
20343
20496
|
inputPaths.push(relativeInputPath);
|
|
20344
20497
|
});
|
|
@@ -20467,12 +20620,20 @@ class Renderer extends React.Component {
|
|
|
20467
20620
|
this.getWidgetIds = () => {
|
|
20468
20621
|
return this.widgetIds;
|
|
20469
20622
|
};
|
|
20623
|
+
/**
|
|
20624
|
+
* @deprecated use scorePerseusItem
|
|
20625
|
+
*/
|
|
20626
|
+
this.guessAndScore = () => {
|
|
20627
|
+
const totalGuess = this.getUserInput();
|
|
20628
|
+
const totalScore = this.score();
|
|
20629
|
+
return [totalGuess, totalScore];
|
|
20630
|
+
};
|
|
20470
20631
|
this.examples = () => {
|
|
20471
20632
|
const widgetIds = this.widgetIds;
|
|
20472
|
-
const examples =
|
|
20633
|
+
const examples = widgetIds.map(widgetId => {
|
|
20473
20634
|
const widget = this.getWidgetInstance(widgetId);
|
|
20474
20635
|
return widget != null && widget.examples ? widget.examples() : null;
|
|
20475
|
-
}));
|
|
20636
|
+
}).filter(Boolean);
|
|
20476
20637
|
|
|
20477
20638
|
// no widgets with examples
|
|
20478
20639
|
if (!examples.length) {
|
|
@@ -20727,27 +20888,15 @@ class Renderer extends React.Component {
|
|
|
20727
20888
|
}
|
|
20728
20889
|
|
|
20729
20890
|
/**
|
|
20730
|
-
*
|
|
20731
|
-
*
|
|
20732
|
-
*
|
|
20733
|
-
*/
|
|
20734
|
-
scoreWidgets() {
|
|
20735
|
-
return scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20736
|
-
}
|
|
20737
|
-
|
|
20738
|
-
/**
|
|
20739
|
-
* Grades the content.
|
|
20891
|
+
* Scores the content.
|
|
20892
|
+
*
|
|
20893
|
+
* @deprecated use scorePerseusItem
|
|
20740
20894
|
*/
|
|
20741
20895
|
score() {
|
|
20742
|
-
const scores = this.
|
|
20896
|
+
const scores = scoreWidgetsFunctional(this.state.widgetInfo, this.widgetIds, this.getUserInputMap(), this.props.strings, this.context.locale);
|
|
20743
20897
|
const combinedScore = Util.flattenScores(scores);
|
|
20744
20898
|
return combinedScore;
|
|
20745
20899
|
}
|
|
20746
|
-
guessAndScore() {
|
|
20747
|
-
const totalGuess = this.getUserInput();
|
|
20748
|
-
const totalScore = this.score();
|
|
20749
|
-
return [totalGuess, totalScore];
|
|
20750
|
-
}
|
|
20751
20900
|
render() {
|
|
20752
20901
|
const apiOptions = this.getApiOptions();
|
|
20753
20902
|
if (this.reuseMarkdown) {
|
|
@@ -29327,6 +29476,9 @@ class Group extends React.Component {
|
|
|
29327
29476
|
this.change("widgets", this.rendererRef.props.widgets);
|
|
29328
29477
|
}
|
|
29329
29478
|
};
|
|
29479
|
+
|
|
29480
|
+
// TODO(LEMS-2391): replace this when there's a separate check
|
|
29481
|
+
// for valid/invalid state
|
|
29330
29482
|
const score = (_this$rendererRef12 = this.rendererRef) == null ? void 0 : _this$rendererRef12.score();
|
|
29331
29483
|
const isValid = score && score.type !== "invalid";
|
|
29332
29484
|
const isInvalid = score && score.type === "invalid";
|
|
@@ -44861,7 +45013,7 @@ const init = function init() {
|
|
|
44861
45013
|
|
|
44862
45014
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44863
45015
|
const libName = "@khanacademy/perseus";
|
|
44864
|
-
const libVersion = "
|
|
45016
|
+
const libVersion = "45.0.0";
|
|
44865
45017
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44866
45018
|
|
|
44867
45019
|
const apiVersion = {
|
|
@@ -45485,6 +45637,13 @@ class ServerItemRenderer extends React.Component {
|
|
|
45485
45637
|
this.props.onRendered(true);
|
|
45486
45638
|
}
|
|
45487
45639
|
}
|
|
45640
|
+
if (this.props.score && this.props.score !== prevProps.score) {
|
|
45641
|
+
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45642
|
+
this.setState({
|
|
45643
|
+
questionCompleted: this.props.score.correct,
|
|
45644
|
+
questionHighlightedWidgets: emptyQuestionAreaWidgets
|
|
45645
|
+
});
|
|
45646
|
+
}
|
|
45488
45647
|
}
|
|
45489
45648
|
componentWillUnmount() {
|
|
45490
45649
|
if (this.blurTimeoutID != null) {
|
|
@@ -45602,18 +45761,37 @@ class ServerItemRenderer extends React.Component {
|
|
|
45602
45761
|
return this.questionRenderer.getPromptJSON();
|
|
45603
45762
|
}
|
|
45604
45763
|
|
|
45764
|
+
/**
|
|
45765
|
+
* Returns an array of the widget `.getUserInput()` results
|
|
45766
|
+
*
|
|
45767
|
+
* TODO: can we remove this? Seems to be just for backwards
|
|
45768
|
+
* compatibility with old Perseus Chrome logging
|
|
45769
|
+
* @deprecated use getUserInput
|
|
45770
|
+
*/
|
|
45771
|
+
getUserInputLegacy() {
|
|
45772
|
+
return this.questionRenderer.getUserInput();
|
|
45773
|
+
}
|
|
45774
|
+
|
|
45775
|
+
/**
|
|
45776
|
+
* Returns an object of the widget `.getUserInput()` results
|
|
45777
|
+
*/
|
|
45778
|
+
getUserInput() {
|
|
45779
|
+
return this.questionRenderer.getUserInputMap();
|
|
45780
|
+
}
|
|
45781
|
+
|
|
45605
45782
|
/**
|
|
45606
45783
|
* Grades the item.
|
|
45784
|
+
*
|
|
45785
|
+
* @deprecated use scorePerseusItem
|
|
45607
45786
|
*/
|
|
45608
45787
|
scoreInput() {
|
|
45609
|
-
const
|
|
45610
|
-
const
|
|
45611
|
-
const score = guessAndScore[1];
|
|
45788
|
+
const guess = this.getUserInput();
|
|
45789
|
+
const score = scorePerseusItem(this.props.item.question, guess, this.context.strings, this.context.locale);
|
|
45612
45790
|
|
|
45613
45791
|
// Continue to include an empty guess for the now defunct answer area.
|
|
45614
45792
|
// TODO(alex): Check whether we rely on the format here for
|
|
45615
45793
|
// analyzing ProblemLogs. If not, remove this layer.
|
|
45616
|
-
const maxCompatGuess = [
|
|
45794
|
+
const maxCompatGuess = [this.questionRenderer.getUserInput(), []];
|
|
45617
45795
|
const keScore = Util.keScoreFromPerseusScore(score, maxCompatGuess, this.questionRenderer.getSerializedState());
|
|
45618
45796
|
const emptyQuestionAreaWidgets = this.questionRenderer.emptyWidgets();
|
|
45619
45797
|
this.setState({
|
|
@@ -47461,140 +47639,6 @@ const registerAllWidgetsForTesting = () => {
|
|
|
47461
47639
|
replaceDeprecatedWidgets();
|
|
47462
47640
|
};
|
|
47463
47641
|
|
|
47464
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
47465
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
47466
|
-
|
|
47467
|
-
/**
|
|
47468
|
-
* Regex for widget placeholders in a string.
|
|
47469
|
-
*
|
|
47470
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
47471
|
-
* Second capture group is the widget type (ex. "radio)
|
|
47472
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
47473
|
-
*/
|
|
47474
|
-
function getWidgetRegex() {
|
|
47475
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
47476
|
-
}
|
|
47477
|
-
|
|
47478
|
-
/**
|
|
47479
|
-
* Add a widget placeholder using the widget ID.
|
|
47480
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
47481
|
-
*
|
|
47482
|
-
* @param {string} id
|
|
47483
|
-
* @returns {string}
|
|
47484
|
-
*/
|
|
47485
|
-
function addWidget(id) {
|
|
47486
|
-
return `[[☃ ${id}]]`;
|
|
47487
|
-
}
|
|
47488
|
-
|
|
47489
|
-
/**
|
|
47490
|
-
* Get a widget type by a widget's ID
|
|
47491
|
-
* (please don't derive type from ID)
|
|
47492
|
-
*
|
|
47493
|
-
* @param {string} widgetId the ID of the widget
|
|
47494
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47495
|
-
* @returns {string} the widget type (ie "radio")
|
|
47496
|
-
*/
|
|
47497
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
47498
|
-
var _widget$type;
|
|
47499
|
-
const widget = widgetMap[WidgetId];
|
|
47500
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
47501
|
-
}
|
|
47502
|
-
|
|
47503
|
-
/**
|
|
47504
|
-
* Does the content have a specific type of widget?
|
|
47505
|
-
*
|
|
47506
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
47507
|
-
* @param {string} content the string to search through
|
|
47508
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47509
|
-
* @returns {boolean} if the content includes the widget type
|
|
47510
|
-
*/
|
|
47511
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
47512
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
47513
|
-
}
|
|
47514
|
-
|
|
47515
|
-
/**
|
|
47516
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
47517
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
47518
|
-
*
|
|
47519
|
-
* Content should contain Perseus widget placeholders,
|
|
47520
|
-
* which look like: '[[☃ radio 1]]'.
|
|
47521
|
-
*
|
|
47522
|
-
* @param {string} content
|
|
47523
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
47524
|
-
*/
|
|
47525
|
-
function getWidgetIdsFromContent(content) {
|
|
47526
|
-
const widgets = [];
|
|
47527
|
-
const localWidgetRegex = getWidgetRegex();
|
|
47528
|
-
let match = localWidgetRegex.exec(content);
|
|
47529
|
-
while (match !== null) {
|
|
47530
|
-
widgets.push(match[1]);
|
|
47531
|
-
match = localWidgetRegex.exec(content);
|
|
47532
|
-
}
|
|
47533
|
-
return widgets;
|
|
47534
|
-
}
|
|
47535
|
-
|
|
47536
|
-
/**
|
|
47537
|
-
* Get a list of widget IDs from content,
|
|
47538
|
-
* but only for specific widget types
|
|
47539
|
-
*
|
|
47540
|
-
* @param {string} type the type of widget (ie "radio")
|
|
47541
|
-
* @param {string} content the string to parse
|
|
47542
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
47543
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
47544
|
-
*/
|
|
47545
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
47546
|
-
const rv = [];
|
|
47547
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
47548
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
47549
|
-
const widget = widgetMap[widgetId];
|
|
47550
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
47551
|
-
rv.push(widgetId);
|
|
47552
|
-
}
|
|
47553
|
-
});
|
|
47554
|
-
return rv;
|
|
47555
|
-
}
|
|
47556
|
-
|
|
47557
|
-
/**
|
|
47558
|
-
* Pull the widget map out of ItemData
|
|
47559
|
-
*
|
|
47560
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
47561
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
47562
|
-
*/
|
|
47563
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
47564
|
-
return itemData.question.widgets;
|
|
47565
|
-
}
|
|
47566
|
-
|
|
47567
|
-
/**
|
|
47568
|
-
* Get the widget information from a WidgetMap
|
|
47569
|
-
*
|
|
47570
|
-
* @param {string} widgetId the ID of the widget
|
|
47571
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
47572
|
-
*/
|
|
47573
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
47574
|
-
var _widgetMap$widgetId;
|
|
47575
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
47576
|
-
}
|
|
47577
|
-
|
|
47578
|
-
/**
|
|
47579
|
-
* Get select widgets from a widget map.
|
|
47580
|
-
* Useful for multi-items that needs to split
|
|
47581
|
-
* one PerseusItem into several
|
|
47582
|
-
*
|
|
47583
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
47584
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
47585
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
47586
|
-
*/
|
|
47587
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
47588
|
-
const widgets = {};
|
|
47589
|
-
widgetIds.forEach(widgetId => {
|
|
47590
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
47591
|
-
if (widget) {
|
|
47592
|
-
widgets[widgetId] = widget;
|
|
47593
|
-
}
|
|
47594
|
-
});
|
|
47595
|
-
return widgets;
|
|
47596
|
-
}
|
|
47597
|
-
|
|
47598
47642
|
/**
|
|
47599
47643
|
* This function extracts the answers from the widgets.
|
|
47600
47644
|
*
|
|
@@ -49651,5 +49695,5 @@ const GrapherUtil = {
|
|
|
49651
49695
|
typeToButton
|
|
49652
49696
|
};
|
|
49653
49697
|
|
|
49654
|
-
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 };
|
|
49698
|
+
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, scorePerseusItem, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
49655
49699
|
//# sourceMappingURL=index.js.map
|