@khanacademy/perseus-core 3.2.0 → 3.4.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/data-schema.d.ts +57 -12
- package/dist/es/index.js +2968 -280
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +80 -1
- package/dist/index.js +3027 -281
- package/dist/index.js.map +1 -1
- package/dist/parse-perseus-json/error-tracking-parse-context.d.ts +9 -0
- package/dist/parse-perseus-json/exhaustive-test-tool/index.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/any.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/array.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/boolean.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/constant.d.ts +4 -0
- package/dist/parse-perseus-json/general-purpose-parsers/convert.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +21 -0
- package/dist/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/index.d.ts +17 -0
- package/dist/parse-perseus-json/general-purpose-parsers/is-object.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/nullable.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/number.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/object.d.ts +6 -0
- package/dist/parse-perseus-json/general-purpose-parsers/optional.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/pair.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +7 -0
- package/dist/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/record.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/string.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +8 -0
- package/dist/parse-perseus-json/general-purpose-parsers/trio.d.ts +2 -0
- package/dist/parse-perseus-json/general-purpose-parsers/union.d.ts +7 -0
- package/dist/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +1 -0
- package/dist/parse-perseus-json/general-purpose-parsers/unknown.d.ts +2 -0
- package/dist/parse-perseus-json/index.d.ts +45 -0
- package/dist/parse-perseus-json/object-path.d.ts +2 -0
- package/dist/parse-perseus-json/parse-failure-detail.d.ts +2 -0
- package/dist/parse-perseus-json/parse.d.ts +3 -0
- package/dist/parse-perseus-json/parser-types.d.ts +30 -0
- package/dist/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/definition-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/expression-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +16 -0
- package/dist/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/group-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/hint.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/image-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/images-map.d.ts +5 -0
- package/dist/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/passage-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/perseus-article.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/perseus-item.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/radio-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/table-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +36 -0
- package/dist/parse-perseus-json/perseus-parsers/video-widget.d.ts +3 -0
- package/dist/parse-perseus-json/perseus-parsers/widget.d.ts +7 -0
- package/dist/parse-perseus-json/perseus-parsers/widgets-map.d.ts +3 -0
- package/dist/parse-perseus-json/result.d.ts +16 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/is-real-json-parse.d.ts +1 -0
- package/dist/utils/widget-id-utils.d.ts +38 -0
- package/dist/widgets/categorizer/categorizer-util.d.ts +17 -0
- package/dist/widgets/categorizer/index.d.ts +5 -0
- package/dist/widgets/core-widget-registry.d.ts +28 -0
- package/dist/widgets/cs-program/cs-program-util.d.ts +2 -0
- package/dist/widgets/cs-program/index.d.ts +5 -0
- package/dist/widgets/definition/index.d.ts +5 -0
- package/dist/widgets/dropdown/dropdown-util.d.ts +20 -0
- package/dist/widgets/dropdown/index.d.ts +5 -0
- package/dist/widgets/explanation/index.d.ts +5 -0
- package/dist/widgets/expression/expression-upgrade.d.ts +10 -0
- package/dist/widgets/expression/expression-util.d.ts +19 -0
- package/dist/widgets/expression/index.d.ts +4 -0
- package/dist/widgets/graded-group/index.d.ts +5 -0
- package/dist/widgets/graded-group-set/index.d.ts +5 -0
- package/dist/widgets/grapher/index.d.ts +5 -0
- package/dist/widgets/group/index.d.ts +5 -0
- package/dist/widgets/iframe/index.d.ts +5 -0
- package/dist/widgets/image/index.d.ts +5 -0
- package/dist/widgets/input-number/index.d.ts +5 -0
- package/dist/widgets/interaction/index.d.ts +5 -0
- package/dist/widgets/interactive-graph/index.d.ts +5 -0
- package/dist/widgets/label-image/index.d.ts +5 -0
- package/dist/widgets/label-image/label-image-util.d.ts +19 -0
- package/dist/widgets/logic-export.types.d.ts +13 -0
- package/dist/widgets/matcher/index.d.ts +5 -0
- package/dist/widgets/matrix/index.d.ts +5 -0
- package/dist/widgets/measurer/index.d.ts +4 -0
- package/dist/widgets/measurer/measurer-upgrade.d.ts +10 -0
- package/dist/widgets/number-line/index.d.ts +5 -0
- package/dist/widgets/number-line/number-line-util.d.ts +4 -0
- package/dist/widgets/numeric-input/index.d.ts +5 -0
- package/dist/widgets/numeric-input/numeric-input-util.d.ts +19 -0
- package/dist/widgets/orderer/index.d.ts +5 -0
- package/dist/widgets/orderer/orderer-util.d.ts +16 -0
- package/dist/widgets/passage/index.d.ts +5 -0
- package/dist/widgets/passage-ref/index.d.ts +4 -0
- package/dist/widgets/passage-ref/passage-ref-upgrade.d.ts +7 -0
- package/dist/widgets/passage-ref-target/index.d.ts +5 -0
- package/dist/widgets/phet-simulation/index.d.ts +5 -0
- package/dist/widgets/plotter/index.d.ts +5 -0
- package/dist/widgets/python-program/index.d.ts +5 -0
- package/dist/widgets/radio/index.d.ts +4 -0
- package/dist/widgets/radio/radio-upgrade.d.ts +10 -0
- package/dist/widgets/sorter/index.d.ts +5 -0
- package/dist/widgets/sorter/sorter-util.d.ts +16 -0
- package/dist/widgets/table/index.d.ts +5 -0
- package/dist/widgets/upgrade.d.ts +3 -0
- package/dist/widgets/video/index.d.ts +5 -0
- package/package.json +1 -1
package/dist/es/index.js
CHANGED
|
@@ -113,7 +113,7 @@ function restArguments(func, startIndex) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Is a given variable an object?
|
|
116
|
-
function isObject(obj) {
|
|
116
|
+
function isObject$1(obj) {
|
|
117
117
|
var type = typeof obj;
|
|
118
118
|
return type === 'function' || (type === 'object' && !!obj);
|
|
119
119
|
}
|
|
@@ -227,7 +227,7 @@ function isNaN$1(obj) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Predicate-generating function. Often useful outside of Underscore.
|
|
230
|
-
function constant(value) {
|
|
230
|
+
function constant$1(value) {
|
|
231
231
|
return function() {
|
|
232
232
|
return value;
|
|
233
233
|
};
|
|
@@ -264,7 +264,7 @@ function isTypedArray(obj) {
|
|
|
264
264
|
isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false);
|
|
267
|
+
var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant$1(false);
|
|
268
268
|
|
|
269
269
|
// Internal helper to obtain the `length` property of an object.
|
|
270
270
|
var getLength = shallowProperty('length');
|
|
@@ -309,7 +309,7 @@ function collectNonEnumProps(obj, keys) {
|
|
|
309
309
|
// Retrieve the names of an object's own properties.
|
|
310
310
|
// Delegates to **ECMAScript 5**'s native `Object.keys`.
|
|
311
311
|
function keys(obj) {
|
|
312
|
-
if (!isObject(obj)) return [];
|
|
312
|
+
if (!isObject$1(obj)) return [];
|
|
313
313
|
if (nativeKeys) return nativeKeys(obj);
|
|
314
314
|
var keys = [];
|
|
315
315
|
for (var key in obj) if (has$1(obj, key)) keys.push(key);
|
|
@@ -507,7 +507,7 @@ function isEqual(a, b) {
|
|
|
507
507
|
|
|
508
508
|
// Retrieve all the enumerable property names of an object.
|
|
509
509
|
function allKeys(obj) {
|
|
510
|
-
if (!isObject(obj)) return [];
|
|
510
|
+
if (!isObject$1(obj)) return [];
|
|
511
511
|
var keys = [];
|
|
512
512
|
for (var key in obj) keys.push(key);
|
|
513
513
|
// Ahem, IE < 9.
|
|
@@ -636,7 +636,7 @@ function ctor() {
|
|
|
636
636
|
|
|
637
637
|
// An internal function for creating a new object that inherits from another.
|
|
638
638
|
function baseCreate(prototype) {
|
|
639
|
-
if (!isObject(prototype)) return {};
|
|
639
|
+
if (!isObject$1(prototype)) return {};
|
|
640
640
|
if (nativeCreate) return nativeCreate(prototype);
|
|
641
641
|
var Ctor = ctor();
|
|
642
642
|
Ctor.prototype = prototype;
|
|
@@ -656,7 +656,7 @@ function create(prototype, props) {
|
|
|
656
656
|
|
|
657
657
|
// Create a (shallow-cloned) duplicate of an object.
|
|
658
658
|
function clone(obj) {
|
|
659
|
-
if (!isObject(obj)) return obj;
|
|
659
|
+
if (!isObject$1(obj)) return obj;
|
|
660
660
|
return isArray(obj) ? obj.slice() : extend({}, obj);
|
|
661
661
|
}
|
|
662
662
|
|
|
@@ -765,7 +765,7 @@ function optimizeCb(func, context, argCount) {
|
|
|
765
765
|
function baseIteratee(value, context, argCount) {
|
|
766
766
|
if (value == null) return identity;
|
|
767
767
|
if (isFunction$1(value)) return optimizeCb(value, context, argCount);
|
|
768
|
-
if (isObject(value) && !isArray(value)) return matcher(value);
|
|
768
|
+
if (isObject$1(value) && !isArray(value)) return matcher(value);
|
|
769
769
|
return property(value);
|
|
770
770
|
}
|
|
771
771
|
|
|
@@ -1014,7 +1014,7 @@ function executeBound(sourceFunc, boundFunc, context, callingContext, args) {
|
|
|
1014
1014
|
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
|
|
1015
1015
|
var self = baseCreate(sourceFunc.prototype);
|
|
1016
1016
|
var result = sourceFunc.apply(self, args);
|
|
1017
|
-
if (isObject(result)) return result;
|
|
1017
|
+
if (isObject$1(result)) return result;
|
|
1018
1018
|
return self;
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
@@ -1776,7 +1776,7 @@ function uniq(array, isSorted, iteratee, context) {
|
|
|
1776
1776
|
|
|
1777
1777
|
// Produce an array that contains the union: each distinct element from all of
|
|
1778
1778
|
// the passed-in arrays.
|
|
1779
|
-
var union = restArguments(function(arrays) {
|
|
1779
|
+
var union$1 = restArguments(function(arrays) {
|
|
1780
1780
|
return uniq(flatten$1(arrays, true, true));
|
|
1781
1781
|
});
|
|
1782
1782
|
|
|
@@ -1816,7 +1816,7 @@ var zip = restArguments(unzip);
|
|
|
1816
1816
|
// Converts lists into objects. Pass either a single array of `[key, value]`
|
|
1817
1817
|
// pairs, or two parallel arrays of the same length -- one of keys, and one of
|
|
1818
1818
|
// the corresponding values. Passing by pairs is the reverse of `_.pairs`.
|
|
1819
|
-
function object(list, values) {
|
|
1819
|
+
function object$1(list, values) {
|
|
1820
1820
|
var result = {};
|
|
1821
1821
|
for (var i = 0, length = getLength(list); i < length; i++) {
|
|
1822
1822
|
if (values) {
|
|
@@ -1911,7 +1911,7 @@ var allExports = /*#__PURE__*/Object.freeze({
|
|
|
1911
1911
|
__proto__: null,
|
|
1912
1912
|
VERSION: VERSION,
|
|
1913
1913
|
restArguments: restArguments,
|
|
1914
|
-
isObject: isObject,
|
|
1914
|
+
isObject: isObject$1,
|
|
1915
1915
|
isNull: isNull,
|
|
1916
1916
|
isUndefined: isUndefined,
|
|
1917
1917
|
isBoolean: isBoolean,
|
|
@@ -1955,7 +1955,7 @@ var allExports = /*#__PURE__*/Object.freeze({
|
|
|
1955
1955
|
has: has,
|
|
1956
1956
|
mapObject: mapObject$1,
|
|
1957
1957
|
identity: identity,
|
|
1958
|
-
constant: constant,
|
|
1958
|
+
constant: constant$1,
|
|
1959
1959
|
noop: noop,
|
|
1960
1960
|
toPath: toPath$1,
|
|
1961
1961
|
property: property,
|
|
@@ -2044,13 +2044,13 @@ var allExports = /*#__PURE__*/Object.freeze({
|
|
|
2044
2044
|
without: without,
|
|
2045
2045
|
uniq: uniq,
|
|
2046
2046
|
unique: uniq,
|
|
2047
|
-
union: union,
|
|
2047
|
+
union: union$1,
|
|
2048
2048
|
intersection: intersection,
|
|
2049
2049
|
difference: difference,
|
|
2050
2050
|
unzip: unzip,
|
|
2051
2051
|
transpose: unzip,
|
|
2052
2052
|
zip: zip,
|
|
2053
|
-
object: object,
|
|
2053
|
+
object: object$1,
|
|
2054
2054
|
range: range,
|
|
2055
2055
|
chunk: chunk,
|
|
2056
2056
|
mixin: mixin,
|
|
@@ -2164,6 +2164,70 @@ function approximateDeepEqual(x, y) {
|
|
|
2164
2164
|
return approximateEqual(x, y);
|
|
2165
2165
|
}
|
|
2166
2166
|
|
|
2167
|
+
/**
|
|
2168
|
+
* Add a widget placeholder using the widget ID.
|
|
2169
|
+
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
2170
|
+
*
|
|
2171
|
+
* @param {string} id
|
|
2172
|
+
* @returns {string}
|
|
2173
|
+
*/
|
|
2174
|
+
function addWidget(id) {
|
|
2175
|
+
return `[[☃ ${id}]]`;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Regex for widget placeholders in a string.
|
|
2180
|
+
*
|
|
2181
|
+
* First capture group is the widget ID (ex. 'radio 1')
|
|
2182
|
+
* Second capture group is the widget type (ex. "radio)
|
|
2183
|
+
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
2184
|
+
*/
|
|
2185
|
+
function getWidgetRegex() {
|
|
2186
|
+
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* Extract all widget IDs, which includes the widget type and instance number.
|
|
2191
|
+
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
2192
|
+
*
|
|
2193
|
+
* Content should contain Perseus widget placeholders,
|
|
2194
|
+
* which look like: '[[☃ radio 1]]'.
|
|
2195
|
+
*
|
|
2196
|
+
* @param {string} content
|
|
2197
|
+
* @returns {ReadonlyArray<string>} widgetIds
|
|
2198
|
+
*/
|
|
2199
|
+
function getWidgetIdsFromContent(content) {
|
|
2200
|
+
const widgets = [];
|
|
2201
|
+
const localWidgetRegex = getWidgetRegex();
|
|
2202
|
+
let match = localWidgetRegex.exec(content);
|
|
2203
|
+
while (match !== null) {
|
|
2204
|
+
widgets.push(match[1]);
|
|
2205
|
+
match = localWidgetRegex.exec(content);
|
|
2206
|
+
}
|
|
2207
|
+
return widgets;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
/**
|
|
2211
|
+
* Get a list of widget IDs from content,
|
|
2212
|
+
* but only for specific widget types
|
|
2213
|
+
*
|
|
2214
|
+
* @param {string} type the type of widget (ie "radio")
|
|
2215
|
+
* @param {string} content the string to parse
|
|
2216
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
2217
|
+
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
2218
|
+
*/
|
|
2219
|
+
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
2220
|
+
const rv = [];
|
|
2221
|
+
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
2222
|
+
widgetIdsInContent.forEach(widgetId => {
|
|
2223
|
+
const widget = widgetMap[widgetId];
|
|
2224
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
2225
|
+
rv.push(widgetId);
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
return rv;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2167
2231
|
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
2168
2232
|
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
2169
2233
|
|
|
@@ -2578,341 +2642,2965 @@ var grapherUtil = /*#__PURE__*/Object.freeze({
|
|
|
2578
2642
|
functionForType: functionForType
|
|
2579
2643
|
});
|
|
2580
2644
|
|
|
2581
|
-
|
|
2582
|
-
const
|
|
2583
|
-
const
|
|
2584
|
-
|
|
2645
|
+
function isRealJSONParse(jsonParse) {
|
|
2646
|
+
const randomPhrase = buildRandomPhrase();
|
|
2647
|
+
const randomHintPhrase = buildRandomPhrase();
|
|
2648
|
+
const randomString = buildRandomString();
|
|
2649
|
+
const testingObject = JSON.stringify({
|
|
2650
|
+
answerArea: {
|
|
2651
|
+
calculator: false,
|
|
2652
|
+
chi2Table: false,
|
|
2653
|
+
financialCalculatorMonthlyPayment: false,
|
|
2654
|
+
financialCalculatorTimeToPayOff: false,
|
|
2655
|
+
financialCalculatorTotalAmount: false,
|
|
2656
|
+
periodicTable: false,
|
|
2657
|
+
periodicTableWithKey: false,
|
|
2658
|
+
tTable: false,
|
|
2659
|
+
zTable: false
|
|
2660
|
+
},
|
|
2661
|
+
hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
|
|
2662
|
+
itemDataVersion: {
|
|
2663
|
+
major: 0,
|
|
2664
|
+
minor: 1
|
|
2665
|
+
},
|
|
2666
|
+
question: {
|
|
2667
|
+
content: `${randomPhrase}`,
|
|
2668
|
+
images: {},
|
|
2669
|
+
widgets: {
|
|
2670
|
+
expression1: {
|
|
2671
|
+
alignment: "default",
|
|
2672
|
+
graded: false,
|
|
2673
|
+
options: {
|
|
2674
|
+
answerForms: [{
|
|
2675
|
+
considered: "wrong",
|
|
2676
|
+
form: false,
|
|
2677
|
+
key: 0,
|
|
2678
|
+
simplify: false,
|
|
2679
|
+
value: `${randomString}`
|
|
2680
|
+
}],
|
|
2681
|
+
ariaLabel: "Answer",
|
|
2682
|
+
buttonSets: ["basic"],
|
|
2683
|
+
functions: ["f", "g", "h"],
|
|
2684
|
+
static: true,
|
|
2685
|
+
times: false,
|
|
2686
|
+
visibleLabel: "Answer"
|
|
2687
|
+
},
|
|
2688
|
+
static: true,
|
|
2689
|
+
type: "expression",
|
|
2690
|
+
version: {
|
|
2691
|
+
major: 1,
|
|
2692
|
+
minor: 0
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
2699
|
+
const parsedTestJSON = jsonParse(testJSON);
|
|
2700
|
+
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
2701
|
+
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
2702
|
+
}
|
|
2703
|
+
function buildRandomString(capitalize = false) {
|
|
2704
|
+
let randomString = "";
|
|
2705
|
+
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
2706
|
+
for (let i = 0; i < randomLength; i++) {
|
|
2707
|
+
const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
2708
|
+
randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
|
|
2709
|
+
}
|
|
2710
|
+
return randomString;
|
|
2711
|
+
}
|
|
2712
|
+
function buildRandomPhrase() {
|
|
2713
|
+
const phrases = [];
|
|
2714
|
+
const randomLength = Math.floor(Math.random() * 10) + 5;
|
|
2715
|
+
for (let i = 0; i < randomLength; i++) {
|
|
2716
|
+
phrases.push(buildRandomString(i === 0));
|
|
2717
|
+
}
|
|
2718
|
+
const modifierStart = ["**", "$"];
|
|
2719
|
+
const modifierEnd = ["**", "$"];
|
|
2720
|
+
const modifierIndex = Math.floor(Math.random() * modifierStart.length);
|
|
2721
|
+
return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
|
|
2722
|
+
}
|
|
2723
|
+
function buildTestData(testObject) {
|
|
2724
|
+
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
2725
|
+
}
|
|
2585
2726
|
|
|
2586
|
-
|
|
2587
|
-
* @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
|
|
2588
|
-
*/
|
|
2589
|
-
const Errors = Object.freeze({
|
|
2590
|
-
/**
|
|
2591
|
-
* @property {ErrorKind} Unknown The kind of error is not known.
|
|
2592
|
-
*/
|
|
2593
|
-
Unknown: "Unknown",
|
|
2594
|
-
/**
|
|
2595
|
-
* @property {ErrorKind} Internal The error is internal to the executing code.
|
|
2596
|
-
*/
|
|
2597
|
-
Internal: "Internal",
|
|
2598
|
-
/**
|
|
2599
|
-
* @property {ErrorKind} InvalidInput There was a problem with the provided
|
|
2600
|
-
* input, such as the wrong format or a null value.
|
|
2601
|
-
*/
|
|
2602
|
-
InvalidInput: "InvalidInput",
|
|
2603
|
-
/**
|
|
2604
|
-
* @property {ErrorKind} NotAllowed There was a problem due to the state of
|
|
2605
|
-
* the system not matching the requested operation or input. For example,
|
|
2606
|
-
* trying to create a username that is valid, but is already taken by
|
|
2607
|
-
* another user. Use {@link InvalidInput} instead when the input isn't
|
|
2608
|
-
* valid regardless of the state of the system. Use {@link NotFound} when
|
|
2609
|
-
* the failure is due to not being able to find a resource.
|
|
2610
|
-
*/
|
|
2611
|
-
NotAllowed: "NotAllowed",
|
|
2612
|
-
/**
|
|
2613
|
-
* @property {ErrorKind} TransientService There was a problem when making a
|
|
2614
|
-
* request to a service.
|
|
2615
|
-
*/
|
|
2616
|
-
TransientService: "TransientService",
|
|
2617
|
-
/**
|
|
2618
|
-
* @property {ErrorKind} Service There was a non-transient problem when
|
|
2619
|
-
* making a request to service.
|
|
2620
|
-
*/
|
|
2621
|
-
Service: "Service"
|
|
2622
|
-
});
|
|
2727
|
+
process.env.NODE_ENV === 'production';
|
|
2623
2728
|
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2729
|
+
function success(value) {
|
|
2730
|
+
return {
|
|
2731
|
+
type: "success",
|
|
2732
|
+
value
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
function failure(detail) {
|
|
2736
|
+
return {
|
|
2737
|
+
type: "failure",
|
|
2738
|
+
detail
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
function isFailure(result) {
|
|
2742
|
+
return result.type === "failure";
|
|
2743
|
+
}
|
|
2744
|
+
function isSuccess(result) {
|
|
2745
|
+
return result.type === "success";
|
|
2746
|
+
}
|
|
2627
2747
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2748
|
+
// Result's `all` function is similar to Promise.all: given an array of
|
|
2749
|
+
// results, it returns success if all succeeded, and failure if any failed.
|
|
2750
|
+
function all(results, combineFailureDetails = a => a) {
|
|
2751
|
+
const values = [];
|
|
2752
|
+
const failureDetails = [];
|
|
2753
|
+
for (const result of results) {
|
|
2754
|
+
if (result.type === "success") {
|
|
2755
|
+
values.push(result.value);
|
|
2756
|
+
} else {
|
|
2757
|
+
failureDetails.push(result.detail);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
if (failureDetails.length > 0) {
|
|
2761
|
+
return failure(failureDetails.reduce(combineFailureDetails));
|
|
2635
2762
|
}
|
|
2763
|
+
return success(values);
|
|
2636
2764
|
}
|
|
2637
2765
|
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
*
|
|
2660
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
2661
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
2662
|
-
* introduce hard-to-diagnose bugs.
|
|
2663
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
2664
|
-
* the new format _as well as_ the old format.
|
|
2665
|
-
*/
|
|
2666
|
-
|
|
2667
|
-
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
2766
|
+
class ErrorTrackingParseContext {
|
|
2767
|
+
constructor(path) {
|
|
2768
|
+
this.path = path;
|
|
2769
|
+
}
|
|
2770
|
+
failure(expected, badValue) {
|
|
2771
|
+
return failure([{
|
|
2772
|
+
expected: wrapInArray(expected),
|
|
2773
|
+
badValue,
|
|
2774
|
+
path: this.path
|
|
2775
|
+
}]);
|
|
2776
|
+
}
|
|
2777
|
+
forSubtree(key) {
|
|
2778
|
+
return new ErrorTrackingParseContext([...this.path, key]);
|
|
2779
|
+
}
|
|
2780
|
+
success(value) {
|
|
2781
|
+
return success(value);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
function wrapInArray(a) {
|
|
2785
|
+
return Array.isArray(a) ? a : [a];
|
|
2786
|
+
}
|
|
2668
2787
|
|
|
2669
|
-
|
|
2788
|
+
function formatPath(path) {
|
|
2789
|
+
return "(root)" + path.map(formatPathSegment).join("");
|
|
2790
|
+
}
|
|
2791
|
+
function formatPathSegment(segment) {
|
|
2792
|
+
if (typeof segment === "string") {
|
|
2793
|
+
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
2794
|
+
}
|
|
2795
|
+
return `[${segment.toString()}]`;
|
|
2796
|
+
}
|
|
2797
|
+
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
2670
2798
|
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
* declare module "@khanacademy/perseus" {
|
|
2693
|
-
* interface PerseusWidgetTypes {
|
|
2694
|
-
* // A new widget
|
|
2695
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
2696
|
-
*
|
|
2697
|
-
* // A deprecated widget
|
|
2698
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
2699
|
-
* }
|
|
2700
|
-
* }
|
|
2701
|
-
*
|
|
2702
|
-
* // The new widget's options definition
|
|
2703
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
2704
|
-
*
|
|
2705
|
-
* // The deprecated widget's options definition
|
|
2706
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
2707
|
-
* ```
|
|
2708
|
-
*
|
|
2709
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
2710
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
2711
|
-
*
|
|
2712
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
2713
|
-
*/
|
|
2799
|
+
function message(failure) {
|
|
2800
|
+
const expected = conjoin(failure.expected);
|
|
2801
|
+
const path = formatPath(failure.path);
|
|
2802
|
+
const badValue = JSON.stringify(failure.badValue);
|
|
2803
|
+
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
2804
|
+
}
|
|
2805
|
+
function conjoin(items) {
|
|
2806
|
+
switch (items.length) {
|
|
2807
|
+
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
2808
|
+
case 1:
|
|
2809
|
+
return items[0];
|
|
2810
|
+
case 2:
|
|
2811
|
+
return items.join(" or ");
|
|
2812
|
+
default:
|
|
2813
|
+
{
|
|
2814
|
+
const allButLast = items.slice(0, items.length - 1);
|
|
2815
|
+
const last = items[items.length - 1];
|
|
2816
|
+
return allButLast.join(", ") + ", or " + last;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2714
2820
|
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
* the case of this map.
|
|
2723
|
-
*
|
|
2724
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
2725
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
2726
|
-
*/
|
|
2821
|
+
function parse(value, parser) {
|
|
2822
|
+
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
2823
|
+
if (isFailure(result)) {
|
|
2824
|
+
return failure(result.detail.map(message).join("; "));
|
|
2825
|
+
}
|
|
2826
|
+
return result;
|
|
2827
|
+
}
|
|
2727
2828
|
|
|
2728
|
-
|
|
2729
|
-
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
2730
|
-
* `ServerItemRenderer` and the layout is pre-set.
|
|
2731
|
-
*
|
|
2732
|
-
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
2733
|
-
* area.
|
|
2734
|
-
*/
|
|
2829
|
+
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
2735
2830
|
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2831
|
+
function array(elementParser) {
|
|
2832
|
+
return (rawValue, ctx) => {
|
|
2833
|
+
if (!Array.isArray(rawValue)) {
|
|
2834
|
+
return ctx.failure("array", rawValue);
|
|
2835
|
+
}
|
|
2836
|
+
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
2837
|
+
return all(elementResults, concat);
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function concat(a, b) {
|
|
2841
|
+
return [...a, ...b];
|
|
2842
|
+
}
|
|
2740
2843
|
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
"
|
|
2746
|
-
|
|
2747
|
-
"financialCalculatorMonthlyPayment",
|
|
2748
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
2749
|
-
"financialCalculatorTotalAmount",
|
|
2750
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
2751
|
-
"financialCalculatorTimeToPayOff",
|
|
2752
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
2753
|
-
"periodicTable",
|
|
2754
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
2755
|
-
"periodicTableWithKey",
|
|
2756
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
2757
|
-
"tTable",
|
|
2758
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
2759
|
-
"zTable"];
|
|
2844
|
+
function boolean(rawValue, ctx) {
|
|
2845
|
+
if (typeof rawValue === "boolean") {
|
|
2846
|
+
return ctx.success(rawValue);
|
|
2847
|
+
}
|
|
2848
|
+
return ctx.failure("boolean", rawValue);
|
|
2849
|
+
}
|
|
2760
2850
|
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2851
|
+
function constant(acceptedValue) {
|
|
2852
|
+
return (rawValue, ctx) => {
|
|
2853
|
+
if (rawValue !== acceptedValue) {
|
|
2854
|
+
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
2855
|
+
}
|
|
2856
|
+
return ctx.success(acceptedValue);
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2765
2859
|
|
|
2766
|
-
|
|
2860
|
+
function enumeration(...acceptedValues) {
|
|
2861
|
+
return (rawValue, ctx) => {
|
|
2862
|
+
if (typeof rawValue === "string") {
|
|
2863
|
+
const index = acceptedValues.indexOf(rawValue);
|
|
2864
|
+
if (index > -1) {
|
|
2865
|
+
return ctx.success(acceptedValues[index]);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
2869
|
+
return ctx.failure(expected, rawValue);
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2767
2872
|
|
|
2768
|
-
|
|
2873
|
+
function isObject(x) {
|
|
2874
|
+
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
2875
|
+
}
|
|
2769
2876
|
|
|
2770
|
-
|
|
2877
|
+
function nullable(parseValue) {
|
|
2878
|
+
return (rawValue, ctx) => {
|
|
2879
|
+
if (rawValue === null) {
|
|
2880
|
+
return ctx.success(rawValue);
|
|
2881
|
+
}
|
|
2882
|
+
return parseValue(rawValue, ctx);
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2771
2885
|
|
|
2772
|
-
|
|
2886
|
+
const number = (rawValue, ctx) => {
|
|
2887
|
+
if (typeof rawValue === "number") {
|
|
2888
|
+
return ctx.success(rawValue);
|
|
2889
|
+
}
|
|
2890
|
+
return ctx.failure("number", rawValue);
|
|
2891
|
+
};
|
|
2773
2892
|
|
|
2774
|
-
|
|
2893
|
+
function _extends() {
|
|
2894
|
+
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
2895
|
+
for (var e = 1; e < arguments.length; e++) {
|
|
2896
|
+
var t = arguments[e];
|
|
2897
|
+
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
2898
|
+
}
|
|
2899
|
+
return n;
|
|
2900
|
+
}, _extends.apply(null, arguments);
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
function object(schema) {
|
|
2904
|
+
return (rawValue, ctx) => {
|
|
2905
|
+
if (!isObject(rawValue)) {
|
|
2906
|
+
return ctx.failure("object", rawValue);
|
|
2907
|
+
}
|
|
2908
|
+
const ret = _extends({}, rawValue);
|
|
2909
|
+
const mismatches = [];
|
|
2910
|
+
for (const [prop, propParser] of Object.entries(schema)) {
|
|
2911
|
+
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
2912
|
+
if (isSuccess(result)) {
|
|
2913
|
+
if (result.value !== undefined || prop in rawValue) {
|
|
2914
|
+
ret[prop] = result.value;
|
|
2915
|
+
}
|
|
2916
|
+
} else {
|
|
2917
|
+
mismatches.push(...result.detail);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (mismatches.length > 0) {
|
|
2921
|
+
return failure(mismatches);
|
|
2922
|
+
}
|
|
2923
|
+
return ctx.success(ret);
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
function optional(parseValue) {
|
|
2928
|
+
return (rawValue, ctx) => {
|
|
2929
|
+
if (rawValue === undefined) {
|
|
2930
|
+
return ctx.success(rawValue);
|
|
2931
|
+
}
|
|
2932
|
+
return parseValue(rawValue, ctx);
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
function pair(parseA, parseB) {
|
|
2937
|
+
return (rawValue, ctx) => {
|
|
2938
|
+
if (!Array.isArray(rawValue)) {
|
|
2939
|
+
return ctx.failure("array", rawValue);
|
|
2940
|
+
}
|
|
2941
|
+
if (rawValue.length !== 2) {
|
|
2942
|
+
return ctx.failure("array of length 2", rawValue);
|
|
2943
|
+
}
|
|
2944
|
+
const [rawA, rawB] = rawValue;
|
|
2945
|
+
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
2946
|
+
if (isFailure(resultA)) {
|
|
2947
|
+
return resultA;
|
|
2948
|
+
}
|
|
2949
|
+
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
2950
|
+
if (isFailure(resultB)) {
|
|
2951
|
+
return resultB;
|
|
2952
|
+
}
|
|
2953
|
+
return ctx.success([resultA.value, resultB.value]);
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
function pipeParsers(p) {
|
|
2958
|
+
return new ParserPipeline(p);
|
|
2959
|
+
}
|
|
2960
|
+
class ParserPipeline {
|
|
2961
|
+
constructor(parser) {
|
|
2962
|
+
this.parser = parser;
|
|
2963
|
+
}
|
|
2964
|
+
then(nextParser) {
|
|
2965
|
+
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
function composeParsers(parserA, parserB) {
|
|
2969
|
+
return (rawValue, ctx) => {
|
|
2970
|
+
const partialResult = parserA(rawValue, ctx);
|
|
2971
|
+
if (isFailure(partialResult)) {
|
|
2972
|
+
return partialResult;
|
|
2973
|
+
}
|
|
2974
|
+
return parserB(partialResult.value, ctx);
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
function record(parseKey, parseValue) {
|
|
2979
|
+
return (rawValue, ctx) => {
|
|
2980
|
+
if (!isObject(rawValue)) {
|
|
2981
|
+
return ctx.failure("object", rawValue);
|
|
2982
|
+
}
|
|
2983
|
+
const result = {};
|
|
2984
|
+
const mismatches = [];
|
|
2985
|
+
for (const [key, value] of Object.entries(rawValue)) {
|
|
2986
|
+
const entryCtx = ctx.forSubtree(key);
|
|
2987
|
+
const keyResult = parseKey(key, entryCtx);
|
|
2988
|
+
if (isFailure(keyResult)) {
|
|
2989
|
+
mismatches.push(...keyResult.detail);
|
|
2990
|
+
}
|
|
2991
|
+
const valueResult = parseValue(value, entryCtx);
|
|
2992
|
+
if (isFailure(valueResult)) {
|
|
2993
|
+
mismatches.push(...valueResult.detail);
|
|
2994
|
+
}
|
|
2995
|
+
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
2996
|
+
result[keyResult.value] = valueResult.value;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
if (mismatches.length > 0) {
|
|
3000
|
+
return failure(mismatches);
|
|
3001
|
+
}
|
|
3002
|
+
return ctx.success(result);
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
const string = (rawValue, ctx) => {
|
|
3007
|
+
if (typeof rawValue === "string") {
|
|
3008
|
+
return ctx.success(rawValue);
|
|
3009
|
+
}
|
|
3010
|
+
return ctx.failure("string", rawValue);
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
function trio(parseA, parseB, parseC) {
|
|
3014
|
+
return (rawValue, ctx) => {
|
|
3015
|
+
if (!Array.isArray(rawValue)) {
|
|
3016
|
+
return ctx.failure("array", rawValue);
|
|
3017
|
+
}
|
|
3018
|
+
if (rawValue.length !== 3) {
|
|
3019
|
+
return ctx.failure("array of length 3", rawValue);
|
|
3020
|
+
}
|
|
3021
|
+
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
3022
|
+
if (isFailure(resultA)) {
|
|
3023
|
+
return resultA;
|
|
3024
|
+
}
|
|
3025
|
+
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
3026
|
+
if (isFailure(resultB)) {
|
|
3027
|
+
return resultB;
|
|
3028
|
+
}
|
|
3029
|
+
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
3030
|
+
if (isFailure(resultC)) {
|
|
3031
|
+
return resultC;
|
|
3032
|
+
}
|
|
3033
|
+
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
function union(parseBranch) {
|
|
3038
|
+
return new UnionBuilder(parseBranch);
|
|
3039
|
+
}
|
|
3040
|
+
class UnionBuilder {
|
|
3041
|
+
constructor(parser) {
|
|
3042
|
+
this.parser = parser;
|
|
3043
|
+
}
|
|
3044
|
+
or(newBranch) {
|
|
3045
|
+
return new UnionBuilder(either(this.parser, newBranch));
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
function either(parseA, parseB) {
|
|
3049
|
+
return (rawValue, ctx) => {
|
|
3050
|
+
const resultA = parseA(rawValue, ctx);
|
|
3051
|
+
if (isSuccess(resultA)) {
|
|
3052
|
+
return resultA;
|
|
3053
|
+
}
|
|
3054
|
+
return parseB(rawValue, ctx);
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function defaulted(parser, fallback) {
|
|
3059
|
+
return (rawValue, ctx) => {
|
|
3060
|
+
if (rawValue == null) {
|
|
3061
|
+
return success(fallback(rawValue));
|
|
3062
|
+
}
|
|
3063
|
+
return parser(rawValue, ctx);
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
const parseImages = defaulted(record(string, object({
|
|
3068
|
+
width: number,
|
|
3069
|
+
height: number
|
|
3070
|
+
})), () => ({}));
|
|
3071
|
+
|
|
3072
|
+
function parseWidget(parseType, parseOptions) {
|
|
3073
|
+
return object({
|
|
3074
|
+
type: parseType,
|
|
3075
|
+
static: optional(boolean),
|
|
3076
|
+
graded: optional(boolean),
|
|
3077
|
+
alignment: optional(string),
|
|
3078
|
+
options: parseOptions,
|
|
3079
|
+
key: optional(number),
|
|
3080
|
+
version: optional(object({
|
|
3081
|
+
major: number,
|
|
3082
|
+
minor: number
|
|
3083
|
+
}))
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
3087
|
+
return object({
|
|
3088
|
+
type: parseType,
|
|
3089
|
+
static: optional(boolean),
|
|
3090
|
+
graded: optional(boolean),
|
|
3091
|
+
alignment: optional(string),
|
|
3092
|
+
options: parseOptions,
|
|
3093
|
+
key: optional(number),
|
|
3094
|
+
version: parseVersion
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
3099
|
+
items: array(string),
|
|
3100
|
+
categories: array(string),
|
|
3101
|
+
randomizeItems: defaulted(boolean, () => false),
|
|
3102
|
+
static: defaulted(boolean, () => false),
|
|
3103
|
+
values: array(defaulted(number, () => 0)),
|
|
3104
|
+
highlightLint: optional(boolean),
|
|
3105
|
+
linterContext: optional(object({
|
|
3106
|
+
contentType: string,
|
|
3107
|
+
paths: array(string),
|
|
3108
|
+
stack: array(string)
|
|
3109
|
+
}))
|
|
3110
|
+
}));
|
|
3111
|
+
|
|
3112
|
+
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
3113
|
+
programID: string,
|
|
3114
|
+
programType: any,
|
|
3115
|
+
settings: array(object({
|
|
3116
|
+
name: string,
|
|
3117
|
+
value: string
|
|
3118
|
+
})),
|
|
3119
|
+
showEditor: boolean,
|
|
3120
|
+
showButtons: boolean,
|
|
3121
|
+
height: number,
|
|
3122
|
+
static: defaulted(boolean, () => false)
|
|
3123
|
+
}));
|
|
3124
|
+
|
|
3125
|
+
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
3126
|
+
togglePrompt: string,
|
|
3127
|
+
definition: string,
|
|
3128
|
+
static: defaulted(boolean, () => false)
|
|
3129
|
+
}));
|
|
3130
|
+
|
|
3131
|
+
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
3132
|
+
placeholder: defaulted(string, () => ""),
|
|
3133
|
+
ariaLabel: optional(string),
|
|
3134
|
+
visibleLabel: optional(string),
|
|
3135
|
+
static: defaulted(boolean, () => false),
|
|
3136
|
+
choices: array(object({
|
|
3137
|
+
content: string,
|
|
3138
|
+
correct: boolean
|
|
3139
|
+
}))
|
|
3140
|
+
}));
|
|
3141
|
+
|
|
3142
|
+
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
3143
|
+
showPrompt: string,
|
|
3144
|
+
hidePrompt: string,
|
|
3145
|
+
explanation: string,
|
|
3146
|
+
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
3147
|
+
// referenced before it is defined. There is an import cycle between
|
|
3148
|
+
// this file and widgets-map.ts that could cause it to be undefined.
|
|
3149
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
3150
|
+
static: defaulted(boolean, () => false)
|
|
3151
|
+
}));
|
|
3152
|
+
|
|
3153
|
+
// Given a function, creates a PartialParser that converts one type to another
|
|
3154
|
+
// using that function. The returned parser never fails.
|
|
3155
|
+
function convert(f) {
|
|
3156
|
+
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/**
|
|
3160
|
+
* Creates a parser for a widget options type with multiple major versions. Old
|
|
3161
|
+
* versions are migrated to the latest version. The parse fails if the input
|
|
3162
|
+
* data does not match any of the versions.
|
|
3163
|
+
*
|
|
3164
|
+
* @example
|
|
3165
|
+
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
3166
|
+
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
3167
|
+
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
3168
|
+
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
3169
|
+
* .parser;
|
|
3170
|
+
*
|
|
3171
|
+
* @param latestMajorVersion the latest major version of the widget options.
|
|
3172
|
+
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
3173
|
+
* options.
|
|
3174
|
+
* @returns a builder object, to which migrations from earlier versions can be
|
|
3175
|
+
* added. Migrations must be added in "reverse chronological" order as in the
|
|
3176
|
+
* example above.
|
|
3177
|
+
*/
|
|
3178
|
+
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
3179
|
+
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
3180
|
+
}
|
|
3181
|
+
class VersionedWidgetOptionsParserBuilder {
|
|
3182
|
+
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
3183
|
+
this.parser = void 0;
|
|
3184
|
+
this.migrateToLatest = migrateToLatest;
|
|
3185
|
+
this.parseOtherVersions = parseOtherVersions;
|
|
3186
|
+
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
3187
|
+
this.parser = (raw, ctx) => {
|
|
3188
|
+
if (!isObject(raw)) {
|
|
3189
|
+
return ctx.failure("object", raw);
|
|
3190
|
+
}
|
|
3191
|
+
const versionParseResult = parseVersionedObject(raw, ctx);
|
|
3192
|
+
if (isFailure(versionParseResult)) {
|
|
3193
|
+
return versionParseResult;
|
|
3194
|
+
}
|
|
3195
|
+
if (versionParseResult.value.version.major !== majorVersion) {
|
|
3196
|
+
return this.parseOtherVersions(raw, ctx);
|
|
3197
|
+
}
|
|
3198
|
+
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
/**
|
|
3203
|
+
* Add a migration from an old version of the widget options.
|
|
3204
|
+
*/
|
|
3205
|
+
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
3206
|
+
const parseOtherVersions = this.parser;
|
|
3207
|
+
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
3208
|
+
return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
const parseVersionedObject = object({
|
|
3212
|
+
version: defaulted(object({
|
|
3213
|
+
major: number,
|
|
3214
|
+
minor: number
|
|
3215
|
+
}), () => ({
|
|
3216
|
+
major: 0,
|
|
3217
|
+
minor: 0
|
|
3218
|
+
}))
|
|
3219
|
+
});
|
|
3220
|
+
|
|
3221
|
+
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
3222
|
+
const parsePossiblyInvalidAnswerForm = object({
|
|
3223
|
+
// `value` is the possibly invalid part of this. It should always be a
|
|
3224
|
+
// string, but some answer forms don't have it. The Expression widget
|
|
3225
|
+
// ignores invalid values, so we can safely filter them out during parsing.
|
|
3226
|
+
value: optional(string),
|
|
3227
|
+
form: defaulted(boolean, () => false),
|
|
3228
|
+
simplify: defaulted(boolean, () => false),
|
|
3229
|
+
considered: enumeration("correct", "wrong", "ungraded"),
|
|
3230
|
+
key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
|
|
3231
|
+
});
|
|
3232
|
+
function removeInvalidAnswerForms(possiblyInvalid) {
|
|
3233
|
+
const valid = [];
|
|
3234
|
+
for (const answerForm of possiblyInvalid) {
|
|
3235
|
+
const {
|
|
3236
|
+
value
|
|
3237
|
+
} = answerForm;
|
|
3238
|
+
if (value != null) {
|
|
3239
|
+
// Copying the object seems to be needed to make TypeScript happy
|
|
3240
|
+
valid.push(_extends({}, answerForm, {
|
|
3241
|
+
value
|
|
3242
|
+
}));
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
return valid;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// NOTE(benchristel): I copied the default buttonSets from
|
|
3249
|
+
// expression.tsx. See the parse-perseus-json/README.md for
|
|
3250
|
+
// an explanation of why we want to duplicate the default here.
|
|
3251
|
+
const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
3252
|
+
const version1 = object({
|
|
3253
|
+
major: constant(1),
|
|
3254
|
+
minor: number
|
|
3255
|
+
});
|
|
3256
|
+
const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
|
|
3257
|
+
answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
|
|
3258
|
+
functions: array(string),
|
|
3259
|
+
times: boolean,
|
|
3260
|
+
visibleLabel: optional(string),
|
|
3261
|
+
ariaLabel: optional(string),
|
|
3262
|
+
buttonSets: parseButtonSets,
|
|
3263
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
3264
|
+
}));
|
|
3265
|
+
const version0 = optional(object({
|
|
3266
|
+
major: constant(0),
|
|
3267
|
+
minor: number
|
|
3268
|
+
}));
|
|
3269
|
+
const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
|
|
3270
|
+
functions: array(string),
|
|
3271
|
+
times: boolean,
|
|
3272
|
+
visibleLabel: optional(string),
|
|
3273
|
+
ariaLabel: optional(string),
|
|
3274
|
+
form: boolean,
|
|
3275
|
+
simplify: boolean,
|
|
3276
|
+
value: string,
|
|
3277
|
+
buttonSets: parseButtonSets,
|
|
3278
|
+
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
3279
|
+
}));
|
|
3280
|
+
function migrateV0ToV1(widget) {
|
|
3281
|
+
const {
|
|
3282
|
+
options
|
|
3283
|
+
} = widget;
|
|
3284
|
+
return _extends({}, widget, {
|
|
3285
|
+
version: {
|
|
3286
|
+
major: 1,
|
|
3287
|
+
minor: 0
|
|
3288
|
+
},
|
|
3289
|
+
options: {
|
|
3290
|
+
times: options.times,
|
|
3291
|
+
buttonSets: options.buttonSets,
|
|
3292
|
+
functions: options.functions,
|
|
3293
|
+
buttonsVisible: options.buttonsVisible,
|
|
3294
|
+
visibleLabel: options.visibleLabel,
|
|
3295
|
+
ariaLabel: options.ariaLabel,
|
|
3296
|
+
answerForms: [{
|
|
3297
|
+
considered: "correct",
|
|
3298
|
+
form: options.form,
|
|
3299
|
+
simplify: options.simplify,
|
|
3300
|
+
value: options.value
|
|
3301
|
+
}]
|
|
3302
|
+
}
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
3306
|
+
|
|
3307
|
+
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
3308
|
+
const parseGradedGroupWidgetOptions = object({
|
|
3309
|
+
title: defaulted(string, () => ""),
|
|
3310
|
+
hasHint: optional(nullable(boolean)),
|
|
3311
|
+
// This module has an import cycle with parsePerseusRenderer.
|
|
3312
|
+
// The anonymous function below ensures that we don't try to access
|
|
3313
|
+
// parsePerseusRenderer before it's defined.
|
|
3314
|
+
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
3315
|
+
content: string,
|
|
3316
|
+
// This module has an import cycle with parseWidgetsMap.
|
|
3317
|
+
// The anonymous function below ensures that we don't try to access
|
|
3318
|
+
// parseWidgetsMap before it's defined.
|
|
3319
|
+
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
3320
|
+
widgetEnabled: optional(nullable(boolean)),
|
|
3321
|
+
immutableWidgets: optional(nullable(boolean)),
|
|
3322
|
+
images: record(string, object({
|
|
3323
|
+
width: number,
|
|
3324
|
+
height: number
|
|
3325
|
+
}))
|
|
3326
|
+
});
|
|
3327
|
+
const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
|
|
3328
|
+
|
|
3329
|
+
const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
|
|
3330
|
+
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
3331
|
+
}));
|
|
3332
|
+
|
|
3333
|
+
/**
|
|
3334
|
+
* discriminatedUnion() should be preferred over union() when parsing a
|
|
3335
|
+
* discriminated union type, because discriminatedUnion() produces more
|
|
3336
|
+
* understandable failure messages. It takes the discriminant as the source of
|
|
3337
|
+
* truth for which variant is to be parsed, and expects the other data to match
|
|
3338
|
+
* that variant.
|
|
3339
|
+
*/
|
|
3340
|
+
function discriminatedUnionOn(discriminantKey) {
|
|
3341
|
+
const noMoreBranches = (raw, ctx) => {
|
|
3342
|
+
if (!isObject(raw)) {
|
|
3343
|
+
return ctx.failure("object", raw);
|
|
3344
|
+
}
|
|
3345
|
+
return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
|
|
3346
|
+
};
|
|
3347
|
+
return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
|
|
3348
|
+
}
|
|
3349
|
+
class DiscriminatedUnionBuilder {
|
|
3350
|
+
constructor(discriminantKey, parser) {
|
|
3351
|
+
this.discriminantKey = discriminantKey;
|
|
3352
|
+
this.parser = parser;
|
|
3353
|
+
}
|
|
3354
|
+
withBranch(discriminantValue, parseNewVariant) {
|
|
3355
|
+
const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
|
|
3356
|
+
return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
|
|
3360
|
+
return (raw, ctx) => {
|
|
3361
|
+
if (!isObject(raw)) {
|
|
3362
|
+
return ctx.failure("object", raw);
|
|
3363
|
+
}
|
|
3364
|
+
if (raw[discriminantKey] === discriminantValue) {
|
|
3365
|
+
return parseVariant(raw, ctx);
|
|
3366
|
+
}
|
|
3367
|
+
return parseOtherBranches(raw, ctx);
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
const pairOfNumbers$3 = pair(number, number);
|
|
3372
|
+
const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
|
|
3373
|
+
const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
3374
|
+
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
3375
|
+
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
3376
|
+
type: constant("absolute_value"),
|
|
3377
|
+
coords: nullable(pairOfPoints)
|
|
3378
|
+
})).withBranch("exponential", object({
|
|
3379
|
+
type: constant("exponential"),
|
|
3380
|
+
asymptote: pairOfPoints,
|
|
3381
|
+
coords: nullable(pairOfPoints)
|
|
3382
|
+
})).withBranch("linear", object({
|
|
3383
|
+
type: constant("linear"),
|
|
3384
|
+
coords: nullable(pairOfPoints)
|
|
3385
|
+
})).withBranch("logarithm", object({
|
|
3386
|
+
type: constant("logarithm"),
|
|
3387
|
+
asymptote: pairOfPoints,
|
|
3388
|
+
coords: nullable(pairOfPoints)
|
|
3389
|
+
})).withBranch("quadratic", object({
|
|
3390
|
+
type: constant("quadratic"),
|
|
3391
|
+
coords: nullable(pairOfPoints)
|
|
3392
|
+
})).withBranch("sinusoid", object({
|
|
3393
|
+
type: constant("sinusoid"),
|
|
3394
|
+
coords: nullable(pairOfPoints)
|
|
3395
|
+
})).withBranch("tangent", object({
|
|
3396
|
+
type: constant("tangent"),
|
|
3397
|
+
coords: nullable(pairOfPoints)
|
|
3398
|
+
})).parser,
|
|
3399
|
+
graph: object({
|
|
3400
|
+
backgroundImage: object({
|
|
3401
|
+
bottom: optional(number),
|
|
3402
|
+
height: optional(number),
|
|
3403
|
+
left: optional(number),
|
|
3404
|
+
scale: optional(number),
|
|
3405
|
+
url: optional(nullable(string)),
|
|
3406
|
+
width: optional(number)
|
|
3407
|
+
}),
|
|
3408
|
+
box: optional(pairOfNumbers$3),
|
|
3409
|
+
editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
|
|
3410
|
+
gridStep: optional(pairOfNumbers$3),
|
|
3411
|
+
labels: pair(string, string),
|
|
3412
|
+
markings: enumeration("graph", "none", "grid"),
|
|
3413
|
+
range: pair(pairOfNumbers$3, pairOfNumbers$3),
|
|
3414
|
+
rulerLabel: constant(""),
|
|
3415
|
+
rulerTicks: number,
|
|
3416
|
+
showProtractor: optional(boolean),
|
|
3417
|
+
showRuler: optional(boolean),
|
|
3418
|
+
showTooltips: optional(boolean),
|
|
3419
|
+
snapStep: optional(pairOfNumbers$3),
|
|
3420
|
+
step: pairOfNumbers$3,
|
|
3421
|
+
valid: optional(union(boolean).or(string).parser)
|
|
3422
|
+
})
|
|
3423
|
+
}));
|
|
3424
|
+
|
|
3425
|
+
const parseGroupWidget = parseWidget(constant("group"),
|
|
3426
|
+
// This module has an import cycle with parsePerseusRenderer.
|
|
3427
|
+
// The anonymous function below ensures that we don't try to access
|
|
3428
|
+
// parsePerseusRenderer before it's defined.
|
|
3429
|
+
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
3430
|
+
|
|
3431
|
+
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
3432
|
+
url: string,
|
|
3433
|
+
settings: optional(array(object({
|
|
3434
|
+
name: string,
|
|
3435
|
+
value: string
|
|
3436
|
+
}))),
|
|
3437
|
+
width: union(number).or(string).parser,
|
|
3438
|
+
height: union(number).or(string).parser,
|
|
3439
|
+
allowFullScreen: defaulted(boolean, () => false),
|
|
3440
|
+
allowTopNavigation: optional(boolean),
|
|
3441
|
+
static: defaulted(boolean, () => false)
|
|
3442
|
+
}));
|
|
3443
|
+
|
|
3444
|
+
const stringToNumber = (rawValue, ctx) => {
|
|
3445
|
+
if (typeof rawValue === "number") {
|
|
3446
|
+
return ctx.success(rawValue);
|
|
3447
|
+
}
|
|
3448
|
+
const parsedNumber = +rawValue;
|
|
3449
|
+
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
3450
|
+
return ctx.failure("a number or numeric string", rawValue);
|
|
3451
|
+
}
|
|
3452
|
+
return ctx.success(parsedNumber);
|
|
3453
|
+
};
|
|
3454
|
+
|
|
3455
|
+
function emptyToZero(x) {
|
|
3456
|
+
return x === "" ? 0 : x;
|
|
3457
|
+
}
|
|
3458
|
+
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
3459
|
+
// In this specific case, empty string is equivalent to zero. An empty
|
|
3460
|
+
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
3461
|
+
// CSS will treat NaN as invalid and default to 0 instead.
|
|
3462
|
+
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
3463
|
+
const parsePerseusImageBackground = object({
|
|
3464
|
+
url: optional(nullable(string)),
|
|
3465
|
+
width: optional(imageDimensionToNumber),
|
|
3466
|
+
height: optional(imageDimensionToNumber),
|
|
3467
|
+
top: optional(imageDimensionToNumber),
|
|
3468
|
+
left: optional(imageDimensionToNumber),
|
|
3469
|
+
bottom: optional(imageDimensionToNumber),
|
|
3470
|
+
scale: optional(imageDimensionToNumber)
|
|
3471
|
+
});
|
|
3472
|
+
|
|
3473
|
+
const pairOfNumbers$2 = pair(number, number);
|
|
3474
|
+
const parseImageWidget = parseWidget(constant("image"), object({
|
|
3475
|
+
title: optional(string),
|
|
3476
|
+
caption: optional(string),
|
|
3477
|
+
alt: optional(string),
|
|
3478
|
+
backgroundImage: parsePerseusImageBackground,
|
|
3479
|
+
static: optional(boolean),
|
|
3480
|
+
labels: optional(array(object({
|
|
3481
|
+
content: string,
|
|
3482
|
+
alignment: string,
|
|
3483
|
+
coordinates: array(number)
|
|
3484
|
+
}))),
|
|
3485
|
+
range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
|
|
3486
|
+
box: optional(pairOfNumbers$2)
|
|
3487
|
+
}));
|
|
3488
|
+
|
|
3489
|
+
const booleanToString = (rawValue, ctx) => {
|
|
3490
|
+
if (typeof rawValue === "boolean") {
|
|
3491
|
+
return ctx.success(String(rawValue));
|
|
3492
|
+
}
|
|
3493
|
+
return ctx.failure("boolean", rawValue);
|
|
3494
|
+
};
|
|
3495
|
+
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
3496
|
+
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
3497
|
+
inexact: optional(boolean),
|
|
3498
|
+
maxError: optional(union(number).or(string).parser),
|
|
3499
|
+
rightAlign: optional(boolean),
|
|
3500
|
+
simplify: enumeration("required", "optional", "enforced"),
|
|
3501
|
+
size: enumeration("normal", "small"),
|
|
3502
|
+
// TODO(benchristel): there are some content items where value is a
|
|
3503
|
+
// boolean, even though that makes no sense. We should figure out if
|
|
3504
|
+
// those content items are actually published anywhere, and consider
|
|
3505
|
+
// updating them.
|
|
3506
|
+
value: union(number).or(string).or(booleanToString).parser,
|
|
3507
|
+
customKeypad: optional(boolean)
|
|
3508
|
+
}));
|
|
3509
|
+
|
|
3510
|
+
const pairOfNumbers$1 = pair(number, number);
|
|
3511
|
+
const stringOrEmpty = defaulted(string, () => "");
|
|
3512
|
+
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
3513
|
+
const parseFunctionElement = object({
|
|
3514
|
+
type: constant("function"),
|
|
3515
|
+
key: parseKey,
|
|
3516
|
+
options: object({
|
|
3517
|
+
value: string,
|
|
3518
|
+
funcName: string,
|
|
3519
|
+
rangeMin: string,
|
|
3520
|
+
rangeMax: string,
|
|
3521
|
+
color: string,
|
|
3522
|
+
strokeDasharray: string,
|
|
3523
|
+
strokeWidth: number
|
|
3524
|
+
})
|
|
3525
|
+
});
|
|
3526
|
+
const parseLabelElement = object({
|
|
3527
|
+
type: constant("label"),
|
|
3528
|
+
key: parseKey,
|
|
3529
|
+
options: object({
|
|
3530
|
+
label: string,
|
|
3531
|
+
color: string,
|
|
3532
|
+
coordX: string,
|
|
3533
|
+
coordY: string
|
|
3534
|
+
})
|
|
3535
|
+
});
|
|
3536
|
+
const parseLineElement = object({
|
|
3537
|
+
type: constant("line"),
|
|
3538
|
+
key: parseKey,
|
|
3539
|
+
options: object({
|
|
3540
|
+
color: string,
|
|
3541
|
+
startX: string,
|
|
3542
|
+
startY: string,
|
|
3543
|
+
endX: string,
|
|
3544
|
+
endY: string,
|
|
3545
|
+
strokeDasharray: string,
|
|
3546
|
+
strokeWidth: number,
|
|
3547
|
+
arrows: string
|
|
3548
|
+
})
|
|
3549
|
+
});
|
|
3550
|
+
const parseMovableLineElement = object({
|
|
3551
|
+
type: constant("movable-line"),
|
|
3552
|
+
key: parseKey,
|
|
3553
|
+
options: object({
|
|
3554
|
+
startX: string,
|
|
3555
|
+
startY: string,
|
|
3556
|
+
startSubscript: number,
|
|
3557
|
+
endX: string,
|
|
3558
|
+
endY: string,
|
|
3559
|
+
endSubscript: number,
|
|
3560
|
+
constraint: string,
|
|
3561
|
+
snap: number,
|
|
3562
|
+
constraintFn: string,
|
|
3563
|
+
constraintXMin: string,
|
|
3564
|
+
constraintXMax: string,
|
|
3565
|
+
constraintYMin: string,
|
|
3566
|
+
constraintYMax: string
|
|
3567
|
+
})
|
|
3568
|
+
});
|
|
3569
|
+
const parseMovablePointElement = object({
|
|
3570
|
+
type: constant("movable-point"),
|
|
3571
|
+
key: parseKey,
|
|
3572
|
+
options: object({
|
|
3573
|
+
startX: string,
|
|
3574
|
+
startY: string,
|
|
3575
|
+
varSubscript: number,
|
|
3576
|
+
constraint: string,
|
|
3577
|
+
snap: number,
|
|
3578
|
+
constraintFn: string,
|
|
3579
|
+
constraintXMin: stringOrEmpty,
|
|
3580
|
+
constraintXMax: stringOrEmpty,
|
|
3581
|
+
constraintYMin: stringOrEmpty,
|
|
3582
|
+
constraintYMax: stringOrEmpty
|
|
3583
|
+
})
|
|
3584
|
+
});
|
|
3585
|
+
const parseParametricElement = object({
|
|
3586
|
+
type: constant("parametric"),
|
|
3587
|
+
key: parseKey,
|
|
3588
|
+
options: object({
|
|
3589
|
+
x: string,
|
|
3590
|
+
y: string,
|
|
3591
|
+
rangeMin: string,
|
|
3592
|
+
rangeMax: string,
|
|
3593
|
+
color: string,
|
|
3594
|
+
strokeDasharray: string,
|
|
3595
|
+
strokeWidth: number
|
|
3596
|
+
})
|
|
3597
|
+
});
|
|
3598
|
+
const parsePointElement = object({
|
|
3599
|
+
type: constant("point"),
|
|
3600
|
+
key: parseKey,
|
|
3601
|
+
options: object({
|
|
3602
|
+
color: string,
|
|
3603
|
+
coordX: string,
|
|
3604
|
+
coordY: string
|
|
3605
|
+
})
|
|
3606
|
+
});
|
|
3607
|
+
const parseRectangleElement = object({
|
|
3608
|
+
type: constant("rectangle"),
|
|
3609
|
+
key: parseKey,
|
|
3610
|
+
options: object({
|
|
3611
|
+
color: string,
|
|
3612
|
+
coordX: string,
|
|
3613
|
+
coordY: string,
|
|
3614
|
+
width: string,
|
|
3615
|
+
height: string
|
|
3616
|
+
})
|
|
3617
|
+
});
|
|
3618
|
+
const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
3619
|
+
static: defaulted(boolean, () => false),
|
|
3620
|
+
graph: object({
|
|
3621
|
+
editableSettings: optional(array(enumeration("canvas", "graph"))),
|
|
3622
|
+
box: pairOfNumbers$1,
|
|
3623
|
+
labels: array(string),
|
|
3624
|
+
range: pair(pairOfNumbers$1, pairOfNumbers$1),
|
|
3625
|
+
gridStep: pairOfNumbers$1,
|
|
3626
|
+
markings: enumeration("graph", "grid", "none"),
|
|
3627
|
+
snapStep: optional(pairOfNumbers$1),
|
|
3628
|
+
valid: optional(union(boolean).or(string).parser),
|
|
3629
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
3630
|
+
showProtractor: optional(boolean),
|
|
3631
|
+
showRuler: optional(boolean),
|
|
3632
|
+
rulerLabel: optional(string),
|
|
3633
|
+
rulerTicks: optional(number),
|
|
3634
|
+
tickStep: pairOfNumbers$1
|
|
3635
|
+
}),
|
|
3636
|
+
elements: array(discriminatedUnionOn("type").withBranch("function", parseFunctionElement).withBranch("label", parseLabelElement).withBranch("line", parseLineElement).withBranch("movable-line", parseMovableLineElement).withBranch("movable-point", parseMovablePointElement).withBranch("parametric", parseParametricElement).withBranch("point", parsePointElement).withBranch("rectangle", parseRectangleElement).parser)
|
|
3637
|
+
}));
|
|
3638
|
+
|
|
3639
|
+
/**
|
|
3640
|
+
* The Perseus "data schema" file.
|
|
3641
|
+
*
|
|
3642
|
+
* This file, and the types in it, represents the "data schema" that Perseus
|
|
3643
|
+
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
3644
|
+
* that conform to the types in this file. Similarly, the top-level renderers
|
|
3645
|
+
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
3646
|
+
*
|
|
3647
|
+
* WARNING: This file should not import any types from elsewhere so that it is
|
|
3648
|
+
* easy to reason about changes that alter the Perseus schema. This helps
|
|
3649
|
+
* ensure that it is not changed accidentally when upgrading a dependant
|
|
3650
|
+
* package or other part of Perseus code. Note that TypeScript does type
|
|
3651
|
+
* checking via something called "structural typing". This means that as long
|
|
3652
|
+
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
3653
|
+
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
3654
|
+
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
3655
|
+
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
3656
|
+
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
3657
|
+
* labels are for humans, not TypeScript. :)
|
|
3658
|
+
*
|
|
3659
|
+
* If you make changes to types in this file, be very sure that:
|
|
3660
|
+
*
|
|
3661
|
+
* a) the changes are backwards compatible. If they are not, old data from
|
|
3662
|
+
* previous versions of the "schema" could become unrenderable, or worse,
|
|
3663
|
+
* introduce hard-to-diagnose bugs.
|
|
3664
|
+
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
3665
|
+
* the new format _as well as_ the old format.
|
|
3666
|
+
*/
|
|
3667
|
+
|
|
3668
|
+
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
3669
|
+
|
|
3670
|
+
// Same name as Mafs
|
|
3671
|
+
|
|
3672
|
+
/**
|
|
3673
|
+
* A utility type that constructs a widget map from a "registry interface".
|
|
3674
|
+
* The keys of the registry should be the widget type (aka, "categorizer" or
|
|
3675
|
+
* "radio", etc) and the value should be the option type stored in the value
|
|
3676
|
+
* of the map.
|
|
3677
|
+
*
|
|
3678
|
+
* You can think of this as a type that generates another type. We use
|
|
3679
|
+
* "registry interfaces" as a way to keep a set of widget types to their data
|
|
3680
|
+
* type in several places in Perseus. This type then allows us to generate a
|
|
3681
|
+
* map type that maps a widget id to its data type and keep strong typing by
|
|
3682
|
+
* widget id.
|
|
3683
|
+
*
|
|
3684
|
+
* For example, given a fictitious registry such as this:
|
|
3685
|
+
*
|
|
3686
|
+
* ```
|
|
3687
|
+
* interface DummyRegistry {
|
|
3688
|
+
* categorizer: { categories: ReadonlyArray<string> };
|
|
3689
|
+
* dropdown: { choices: ReadonlyArray<string> }:
|
|
3690
|
+
* }
|
|
3691
|
+
* ```
|
|
3692
|
+
*
|
|
3693
|
+
* If we create a DummyMap using this helper:
|
|
3694
|
+
*
|
|
3695
|
+
* ```
|
|
3696
|
+
* type DummyMap = MakeWidgetMap<DummyRegistry>;
|
|
3697
|
+
* ```
|
|
3698
|
+
*
|
|
3699
|
+
* We'll get a map that looks like this:
|
|
3700
|
+
*
|
|
3701
|
+
* ```
|
|
3702
|
+
* type DummyMap = {
|
|
3703
|
+
* `categorizer ${number}`: { categories: ReadonlyArray<string> };
|
|
3704
|
+
* `dropdown ${number}`: { choices: ReadonlyArray<string> };
|
|
3705
|
+
* }
|
|
3706
|
+
* ```
|
|
3707
|
+
*
|
|
3708
|
+
* We use interfaces for the registries so that they can be extended in cases
|
|
3709
|
+
* where the consuming app brings along their own widgets. Interfaces in
|
|
3710
|
+
* TypeScript are always open (ie. you can extend them) whereas types aren't.
|
|
3711
|
+
*/
|
|
3712
|
+
|
|
3713
|
+
/**
|
|
3714
|
+
* Our core set of Perseus widgets.
|
|
3715
|
+
*
|
|
3716
|
+
* This interface is the basis for "registering" all Perseus widget types.
|
|
3717
|
+
* There should be one key/value pair for each supported widget. If you create
|
|
3718
|
+
* a new widget, an entry should be added to this interface. Note that this
|
|
3719
|
+
* only registers the widget options type, you'll also need to register the
|
|
3720
|
+
* widget so that it's available at runtime (@see
|
|
3721
|
+
* {@link file://./widgets.ts#registerWidget}).
|
|
3722
|
+
*
|
|
3723
|
+
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
3724
|
+
* widgets that is the same as the widget option's `type` field. In cases where
|
|
3725
|
+
* a widget has been deprecated and replaced with the deprecated-standin
|
|
3726
|
+
* widget, it should be the original widget type!
|
|
3727
|
+
*
|
|
3728
|
+
* If you define the widget outside of this package, you can still add the new
|
|
3729
|
+
* widget to this interface by writing the following in that package that
|
|
3730
|
+
* contains the widget. TypeScript will merge that definition of the
|
|
3731
|
+
* `PerseusWidgets` with the one defined below.
|
|
3732
|
+
*
|
|
3733
|
+
* ```typescript
|
|
3734
|
+
* declare module "@khanacademy/perseus-core" {
|
|
3735
|
+
* interface PerseusWidgetTypes {
|
|
3736
|
+
* // A new widget
|
|
3737
|
+
* "new-awesomeness": MyAwesomeNewWidget;
|
|
3738
|
+
*
|
|
3739
|
+
* // A deprecated widget
|
|
3740
|
+
* "super-old-widget": DeprecatedStandinWidget;
|
|
3741
|
+
* }
|
|
3742
|
+
* }
|
|
3743
|
+
*
|
|
3744
|
+
* // The new widget's options definition
|
|
3745
|
+
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
3746
|
+
*
|
|
3747
|
+
* // The deprecated widget's options definition
|
|
3748
|
+
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
3749
|
+
* ```
|
|
3750
|
+
*
|
|
3751
|
+
* This interface can be extended through the magic of TypeScript "Declaration
|
|
3752
|
+
* merging". Specifically, we augment this module and extend this interface.
|
|
3753
|
+
*
|
|
3754
|
+
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
3755
|
+
*/
|
|
3756
|
+
|
|
3757
|
+
/**
|
|
3758
|
+
* A map of widget IDs to widget options. This is most often used as the type
|
|
3759
|
+
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
3760
|
+
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
3761
|
+
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
3762
|
+
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
3763
|
+
* reading/parsing the widget id to derive any information from it, except in
|
|
3764
|
+
* the case of this map.
|
|
3765
|
+
*
|
|
3766
|
+
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
3767
|
+
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
3768
|
+
*/
|
|
3769
|
+
|
|
3770
|
+
/**
|
|
3771
|
+
* PerseusWidget is a union of all the different types of widget options that
|
|
3772
|
+
* Perseus knows about.
|
|
3773
|
+
*
|
|
3774
|
+
* Thanks to it being based on PerseusWidgetTypes interface, this union is
|
|
3775
|
+
* automatically extended to include widgets used in tests without those widget
|
|
3776
|
+
* option types seeping into our production types.
|
|
3777
|
+
*
|
|
3778
|
+
* @see MockWidget for an example
|
|
3779
|
+
*/
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
3783
|
+
* `ServerItemRenderer` and the layout is pre-set.
|
|
3784
|
+
*
|
|
3785
|
+
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
3786
|
+
* area.
|
|
3787
|
+
*/
|
|
3788
|
+
|
|
3789
|
+
/**
|
|
3790
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
3791
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
3792
|
+
*/
|
|
3793
|
+
|
|
3794
|
+
const ItemExtras = [
|
|
3795
|
+
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
3796
|
+
"calculator",
|
|
3797
|
+
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
3798
|
+
"chi2Table",
|
|
3799
|
+
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
3800
|
+
"financialCalculatorMonthlyPayment",
|
|
3801
|
+
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
3802
|
+
"financialCalculatorTotalAmount",
|
|
3803
|
+
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
3804
|
+
"financialCalculatorTimeToPayOff",
|
|
3805
|
+
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
3806
|
+
"periodicTable",
|
|
3807
|
+
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
3808
|
+
"periodicTableWithKey",
|
|
3809
|
+
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
3810
|
+
"tTable",
|
|
3811
|
+
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
3812
|
+
"zTable"];
|
|
3813
|
+
|
|
3814
|
+
/**
|
|
3815
|
+
* The type representing the common structure of all widget's options. The
|
|
3816
|
+
* `Options` generic type represents the widget-specific option data.
|
|
3817
|
+
*/
|
|
3818
|
+
|
|
3819
|
+
// prettier-ignore
|
|
3820
|
+
|
|
3821
|
+
// prettier-ignore
|
|
3822
|
+
|
|
3823
|
+
// prettier-ignore
|
|
3824
|
+
|
|
3825
|
+
// prettier-ignore
|
|
3826
|
+
|
|
3827
|
+
// prettier-ignore
|
|
3828
|
+
|
|
3829
|
+
// prettier-ignore
|
|
3830
|
+
|
|
3831
|
+
// prettier-ignore
|
|
3832
|
+
|
|
3833
|
+
// prettier-ignore
|
|
3834
|
+
|
|
3835
|
+
// prettier-ignore
|
|
3836
|
+
|
|
3837
|
+
// prettier-ignore
|
|
3838
|
+
|
|
3839
|
+
// prettier-ignore
|
|
3840
|
+
|
|
3841
|
+
// prettier-ignore
|
|
3842
|
+
|
|
3843
|
+
// prettier-ignore
|
|
3844
|
+
|
|
3845
|
+
// prettier-ignore
|
|
3846
|
+
|
|
3847
|
+
// prettier-ignore
|
|
3848
|
+
|
|
3849
|
+
// prettier-ignore
|
|
3850
|
+
|
|
3851
|
+
// prettier-ignore
|
|
3852
|
+
|
|
3853
|
+
// prettier-ignore
|
|
3854
|
+
|
|
3855
|
+
// prettier-ignore
|
|
3856
|
+
|
|
3857
|
+
// prettier-ignore
|
|
3858
|
+
|
|
3859
|
+
// prettier-ignore
|
|
3860
|
+
|
|
3861
|
+
// prettier-ignore
|
|
3862
|
+
|
|
3863
|
+
// prettier-ignore
|
|
3864
|
+
|
|
3865
|
+
// prettier-ignore
|
|
3866
|
+
|
|
3867
|
+
// prettier-ignore
|
|
3868
|
+
|
|
3869
|
+
// prettier-ignore
|
|
3870
|
+
|
|
3871
|
+
// prettier-ignore
|
|
3872
|
+
|
|
3873
|
+
// prettier-ignore
|
|
3874
|
+
|
|
3875
|
+
// prettier-ignore
|
|
3876
|
+
|
|
3877
|
+
// prettier-ignore
|
|
3878
|
+
|
|
3879
|
+
// prettier-ignore
|
|
3880
|
+
|
|
3881
|
+
// prettier-ignore
|
|
2775
3882
|
|
|
2776
3883
|
// prettier-ignore
|
|
2777
3884
|
|
|
2778
|
-
//
|
|
3885
|
+
//prettier-ignore
|
|
3886
|
+
|
|
3887
|
+
/**
|
|
3888
|
+
* A background image applied to various widgets.
|
|
3889
|
+
*/
|
|
3890
|
+
|
|
3891
|
+
/**
|
|
3892
|
+
* The type of markings to display on the graph.
|
|
3893
|
+
* - axes: shows the axes without the gride lines
|
|
3894
|
+
* - graph: shows the axes and the grid lines
|
|
3895
|
+
* - grid: shows only the grid lines
|
|
3896
|
+
* - none: shows no markings
|
|
3897
|
+
*/
|
|
3898
|
+
|
|
3899
|
+
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
3900
|
+
|
|
3901
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
3902
|
+
|
|
3903
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
3904
|
+
const lockedFigureColors = {
|
|
3905
|
+
blue: "#3D7586",
|
|
3906
|
+
green: "#447A53",
|
|
3907
|
+
grayH: "#3B3D45",
|
|
3908
|
+
purple: "#594094",
|
|
3909
|
+
pink: "#B25071",
|
|
3910
|
+
red: "#D92916",
|
|
3911
|
+
orange: "#946700"
|
|
3912
|
+
};
|
|
3913
|
+
const lockedFigureFillStyles = {
|
|
3914
|
+
none: 0,
|
|
3915
|
+
white: 1,
|
|
3916
|
+
translucent: 0.4,
|
|
3917
|
+
solid: 1
|
|
3918
|
+
};
|
|
3919
|
+
|
|
3920
|
+
// Not associated with a specific figure
|
|
3921
|
+
|
|
3922
|
+
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
3923
|
+
|
|
3924
|
+
// Used to represent 2-D points and ranges
|
|
3925
|
+
const pairOfNumbers = pair(number, number);
|
|
3926
|
+
const parsePerseusGraphTypeAngle = object({
|
|
3927
|
+
type: constant("angle"),
|
|
3928
|
+
showAngles: optional(boolean),
|
|
3929
|
+
allowReflexAngles: optional(boolean),
|
|
3930
|
+
angleOffsetDeg: optional(number),
|
|
3931
|
+
snapDegrees: optional(number),
|
|
3932
|
+
match: optional(constant("congruent")),
|
|
3933
|
+
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
3934
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
3935
|
+
});
|
|
3936
|
+
const parsePerseusGraphTypeCircle = object({
|
|
3937
|
+
type: constant("circle"),
|
|
3938
|
+
center: optional(pairOfNumbers),
|
|
3939
|
+
radius: optional(number),
|
|
3940
|
+
startCoords: optional(object({
|
|
3941
|
+
center: pairOfNumbers,
|
|
3942
|
+
radius: number
|
|
3943
|
+
})),
|
|
3944
|
+
// TODO: remove coord? it's legacy.
|
|
3945
|
+
coord: optional(pairOfNumbers)
|
|
3946
|
+
});
|
|
3947
|
+
const parsePerseusGraphTypeLinear = object({
|
|
3948
|
+
type: constant("linear"),
|
|
3949
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
3950
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
3951
|
+
// TODO: remove coord? it's legacy.
|
|
3952
|
+
coord: optional(pairOfNumbers)
|
|
3953
|
+
});
|
|
3954
|
+
const parsePerseusGraphTypeLinearSystem = object({
|
|
3955
|
+
type: constant("linear-system"),
|
|
3956
|
+
// TODO(benchristel): default coords to empty array?
|
|
3957
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
3958
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
3959
|
+
// TODO: remove coord? it's legacy.
|
|
3960
|
+
coord: optional(pairOfNumbers)
|
|
3961
|
+
});
|
|
3962
|
+
const parsePerseusGraphTypeNone = object({
|
|
3963
|
+
type: constant("none")
|
|
3964
|
+
});
|
|
3965
|
+
const parsePerseusGraphTypePoint = object({
|
|
3966
|
+
type: constant("point"),
|
|
3967
|
+
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
3968
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
3969
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
3970
|
+
// TODO: remove coord? it's legacy.
|
|
3971
|
+
coord: optional(pairOfNumbers)
|
|
3972
|
+
});
|
|
3973
|
+
const parsePerseusGraphTypePolygon = object({
|
|
3974
|
+
type: constant("polygon"),
|
|
3975
|
+
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
3976
|
+
showAngles: optional(boolean),
|
|
3977
|
+
showSides: optional(boolean),
|
|
3978
|
+
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
3979
|
+
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
3980
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
3981
|
+
// TODO: remove coord? it's legacy.
|
|
3982
|
+
coord: optional(pairOfNumbers)
|
|
3983
|
+
});
|
|
3984
|
+
const parsePerseusGraphTypeQuadratic = object({
|
|
3985
|
+
type: constant("quadratic"),
|
|
3986
|
+
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
3987
|
+
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
3988
|
+
// TODO: remove coord? it's legacy.
|
|
3989
|
+
coord: optional(pairOfNumbers)
|
|
3990
|
+
});
|
|
3991
|
+
const parsePerseusGraphTypeRay = object({
|
|
3992
|
+
type: constant("ray"),
|
|
3993
|
+
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
3994
|
+
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
3995
|
+
// TODO: remove coord? it's legacy.
|
|
3996
|
+
coord: optional(pairOfNumbers)
|
|
3997
|
+
});
|
|
3998
|
+
const parsePerseusGraphTypeSegment = object({
|
|
3999
|
+
type: constant("segment"),
|
|
4000
|
+
// TODO(benchristel): default numSegments?
|
|
4001
|
+
numSegments: optional(number),
|
|
4002
|
+
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
4003
|
+
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
4004
|
+
// TODO: remove coord? it's legacy.
|
|
4005
|
+
coord: optional(pairOfNumbers)
|
|
4006
|
+
});
|
|
4007
|
+
const parsePerseusGraphTypeSinusoid = object({
|
|
4008
|
+
type: constant("sinusoid"),
|
|
4009
|
+
coords: optional(nullable(array(pairOfNumbers))),
|
|
4010
|
+
startCoords: optional(array(pairOfNumbers)),
|
|
4011
|
+
// TODO: remove coord? it's legacy.
|
|
4012
|
+
coord: optional(pairOfNumbers)
|
|
4013
|
+
});
|
|
4014
|
+
const parsePerseusGraphType = discriminatedUnionOn("type").withBranch("angle", parsePerseusGraphTypeAngle).withBranch("circle", parsePerseusGraphTypeCircle).withBranch("linear", parsePerseusGraphTypeLinear).withBranch("linear-system", parsePerseusGraphTypeLinearSystem).withBranch("none", parsePerseusGraphTypeNone).withBranch("point", parsePerseusGraphTypePoint).withBranch("polygon", parsePerseusGraphTypePolygon).withBranch("quadratic", parsePerseusGraphTypeQuadratic).withBranch("ray", parsePerseusGraphTypeRay).withBranch("segment", parsePerseusGraphTypeSegment).withBranch("sinusoid", parsePerseusGraphTypeSinusoid).parser;
|
|
4015
|
+
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
4016
|
+
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
4017
|
+
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
4018
|
+
const parseLockedLabelType = object({
|
|
4019
|
+
type: constant("label"),
|
|
4020
|
+
coord: pairOfNumbers,
|
|
4021
|
+
text: string,
|
|
4022
|
+
color: parseLockedFigureColor,
|
|
4023
|
+
size: enumeration("small", "medium", "large")
|
|
4024
|
+
});
|
|
4025
|
+
const parseLockedPointType = object({
|
|
4026
|
+
type: constant("point"),
|
|
4027
|
+
coord: pairOfNumbers,
|
|
4028
|
+
color: parseLockedFigureColor,
|
|
4029
|
+
filled: boolean,
|
|
4030
|
+
// TODO(benchristel): default labels to empty array?
|
|
4031
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4032
|
+
ariaLabel: optional(string)
|
|
4033
|
+
});
|
|
4034
|
+
const parseLockedLineType = object({
|
|
4035
|
+
type: constant("line"),
|
|
4036
|
+
kind: enumeration("line", "ray", "segment"),
|
|
4037
|
+
points: pair(parseLockedPointType, parseLockedPointType),
|
|
4038
|
+
color: parseLockedFigureColor,
|
|
4039
|
+
lineStyle: parseLockedLineStyle,
|
|
4040
|
+
showPoint1: defaulted(boolean, () => false),
|
|
4041
|
+
showPoint2: defaulted(boolean, () => false),
|
|
4042
|
+
// TODO(benchristel): default labels to empty array?
|
|
4043
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4044
|
+
ariaLabel: optional(string)
|
|
4045
|
+
});
|
|
4046
|
+
const parseLockedVectorType = object({
|
|
4047
|
+
type: constant("vector"),
|
|
4048
|
+
points: pair(pairOfNumbers, pairOfNumbers),
|
|
4049
|
+
color: parseLockedFigureColor,
|
|
4050
|
+
// TODO(benchristel): default labels to empty array?
|
|
4051
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4052
|
+
ariaLabel: optional(string)
|
|
4053
|
+
});
|
|
4054
|
+
const parseLockedEllipseType = object({
|
|
4055
|
+
type: constant("ellipse"),
|
|
4056
|
+
center: pairOfNumbers,
|
|
4057
|
+
radius: pairOfNumbers,
|
|
4058
|
+
angle: number,
|
|
4059
|
+
color: parseLockedFigureColor,
|
|
4060
|
+
fillStyle: parseLockedFigureFillType,
|
|
4061
|
+
strokeStyle: parseLockedLineStyle,
|
|
4062
|
+
// TODO(benchristel): default labels to empty array?
|
|
4063
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4064
|
+
ariaLabel: optional(string)
|
|
4065
|
+
});
|
|
4066
|
+
const parseLockedPolygonType = object({
|
|
4067
|
+
type: constant("polygon"),
|
|
4068
|
+
points: array(pairOfNumbers),
|
|
4069
|
+
color: parseLockedFigureColor,
|
|
4070
|
+
showVertices: boolean,
|
|
4071
|
+
fillStyle: parseLockedFigureFillType,
|
|
4072
|
+
strokeStyle: parseLockedLineStyle,
|
|
4073
|
+
// TODO(benchristel): default labels to empty array?
|
|
4074
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4075
|
+
ariaLabel: optional(string)
|
|
4076
|
+
});
|
|
4077
|
+
const parseLockedFunctionType = object({
|
|
4078
|
+
type: constant("function"),
|
|
4079
|
+
color: parseLockedFigureColor,
|
|
4080
|
+
strokeStyle: parseLockedLineStyle,
|
|
4081
|
+
equation: string,
|
|
4082
|
+
directionalAxis: enumeration("x", "y"),
|
|
4083
|
+
domain: optional(pairOfNumbers),
|
|
4084
|
+
// TODO(benchristel): default labels to empty array?
|
|
4085
|
+
labels: optional(array(parseLockedLabelType)),
|
|
4086
|
+
ariaLabel: optional(string)
|
|
4087
|
+
});
|
|
4088
|
+
const parseLockedFigure = discriminatedUnionOn("type").withBranch("point", parseLockedPointType).withBranch("line", parseLockedLineType).withBranch("vector", parseLockedVectorType).withBranch("ellipse", parseLockedEllipseType).withBranch("polygon", parseLockedPolygonType).withBranch("function", parseLockedFunctionType).withBranch("label", parseLockedLabelType).parser;
|
|
4089
|
+
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
4090
|
+
step: pairOfNumbers,
|
|
4091
|
+
// TODO(benchristel): rather than making gridStep and snapStep
|
|
4092
|
+
// optional, we should duplicate the defaulting logic from the
|
|
4093
|
+
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
4094
|
+
// why.
|
|
4095
|
+
gridStep: optional(pairOfNumbers),
|
|
4096
|
+
snapStep: optional(pairOfNumbers),
|
|
4097
|
+
backgroundImage: optional(parsePerseusImageBackground),
|
|
4098
|
+
markings: enumeration("graph", "grid", "none"),
|
|
4099
|
+
labels: optional(array(string)),
|
|
4100
|
+
showProtractor: boolean,
|
|
4101
|
+
showRuler: optional(boolean),
|
|
4102
|
+
showTooltips: optional(boolean),
|
|
4103
|
+
rulerLabel: optional(string),
|
|
4104
|
+
rulerTicks: optional(number),
|
|
4105
|
+
range: pair(pairOfNumbers, pairOfNumbers),
|
|
4106
|
+
// NOTE(benchristel): I copied the default graph from
|
|
4107
|
+
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
4108
|
+
// an explanation of why we want to duplicate the default here.
|
|
4109
|
+
graph: defaulted(parsePerseusGraphType, () => ({
|
|
4110
|
+
type: "linear"
|
|
4111
|
+
})),
|
|
4112
|
+
correct: parsePerseusGraphType,
|
|
4113
|
+
// TODO(benchristel): default lockedFigures to empty array
|
|
4114
|
+
lockedFigures: optional(array(parseLockedFigure)),
|
|
4115
|
+
fullGraphLabel: optional(string),
|
|
4116
|
+
fullGraphAriaDescription: optional(string)
|
|
4117
|
+
}));
|
|
4118
|
+
|
|
4119
|
+
const parseLabelImageWidget = parseWidget(constant("label-image"), object({
|
|
4120
|
+
choices: array(string),
|
|
4121
|
+
imageUrl: string,
|
|
4122
|
+
imageAlt: string,
|
|
4123
|
+
imageHeight: number,
|
|
4124
|
+
imageWidth: number,
|
|
4125
|
+
markers: array(object({
|
|
4126
|
+
answers: array(string),
|
|
4127
|
+
label: string,
|
|
4128
|
+
x: number,
|
|
4129
|
+
y: number
|
|
4130
|
+
})),
|
|
4131
|
+
hideChoicesFromInstructions: boolean,
|
|
4132
|
+
multipleAnswers: boolean,
|
|
4133
|
+
static: defaulted(boolean, () => false)
|
|
4134
|
+
}));
|
|
4135
|
+
|
|
4136
|
+
const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
4137
|
+
labels: array(string),
|
|
4138
|
+
left: array(string),
|
|
4139
|
+
right: array(string),
|
|
4140
|
+
orderMatters: boolean,
|
|
4141
|
+
padding: boolean
|
|
4142
|
+
}));
|
|
4143
|
+
|
|
4144
|
+
const numberOrString = union(number).or(string).parser;
|
|
4145
|
+
const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
|
|
4146
|
+
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
4147
|
+
prefix: optional(string),
|
|
4148
|
+
suffix: optional(string),
|
|
4149
|
+
answers: array(array(numeric)),
|
|
4150
|
+
cursorPosition: optional(array(number)),
|
|
4151
|
+
matrixBoardSize: array(number),
|
|
4152
|
+
static: optional(boolean)
|
|
4153
|
+
}));
|
|
4154
|
+
|
|
4155
|
+
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
4156
|
+
// The default value for image comes from measurer.tsx.
|
|
4157
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
4158
|
+
// defaults here.
|
|
4159
|
+
image: defaulted(parsePerseusImageBackground, () => ({
|
|
4160
|
+
url: null,
|
|
4161
|
+
top: 0,
|
|
4162
|
+
left: 0
|
|
4163
|
+
})),
|
|
4164
|
+
showProtractor: boolean,
|
|
4165
|
+
showRuler: boolean,
|
|
4166
|
+
rulerLabel: string,
|
|
4167
|
+
rulerTicks: number,
|
|
4168
|
+
rulerPixels: number,
|
|
4169
|
+
rulerLength: number,
|
|
4170
|
+
box: pair(number, number),
|
|
4171
|
+
// TODO(benchristel): static is not used. Remove it?
|
|
4172
|
+
static: defaulted(boolean, () => false)
|
|
4173
|
+
}));
|
|
4174
|
+
|
|
4175
|
+
const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
|
|
4176
|
+
widgetId: string,
|
|
4177
|
+
rotationAngle: optional(number),
|
|
4178
|
+
smiles: optional(string)
|
|
4179
|
+
}));
|
|
4180
|
+
|
|
4181
|
+
const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
|
|
4182
|
+
const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
4183
|
+
range: array(number),
|
|
4184
|
+
labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
|
|
4185
|
+
labelStyle: string,
|
|
4186
|
+
labelTicks: boolean,
|
|
4187
|
+
isTickCtrl: optional(nullable(boolean)),
|
|
4188
|
+
divisionRange: array(number),
|
|
4189
|
+
numDivisions: optional(nullable(number)),
|
|
4190
|
+
// NOTE(benchristel): I copied the default snapDivisions from
|
|
4191
|
+
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
4192
|
+
// an explanation of why we want to duplicate the default here.
|
|
4193
|
+
snapDivisions: defaulted(number, () => 2),
|
|
4194
|
+
tickStep: optional(nullable(number)),
|
|
4195
|
+
correctRel: optional(nullable(string)),
|
|
4196
|
+
correctX: nullable(number),
|
|
4197
|
+
initialX: optional(nullable(number)),
|
|
4198
|
+
showTooltips: optional(boolean),
|
|
4199
|
+
static: defaulted(boolean, () => false)
|
|
4200
|
+
}));
|
|
4201
|
+
|
|
4202
|
+
const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
|
|
4203
|
+
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
4204
|
+
answers: array(object({
|
|
4205
|
+
message: string,
|
|
4206
|
+
// TODO(benchristel): value should never be null or undefined,
|
|
4207
|
+
// but we have some content where it is anyway. If we backfill
|
|
4208
|
+
// the data, simplify this.
|
|
4209
|
+
value: optional(nullable(number)),
|
|
4210
|
+
status: string,
|
|
4211
|
+
answerForms: optional(array(parseMathFormat)),
|
|
4212
|
+
strict: boolean,
|
|
4213
|
+
maxError: optional(nullable(number)),
|
|
4214
|
+
// TODO(benchristel): simplify should never be a boolean, but we
|
|
4215
|
+
// have some content where it is anyway. If we ever backfill
|
|
4216
|
+
// the data, we should simplify `simplify`.
|
|
4217
|
+
simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
|
|
4218
|
+
})),
|
|
4219
|
+
labelText: optional(string),
|
|
4220
|
+
size: string,
|
|
4221
|
+
coefficient: defaulted(boolean, () => false),
|
|
4222
|
+
rightAlign: optional(boolean),
|
|
4223
|
+
static: defaulted(boolean, () => false),
|
|
4224
|
+
answerForms: optional(array(object({
|
|
4225
|
+
name: parseMathFormat,
|
|
4226
|
+
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
4227
|
+
})))
|
|
4228
|
+
}));
|
|
4229
|
+
|
|
4230
|
+
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
4231
|
+
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
4232
|
+
// it's defined.
|
|
4233
|
+
function parseRenderer(rawValue, ctx) {
|
|
4234
|
+
return parsePerseusRenderer(rawValue, ctx);
|
|
4235
|
+
}
|
|
4236
|
+
const largeToAuto = (height, ctx) => {
|
|
4237
|
+
if (height === "large") {
|
|
4238
|
+
return ctx.success("auto");
|
|
4239
|
+
}
|
|
4240
|
+
return ctx.success(height);
|
|
4241
|
+
};
|
|
4242
|
+
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
4243
|
+
options: defaulted(array(parseRenderer), () => []),
|
|
4244
|
+
correctOptions: array(parseRenderer),
|
|
4245
|
+
otherOptions: array(parseRenderer),
|
|
4246
|
+
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
4247
|
+
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
4248
|
+
}));
|
|
4249
|
+
|
|
4250
|
+
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
4251
|
+
passageNumber: number,
|
|
4252
|
+
referenceNumber: number,
|
|
4253
|
+
summaryText: optional(string)
|
|
4254
|
+
}));
|
|
4255
|
+
|
|
4256
|
+
const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
4257
|
+
footnotes: defaulted(string, () => ""),
|
|
4258
|
+
passageText: string,
|
|
4259
|
+
passageTitle: defaulted(string, () => ""),
|
|
4260
|
+
showLineNumbers: boolean,
|
|
4261
|
+
static: defaulted(boolean, () => false)
|
|
4262
|
+
}));
|
|
4263
|
+
|
|
4264
|
+
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
4265
|
+
url: string,
|
|
4266
|
+
description: string
|
|
4267
|
+
}));
|
|
4268
|
+
|
|
4269
|
+
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
4270
|
+
labels: array(string),
|
|
4271
|
+
categories: array(string),
|
|
4272
|
+
type: enumeration(...plotterPlotTypes),
|
|
4273
|
+
maxY: number,
|
|
4274
|
+
// The default value for scaleY comes from plotter.tsx.
|
|
4275
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
4276
|
+
// defaults here.
|
|
4277
|
+
scaleY: defaulted(number, () => 1),
|
|
4278
|
+
labelInterval: optional(nullable(number)),
|
|
4279
|
+
// The default value for snapsPerLine comes from plotter.tsx.
|
|
4280
|
+
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
4281
|
+
// defaults here.
|
|
4282
|
+
snapsPerLine: defaulted(number, () => 2),
|
|
4283
|
+
starting: array(number),
|
|
4284
|
+
correct: array(number),
|
|
4285
|
+
picUrl: optional(nullable(string)),
|
|
4286
|
+
picSize: optional(nullable(number)),
|
|
4287
|
+
picBoxHeight: optional(nullable(number)),
|
|
4288
|
+
// NOTE(benchristel): I copied the default plotDimensions from
|
|
4289
|
+
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
4290
|
+
// of why we want to duplicate the defaults here.
|
|
4291
|
+
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
4292
|
+
}));
|
|
4293
|
+
|
|
4294
|
+
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
4295
|
+
programID: string,
|
|
4296
|
+
height: number
|
|
4297
|
+
}));
|
|
4298
|
+
|
|
4299
|
+
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
4300
|
+
choices: array(object({
|
|
4301
|
+
content: defaulted(string, () => ""),
|
|
4302
|
+
clue: optional(string),
|
|
4303
|
+
correct: optional(boolean),
|
|
4304
|
+
isNoneOfTheAbove: optional(boolean),
|
|
4305
|
+
// deprecated
|
|
4306
|
+
// There is an import cycle between radio-widget.ts and
|
|
4307
|
+
// widgets-map.ts. The anonymous function below ensures that we
|
|
4308
|
+
// don't refer to parseWidgetsMap before it's defined.
|
|
4309
|
+
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
4310
|
+
})),
|
|
4311
|
+
hasNoneOfTheAbove: optional(boolean),
|
|
4312
|
+
countChoices: optional(boolean),
|
|
4313
|
+
randomize: optional(boolean),
|
|
4314
|
+
multipleSelect: optional(boolean),
|
|
4315
|
+
deselectEnabled: optional(boolean),
|
|
4316
|
+
// deprecated
|
|
4317
|
+
onePerLine: optional(boolean),
|
|
4318
|
+
// deprecated
|
|
4319
|
+
displayCount: optional(any),
|
|
4320
|
+
// v0 props
|
|
4321
|
+
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
4322
|
+
noneOfTheAbove: optional(constant(false))
|
|
4323
|
+
}));
|
|
4324
|
+
|
|
4325
|
+
const parseSorterWidget = parseWidget(constant("sorter"), object({
|
|
4326
|
+
correct: array(string),
|
|
4327
|
+
padding: boolean,
|
|
4328
|
+
layout: enumeration("horizontal", "vertical")
|
|
4329
|
+
}));
|
|
4330
|
+
|
|
4331
|
+
const parseTableWidget = parseWidget(constant("table"), object({
|
|
4332
|
+
headers: array(string),
|
|
4333
|
+
rows: number,
|
|
4334
|
+
columns: number,
|
|
4335
|
+
answers: array(array(string))
|
|
4336
|
+
}));
|
|
4337
|
+
|
|
4338
|
+
const parseVideoWidget = parseWidget(constant("video"), object({
|
|
4339
|
+
location: string,
|
|
4340
|
+
static: optional(boolean)
|
|
4341
|
+
}));
|
|
4342
|
+
|
|
4343
|
+
const parseWidgetsMap = (rawValue, ctx) => {
|
|
4344
|
+
if (!isObject(rawValue)) {
|
|
4345
|
+
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
4346
|
+
}
|
|
4347
|
+
const widgetsMap = {};
|
|
4348
|
+
for (const key of Object.keys(rawValue)) {
|
|
4349
|
+
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
4350
|
+
// but it's the only way I could find to make TypeScript check the key
|
|
4351
|
+
// against the widget type.
|
|
4352
|
+
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
4353
|
+
if (isFailure(entryResult)) {
|
|
4354
|
+
return entryResult;
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
return ctx.success(widgetsMap);
|
|
4358
|
+
};
|
|
4359
|
+
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
4360
|
+
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
4361
|
+
if (isFailure(idComponentsResult)) {
|
|
4362
|
+
return idComponentsResult;
|
|
4363
|
+
}
|
|
4364
|
+
const [type, n] = idComponentsResult.value;
|
|
4365
|
+
function parseAndAssign(key, parse) {
|
|
4366
|
+
const widgetResult = parse(widget, ctx);
|
|
4367
|
+
if (isFailure(widgetResult)) {
|
|
4368
|
+
return widgetResult;
|
|
4369
|
+
}
|
|
4370
|
+
widgetMap[key] = widgetResult.value;
|
|
4371
|
+
return ctx.success(undefined);
|
|
4372
|
+
}
|
|
4373
|
+
switch (type) {
|
|
4374
|
+
case "categorizer":
|
|
4375
|
+
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
4376
|
+
case "cs-program":
|
|
4377
|
+
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
4378
|
+
case "definition":
|
|
4379
|
+
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
4380
|
+
case "dropdown":
|
|
4381
|
+
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
4382
|
+
case "explanation":
|
|
4383
|
+
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
4384
|
+
case "expression":
|
|
4385
|
+
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
4386
|
+
case "grapher":
|
|
4387
|
+
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
4388
|
+
case "group":
|
|
4389
|
+
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
4390
|
+
case "graded-group":
|
|
4391
|
+
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
4392
|
+
case "graded-group-set":
|
|
4393
|
+
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
4394
|
+
case "iframe":
|
|
4395
|
+
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
4396
|
+
case "image":
|
|
4397
|
+
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
4398
|
+
case "input-number":
|
|
4399
|
+
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
4400
|
+
case "interaction":
|
|
4401
|
+
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
4402
|
+
case "interactive-graph":
|
|
4403
|
+
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
4404
|
+
case "label-image":
|
|
4405
|
+
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
4406
|
+
case "matcher":
|
|
4407
|
+
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
4408
|
+
case "matrix":
|
|
4409
|
+
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
4410
|
+
case "measurer":
|
|
4411
|
+
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
4412
|
+
case "molecule-renderer":
|
|
4413
|
+
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
4414
|
+
case "number-line":
|
|
4415
|
+
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
4416
|
+
case "numeric-input":
|
|
4417
|
+
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
4418
|
+
case "orderer":
|
|
4419
|
+
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
4420
|
+
case "passage":
|
|
4421
|
+
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
4422
|
+
case "passage-ref":
|
|
4423
|
+
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
4424
|
+
case "passage-ref-target":
|
|
4425
|
+
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
4426
|
+
// used in test content. See:
|
|
4427
|
+
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
4428
|
+
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
4429
|
+
case "phet-simulation":
|
|
4430
|
+
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
4431
|
+
case "plotter":
|
|
4432
|
+
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
4433
|
+
case "python-program":
|
|
4434
|
+
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
4435
|
+
case "radio":
|
|
4436
|
+
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
4437
|
+
case "sorter":
|
|
4438
|
+
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
4439
|
+
case "table":
|
|
4440
|
+
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
4441
|
+
case "video":
|
|
4442
|
+
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
4443
|
+
case "sequence":
|
|
4444
|
+
// sequence is a deprecated widget type, and the corresponding
|
|
4445
|
+
// widget component no longer exists.
|
|
4446
|
+
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
4447
|
+
case "lights-puzzle":
|
|
4448
|
+
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
4449
|
+
case "simulator":
|
|
4450
|
+
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
4451
|
+
case "transformer":
|
|
4452
|
+
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
4453
|
+
default:
|
|
4454
|
+
return parseAndAssign(`${type} ${n}`, parseWidget(constant(type), any));
|
|
4455
|
+
}
|
|
4456
|
+
};
|
|
4457
|
+
const parseDeprecatedWidget = parseWidget(
|
|
4458
|
+
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
4459
|
+
(_, ctx) => ctx.success("deprecated-standin"),
|
|
4460
|
+
// Allow any widget options
|
|
4461
|
+
object({}));
|
|
4462
|
+
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
4463
|
+
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
4464
|
+
return ctx.failure("a string representing a positive integer", rawValue);
|
|
4465
|
+
}
|
|
4466
|
+
return ctx.success(+rawValue);
|
|
4467
|
+
};
|
|
4468
|
+
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
4469
|
+
|
|
4470
|
+
const parsePerseusRenderer = defaulted(object({
|
|
4471
|
+
// TODO(benchristel): content is also defaulted to empty string in
|
|
4472
|
+
// renderer.tsx. See if we can remove one default or the other.
|
|
4473
|
+
content: defaulted(string, () => ""),
|
|
4474
|
+
// This module has an import cycle with parseWidgetsMap, because the
|
|
4475
|
+
// `group` widget can contain another renderer.
|
|
4476
|
+
// The anonymous function below ensures that we don't try to access
|
|
4477
|
+
// parseWidgetsMap before it's defined.
|
|
4478
|
+
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
4479
|
+
metadata: optional(array(string)),
|
|
4480
|
+
images: parseImages
|
|
4481
|
+
}),
|
|
4482
|
+
// Default value
|
|
4483
|
+
() => ({
|
|
4484
|
+
content: "",
|
|
4485
|
+
widgets: {},
|
|
4486
|
+
images: {}
|
|
4487
|
+
}));
|
|
4488
|
+
|
|
4489
|
+
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
4490
|
+
|
|
4491
|
+
function _objectWithoutPropertiesLoose(r, e) {
|
|
4492
|
+
if (null == r) return {};
|
|
4493
|
+
var t = {};
|
|
4494
|
+
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
|
|
4495
|
+
if (e.includes(n)) continue;
|
|
4496
|
+
t[n] = r[n];
|
|
4497
|
+
}
|
|
4498
|
+
return t;
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
const parseHint = object({
|
|
4502
|
+
replace: optional(boolean),
|
|
4503
|
+
content: string,
|
|
4504
|
+
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
4505
|
+
metadata: optional(array(string)),
|
|
4506
|
+
images: parseImages
|
|
4507
|
+
});
|
|
4508
|
+
|
|
4509
|
+
const _excluded$5 = ["type", "options"];
|
|
4510
|
+
const parsePerseusItem$1 = object({
|
|
4511
|
+
question: parsePerseusRenderer,
|
|
4512
|
+
hints: defaulted(array(parseHint), () => []),
|
|
4513
|
+
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
4514
|
+
itemDataVersion: optional(object({
|
|
4515
|
+
major: number,
|
|
4516
|
+
minor: number
|
|
4517
|
+
})),
|
|
4518
|
+
// Deprecated field
|
|
4519
|
+
answer: any
|
|
4520
|
+
});
|
|
4521
|
+
|
|
4522
|
+
// Some answerAreas have extra fields, like:
|
|
4523
|
+
//
|
|
4524
|
+
// "answerArea": {
|
|
4525
|
+
// "type": "multiple",
|
|
4526
|
+
// "options": {
|
|
4527
|
+
// "content": "",
|
|
4528
|
+
// "images": {},
|
|
4529
|
+
// "widgets": {}
|
|
4530
|
+
// }
|
|
4531
|
+
// }
|
|
4532
|
+
//
|
|
4533
|
+
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
4534
|
+
// migration function removes them.
|
|
4535
|
+
function migrateAnswerArea(rawValue, ctx) {
|
|
4536
|
+
const rest = _objectWithoutPropertiesLoose(rawValue, _excluded$5);
|
|
4537
|
+
return ctx.success(rest);
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
/**
|
|
4541
|
+
* Helper to parse PerseusItem JSON
|
|
4542
|
+
* Why not just use JSON.parse? We want:
|
|
4543
|
+
* - To make sure types are correct
|
|
4544
|
+
* - To give us a central place to validate/transform output if needed
|
|
4545
|
+
* @deprecated - use parseAndMigratePerseusItem instead
|
|
4546
|
+
* @param {string} json - the stringified PerseusItem JSON
|
|
4547
|
+
* @returns {PerseusItem} the parsed PerseusItem object
|
|
4548
|
+
*/
|
|
4549
|
+
function parsePerseusItem(json) {
|
|
4550
|
+
// Try to block a cheating vector which relies on monkey-patching
|
|
4551
|
+
// JSON.parse
|
|
4552
|
+
if (isRealJSONParse(JSON.parse)) {
|
|
4553
|
+
return JSON.parse(json);
|
|
4554
|
+
}
|
|
4555
|
+
throw new Error("Something went wrong.");
|
|
4556
|
+
}
|
|
4557
|
+
/**
|
|
4558
|
+
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
4559
|
+
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
4560
|
+
* data.
|
|
4561
|
+
*
|
|
4562
|
+
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
4563
|
+
* failure, it will contain an error message describing where in the tree
|
|
4564
|
+
* parsing failed.
|
|
4565
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
4566
|
+
*/
|
|
4567
|
+
function parseAndMigratePerseusItem(json) {
|
|
4568
|
+
throwErrorIfCheatingDetected();
|
|
4569
|
+
const object = JSON.parse(json);
|
|
4570
|
+
const result = parse(object, parsePerseusItem$1);
|
|
4571
|
+
if (isFailure(result)) {
|
|
4572
|
+
return failure({
|
|
4573
|
+
message: result.detail,
|
|
4574
|
+
invalidObject: object
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4577
|
+
return result;
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
/**
|
|
4581
|
+
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
4582
|
+
* latest schema, and runtime-typechecks the result.
|
|
4583
|
+
*
|
|
4584
|
+
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
4585
|
+
* failure, it will contain an error message describing where in the tree
|
|
4586
|
+
* parsing failed.
|
|
4587
|
+
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
4588
|
+
*/
|
|
4589
|
+
function parseAndMigratePerseusArticle(json) {
|
|
4590
|
+
throwErrorIfCheatingDetected();
|
|
4591
|
+
const object = JSON.parse(json);
|
|
4592
|
+
const result = parse(object, parsePerseusArticle);
|
|
4593
|
+
if (isFailure(result)) {
|
|
4594
|
+
return failure({
|
|
4595
|
+
message: result.detail,
|
|
4596
|
+
invalidObject: object
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
return result;
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
/**
|
|
4603
|
+
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
4604
|
+
*/
|
|
4605
|
+
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
4606
|
+
function throwErrorIfCheatingDetected() {
|
|
4607
|
+
if (!isRealJSONParse(JSON.parse)) {
|
|
4608
|
+
throw new Error("Something went wrong.");
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
4613
|
+
const libName = "@khanacademy/perseus-core";
|
|
4614
|
+
const libVersion = "3.4.0";
|
|
4615
|
+
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
4616
|
+
|
|
4617
|
+
/**
|
|
4618
|
+
* @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
|
|
4619
|
+
*/
|
|
4620
|
+
const Errors = Object.freeze({
|
|
4621
|
+
/**
|
|
4622
|
+
* @property {ErrorKind} Unknown The kind of error is not known.
|
|
4623
|
+
*/
|
|
4624
|
+
Unknown: "Unknown",
|
|
4625
|
+
/**
|
|
4626
|
+
* @property {ErrorKind} Internal The error is internal to the executing code.
|
|
4627
|
+
*/
|
|
4628
|
+
Internal: "Internal",
|
|
4629
|
+
/**
|
|
4630
|
+
* @property {ErrorKind} InvalidInput There was a problem with the provided
|
|
4631
|
+
* input, such as the wrong format or a null value.
|
|
4632
|
+
*/
|
|
4633
|
+
InvalidInput: "InvalidInput",
|
|
4634
|
+
/**
|
|
4635
|
+
* @property {ErrorKind} NotAllowed There was a problem due to the state of
|
|
4636
|
+
* the system not matching the requested operation or input. For example,
|
|
4637
|
+
* trying to create a username that is valid, but is already taken by
|
|
4638
|
+
* another user. Use {@link InvalidInput} instead when the input isn't
|
|
4639
|
+
* valid regardless of the state of the system. Use {@link NotFound} when
|
|
4640
|
+
* the failure is due to not being able to find a resource.
|
|
4641
|
+
*/
|
|
4642
|
+
NotAllowed: "NotAllowed",
|
|
4643
|
+
/**
|
|
4644
|
+
* @property {ErrorKind} TransientService There was a problem when making a
|
|
4645
|
+
* request to a service.
|
|
4646
|
+
*/
|
|
4647
|
+
TransientService: "TransientService",
|
|
4648
|
+
/**
|
|
4649
|
+
* @property {ErrorKind} Service There was a non-transient problem when
|
|
4650
|
+
* making a request to service.
|
|
4651
|
+
*/
|
|
4652
|
+
Service: "Service"
|
|
4653
|
+
});
|
|
4654
|
+
|
|
4655
|
+
/**
|
|
4656
|
+
* @type {ErrorKind} The kind of error being reported
|
|
4657
|
+
*/
|
|
4658
|
+
|
|
4659
|
+
class PerseusError extends Error {
|
|
4660
|
+
constructor(message, kind, options) {
|
|
4661
|
+
super(message);
|
|
4662
|
+
this.kind = void 0;
|
|
4663
|
+
this.metadata = void 0;
|
|
4664
|
+
this.kind = kind;
|
|
4665
|
+
this.metadata = options == null ? void 0 : options.metadata;
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
|
|
4669
|
+
/**
|
|
4670
|
+
* _ utilities for objects
|
|
4671
|
+
*/
|
|
4672
|
+
|
|
4673
|
+
/**
|
|
4674
|
+
* Does a pluck on keys inside objects in an object
|
|
4675
|
+
*
|
|
4676
|
+
* Ex:
|
|
4677
|
+
* tools = {
|
|
4678
|
+
* translation: {
|
|
4679
|
+
* enabled: true
|
|
4680
|
+
* },
|
|
4681
|
+
* rotation: {
|
|
4682
|
+
* enabled: false
|
|
4683
|
+
* }
|
|
4684
|
+
* };
|
|
4685
|
+
* pluckObject(tools, "enabled") returns {
|
|
4686
|
+
* translation: true
|
|
4687
|
+
* rotation: false
|
|
4688
|
+
* }
|
|
4689
|
+
*/
|
|
4690
|
+
const pluck = function pluck(table, subKey) {
|
|
4691
|
+
return _.object(_.map(table, function (value, key) {
|
|
4692
|
+
return [key, value[subKey]];
|
|
4693
|
+
}));
|
|
4694
|
+
};
|
|
4695
|
+
|
|
4696
|
+
/**
|
|
4697
|
+
* Maps an object to an object
|
|
4698
|
+
*
|
|
4699
|
+
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
4700
|
+
* return value + 1;
|
|
4701
|
+
* });
|
|
4702
|
+
* {a: 2, b: 3}
|
|
4703
|
+
*/
|
|
4704
|
+
const mapObject = function mapObject(obj, lambda) {
|
|
4705
|
+
const result = {};
|
|
4706
|
+
Object.keys(obj).forEach(key => {
|
|
4707
|
+
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
4708
|
+
result[key] = lambda(obj[key], key);
|
|
4709
|
+
});
|
|
4710
|
+
return result;
|
|
4711
|
+
};
|
|
4712
|
+
|
|
4713
|
+
const defaultWidgetOptions$v = {
|
|
4714
|
+
items: [],
|
|
4715
|
+
categories: [],
|
|
4716
|
+
values: [],
|
|
4717
|
+
randomizeItems: false
|
|
4718
|
+
};
|
|
4719
|
+
const categorizerWidgetLogic = {
|
|
4720
|
+
name: "categorizer",
|
|
4721
|
+
defaultWidgetOptions: defaultWidgetOptions$v
|
|
4722
|
+
};
|
|
4723
|
+
|
|
4724
|
+
const DEFAULT_HEIGHT = 400;
|
|
4725
|
+
const defaultWidgetOptions$u = {
|
|
4726
|
+
programID: "",
|
|
4727
|
+
programType: null,
|
|
4728
|
+
settings: [{
|
|
4729
|
+
name: "",
|
|
4730
|
+
value: ""
|
|
4731
|
+
}],
|
|
4732
|
+
showEditor: false,
|
|
4733
|
+
showButtons: false,
|
|
4734
|
+
height: DEFAULT_HEIGHT
|
|
4735
|
+
};
|
|
4736
|
+
const csProgramWidgetLogic = {
|
|
4737
|
+
name: "cs-program",
|
|
4738
|
+
defaultWidgetOptions: defaultWidgetOptions$u,
|
|
4739
|
+
supportedAlignments: ["block", "full-width"]
|
|
4740
|
+
};
|
|
4741
|
+
|
|
4742
|
+
const defaultWidgetOptions$t = {
|
|
4743
|
+
togglePrompt: "",
|
|
4744
|
+
definition: ""
|
|
4745
|
+
};
|
|
4746
|
+
const definitionWidgetLogic = {
|
|
4747
|
+
name: "definition",
|
|
4748
|
+
defaultWidgetOptions: defaultWidgetOptions$t,
|
|
4749
|
+
defaultAlignment: "inline"
|
|
4750
|
+
};
|
|
4751
|
+
|
|
4752
|
+
const defaultWidgetOptions$s = {
|
|
4753
|
+
placeholder: "",
|
|
4754
|
+
choices: [{
|
|
4755
|
+
content: "",
|
|
4756
|
+
correct: false
|
|
4757
|
+
}]
|
|
4758
|
+
};
|
|
4759
|
+
const dropdownWidgetLogic = {
|
|
4760
|
+
name: "definition",
|
|
4761
|
+
defaultWidgetOptions: defaultWidgetOptions$s,
|
|
4762
|
+
defaultAlignment: "inline-block"
|
|
4763
|
+
};
|
|
4764
|
+
|
|
4765
|
+
const defaultWidgetOptions$r = {
|
|
4766
|
+
showPrompt: "Explain",
|
|
4767
|
+
hidePrompt: "Hide explanation",
|
|
4768
|
+
explanation: "explanation goes here\n\nmore explanation",
|
|
4769
|
+
widgets: {}
|
|
4770
|
+
};
|
|
4771
|
+
const explanationWidgetLogic = {
|
|
4772
|
+
name: "explanation",
|
|
4773
|
+
defaultWidgetOptions: defaultWidgetOptions$r,
|
|
4774
|
+
defaultAlignment: "inline"
|
|
4775
|
+
};
|
|
2779
4776
|
|
|
2780
|
-
|
|
4777
|
+
const currentVersion$3 = {
|
|
4778
|
+
major: 1,
|
|
4779
|
+
minor: 0
|
|
4780
|
+
};
|
|
4781
|
+
const widgetOptionsUpgrades$2 = {
|
|
4782
|
+
"1": v0options => ({
|
|
4783
|
+
times: v0options.times,
|
|
4784
|
+
buttonSets: v0options.buttonSets,
|
|
4785
|
+
functions: v0options.functions,
|
|
4786
|
+
buttonsVisible: v0options.buttonsVisible,
|
|
4787
|
+
visibleLabel: v0options.visibleLabel,
|
|
4788
|
+
ariaLabel: v0options.ariaLabel,
|
|
4789
|
+
answerForms: [{
|
|
4790
|
+
considered: "correct",
|
|
4791
|
+
form: v0options.form,
|
|
4792
|
+
simplify: v0options.simplify,
|
|
4793
|
+
value: v0options.value
|
|
4794
|
+
}]
|
|
4795
|
+
})
|
|
4796
|
+
};
|
|
4797
|
+
const defaultWidgetOptions$q = {
|
|
4798
|
+
answerForms: [],
|
|
4799
|
+
times: false,
|
|
4800
|
+
buttonSets: ["basic"],
|
|
4801
|
+
functions: ["f", "g", "h"]
|
|
4802
|
+
};
|
|
2781
4803
|
|
|
2782
|
-
|
|
4804
|
+
const expressionWidgetLogic = {
|
|
4805
|
+
name: "expression",
|
|
4806
|
+
version: currentVersion$3,
|
|
4807
|
+
widgetOptionsUpgrades: widgetOptionsUpgrades$2,
|
|
4808
|
+
defaultWidgetOptions: defaultWidgetOptions$q,
|
|
4809
|
+
defaultAlignment: "inline-block"
|
|
4810
|
+
};
|
|
2783
4811
|
|
|
2784
|
-
|
|
4812
|
+
const defaultWidgetOptions$p = {
|
|
4813
|
+
title: "",
|
|
4814
|
+
content: "",
|
|
4815
|
+
widgets: {},
|
|
4816
|
+
images: {},
|
|
4817
|
+
hint: null
|
|
4818
|
+
};
|
|
4819
|
+
const gradedGroupWidgetLogic = {
|
|
4820
|
+
name: "graded-group",
|
|
4821
|
+
defaultWidgetOptions: defaultWidgetOptions$p
|
|
4822
|
+
};
|
|
2785
4823
|
|
|
2786
|
-
|
|
4824
|
+
const defaultWidgetOptions$o = {
|
|
4825
|
+
gradedGroups: []
|
|
4826
|
+
};
|
|
4827
|
+
const gradedGroupSetWidgetLogic = {
|
|
4828
|
+
name: "graded-group-set",
|
|
4829
|
+
defaultWidgetOptions: defaultWidgetOptions$o
|
|
4830
|
+
};
|
|
2787
4831
|
|
|
2788
|
-
|
|
4832
|
+
const defaultWidgetOptions$n = {
|
|
4833
|
+
graph: {
|
|
4834
|
+
labels: ["x", "y"],
|
|
4835
|
+
range: [[-10, 10], [-10, 10]],
|
|
4836
|
+
step: [1, 1],
|
|
4837
|
+
backgroundImage: {
|
|
4838
|
+
url: null
|
|
4839
|
+
},
|
|
4840
|
+
markings: "graph",
|
|
4841
|
+
rulerLabel: "",
|
|
4842
|
+
rulerTicks: 10,
|
|
4843
|
+
valid: true,
|
|
4844
|
+
showTooltips: false
|
|
4845
|
+
},
|
|
4846
|
+
correct: {
|
|
4847
|
+
type: "linear",
|
|
4848
|
+
coords: null
|
|
4849
|
+
},
|
|
4850
|
+
availableTypes: ["linear"]
|
|
4851
|
+
};
|
|
4852
|
+
const grapherWidgetLogic = {
|
|
4853
|
+
name: "grapher",
|
|
4854
|
+
defaultWidgetOptions: defaultWidgetOptions$n
|
|
4855
|
+
};
|
|
2789
4856
|
|
|
2790
|
-
|
|
4857
|
+
const defaultWidgetOptions$m = {
|
|
4858
|
+
content: "",
|
|
4859
|
+
widgets: {},
|
|
4860
|
+
images: {},
|
|
4861
|
+
// `undefined` instead of `null` so that getDefaultProps works for
|
|
4862
|
+
// `the GroupMetadataEditor`
|
|
4863
|
+
metadata: undefined
|
|
4864
|
+
};
|
|
4865
|
+
const groupWidgetLogic = {
|
|
4866
|
+
name: "group",
|
|
4867
|
+
defaultWidgetOptions: defaultWidgetOptions$m
|
|
4868
|
+
};
|
|
2791
4869
|
|
|
2792
|
-
|
|
4870
|
+
const defaultWidgetOptions$l = {
|
|
4871
|
+
url: "",
|
|
4872
|
+
settings: [{
|
|
4873
|
+
name: "",
|
|
4874
|
+
value: ""
|
|
4875
|
+
}],
|
|
4876
|
+
width: "400",
|
|
4877
|
+
height: "400",
|
|
4878
|
+
allowFullScreen: false,
|
|
4879
|
+
allowTopNavigation: false
|
|
4880
|
+
};
|
|
4881
|
+
const iframeWidgetLogic = {
|
|
4882
|
+
name: "iframe",
|
|
4883
|
+
defaultWidgetOptions: defaultWidgetOptions$l
|
|
4884
|
+
};
|
|
2793
4885
|
|
|
2794
|
-
|
|
4886
|
+
const defaultWidgetOptions$k = {
|
|
4887
|
+
title: "",
|
|
4888
|
+
range: [[0, 10], [0, 10]],
|
|
4889
|
+
box: [400, 400],
|
|
4890
|
+
backgroundImage: {
|
|
4891
|
+
url: null,
|
|
4892
|
+
width: 0,
|
|
4893
|
+
height: 0
|
|
4894
|
+
},
|
|
4895
|
+
labels: [],
|
|
4896
|
+
alt: "",
|
|
4897
|
+
caption: ""
|
|
4898
|
+
};
|
|
4899
|
+
const imageWidgetLogic = {
|
|
4900
|
+
name: "image",
|
|
4901
|
+
defaultWidgetOptions: defaultWidgetOptions$k,
|
|
4902
|
+
supportedAlignments: ["block", "full-width"],
|
|
4903
|
+
defaultAlignment: "block"
|
|
4904
|
+
};
|
|
2795
4905
|
|
|
2796
|
-
|
|
4906
|
+
const defaultWidgetOptions$j = {
|
|
4907
|
+
value: 0,
|
|
4908
|
+
simplify: "required",
|
|
4909
|
+
size: "normal",
|
|
4910
|
+
inexact: false,
|
|
4911
|
+
maxError: 0.1,
|
|
4912
|
+
answerType: "number",
|
|
4913
|
+
rightAlign: false
|
|
4914
|
+
};
|
|
4915
|
+
const inputNumberWidgetLogic = {
|
|
4916
|
+
name: "input-number",
|
|
4917
|
+
defaultWidgetOptions: defaultWidgetOptions$j,
|
|
4918
|
+
defaultAlignment: "inline-block"
|
|
4919
|
+
};
|
|
2797
4920
|
|
|
2798
|
-
|
|
4921
|
+
const defaultWidgetOptions$i = {
|
|
4922
|
+
graph: {
|
|
4923
|
+
box: [400, 400],
|
|
4924
|
+
labels: ["x", "y"],
|
|
4925
|
+
range: [[-10, 10], [-10, 10]],
|
|
4926
|
+
tickStep: [1, 1],
|
|
4927
|
+
gridStep: [1, 1],
|
|
4928
|
+
markings: "graph"
|
|
4929
|
+
},
|
|
4930
|
+
elements: []
|
|
4931
|
+
};
|
|
4932
|
+
const interactionWidgetLogic = {
|
|
4933
|
+
name: "interaction",
|
|
4934
|
+
defaultWidgetOptions: defaultWidgetOptions$i
|
|
4935
|
+
};
|
|
2799
4936
|
|
|
2800
|
-
|
|
4937
|
+
const defaultWidgetOptions$h = {
|
|
4938
|
+
labels: ["x", "y"],
|
|
4939
|
+
range: [[-10, 10], [-10, 10]],
|
|
4940
|
+
step: [1, 1],
|
|
4941
|
+
backgroundImage: {
|
|
4942
|
+
url: null
|
|
4943
|
+
},
|
|
4944
|
+
markings: "graph",
|
|
4945
|
+
showTooltips: false,
|
|
4946
|
+
showProtractor: false,
|
|
4947
|
+
graph: {
|
|
4948
|
+
type: "linear"
|
|
4949
|
+
},
|
|
4950
|
+
correct: {
|
|
4951
|
+
type: "linear",
|
|
4952
|
+
coords: null
|
|
4953
|
+
}
|
|
4954
|
+
};
|
|
4955
|
+
const interactiveGraphWidgetLogic = {
|
|
4956
|
+
name: "interactive-graph",
|
|
4957
|
+
defaultWidgetOptions: defaultWidgetOptions$h
|
|
4958
|
+
};
|
|
2801
4959
|
|
|
2802
|
-
|
|
4960
|
+
const defaultWidgetOptions$g = {
|
|
4961
|
+
choices: [],
|
|
4962
|
+
imageAlt: "",
|
|
4963
|
+
imageUrl: "",
|
|
4964
|
+
imageWidth: 0,
|
|
4965
|
+
imageHeight: 0,
|
|
4966
|
+
markers: [],
|
|
4967
|
+
multipleAnswers: false,
|
|
4968
|
+
hideChoicesFromInstructions: false
|
|
4969
|
+
};
|
|
4970
|
+
const labelImageWidgetLogic = {
|
|
4971
|
+
name: "label-image",
|
|
4972
|
+
defaultWidgetOptions: defaultWidgetOptions$g
|
|
4973
|
+
};
|
|
2803
4974
|
|
|
2804
|
-
|
|
4975
|
+
const defaultWidgetOptions$f = {
|
|
4976
|
+
left: ["$x$", "$y$", "$z$"],
|
|
4977
|
+
right: ["$1$", "$2$", "$3$"],
|
|
4978
|
+
labels: ["test", "label"],
|
|
4979
|
+
orderMatters: false,
|
|
4980
|
+
padding: true
|
|
4981
|
+
};
|
|
4982
|
+
const matcherWidgetLogic = {
|
|
4983
|
+
name: "matcher",
|
|
4984
|
+
defaultWidgetOptions: defaultWidgetOptions$f
|
|
4985
|
+
};
|
|
2805
4986
|
|
|
2806
|
-
|
|
4987
|
+
const defaultWidgetOptions$e = {
|
|
4988
|
+
matrixBoardSize: [3, 3],
|
|
4989
|
+
answers: [[]],
|
|
4990
|
+
prefix: "",
|
|
4991
|
+
suffix: "",
|
|
4992
|
+
cursorPosition: [0, 0]
|
|
4993
|
+
};
|
|
4994
|
+
const matrixWidgetLogic = {
|
|
4995
|
+
name: "matrix",
|
|
4996
|
+
defaultWidgetOptions: defaultWidgetOptions$e
|
|
4997
|
+
};
|
|
2807
4998
|
|
|
2808
|
-
|
|
4999
|
+
const _excluded$4 = ["imageUrl", "imageTop", "imageLeft"];
|
|
5000
|
+
const currentVersion$2 = {
|
|
5001
|
+
major: 1,
|
|
5002
|
+
minor: 0
|
|
5003
|
+
};
|
|
5004
|
+
const widgetOptionsUpgrades$1 = {
|
|
5005
|
+
"1": v0options => {
|
|
5006
|
+
const {
|
|
5007
|
+
imageUrl,
|
|
5008
|
+
imageTop,
|
|
5009
|
+
imageLeft
|
|
5010
|
+
} = v0options,
|
|
5011
|
+
rest = _objectWithoutPropertiesLoose(v0options, _excluded$4);
|
|
5012
|
+
return _extends({}, rest, {
|
|
5013
|
+
image: {
|
|
5014
|
+
url: imageUrl,
|
|
5015
|
+
top: imageTop,
|
|
5016
|
+
left: imageLeft
|
|
5017
|
+
}
|
|
5018
|
+
});
|
|
5019
|
+
}
|
|
5020
|
+
};
|
|
5021
|
+
const defaultWidgetOptions$d = {
|
|
5022
|
+
box: [480, 480],
|
|
5023
|
+
image: {},
|
|
5024
|
+
showProtractor: true,
|
|
5025
|
+
showRuler: false,
|
|
5026
|
+
rulerLabel: "",
|
|
5027
|
+
rulerTicks: 10,
|
|
5028
|
+
rulerPixels: 40,
|
|
5029
|
+
rulerLength: 10
|
|
5030
|
+
};
|
|
2809
5031
|
|
|
2810
|
-
|
|
5032
|
+
const measurerWidgetLogic = {
|
|
5033
|
+
name: "measurer",
|
|
5034
|
+
version: currentVersion$2,
|
|
5035
|
+
widgetOptionsUpgrades: widgetOptionsUpgrades$1,
|
|
5036
|
+
defaultWidgetOptions: defaultWidgetOptions$d
|
|
5037
|
+
};
|
|
2811
5038
|
|
|
2812
|
-
|
|
5039
|
+
const defaultWidgetOptions$c = {
|
|
5040
|
+
range: [0, 10],
|
|
5041
|
+
labelRange: [null, null],
|
|
5042
|
+
labelStyle: "decimal",
|
|
5043
|
+
labelTicks: true,
|
|
5044
|
+
divisionRange: [1, 12],
|
|
5045
|
+
numDivisions: 5,
|
|
5046
|
+
snapDivisions: 2,
|
|
5047
|
+
tickStep: null,
|
|
5048
|
+
correctRel: "eq",
|
|
5049
|
+
correctX: null,
|
|
5050
|
+
initialX: null,
|
|
5051
|
+
showTooltips: false
|
|
5052
|
+
};
|
|
5053
|
+
const numberLineWidgetLogic = {
|
|
5054
|
+
name: "number-line",
|
|
5055
|
+
defaultWidgetOptions: defaultWidgetOptions$c
|
|
5056
|
+
};
|
|
2813
5057
|
|
|
2814
|
-
|
|
5058
|
+
const defaultWidgetOptions$b = {
|
|
5059
|
+
answers: [{
|
|
5060
|
+
value: null,
|
|
5061
|
+
status: "correct",
|
|
5062
|
+
message: "",
|
|
5063
|
+
simplify: "required",
|
|
5064
|
+
answerForms: [],
|
|
5065
|
+
strict: false,
|
|
5066
|
+
maxError: null
|
|
5067
|
+
}],
|
|
5068
|
+
size: "normal",
|
|
5069
|
+
coefficient: false,
|
|
5070
|
+
labelText: "",
|
|
5071
|
+
rightAlign: false
|
|
5072
|
+
};
|
|
5073
|
+
const numericInputWidgetLogic = {
|
|
5074
|
+
name: "numeric-input",
|
|
5075
|
+
defaultWidgetOptions: defaultWidgetOptions$b,
|
|
5076
|
+
defaultAlignment: "inline-block"
|
|
5077
|
+
};
|
|
2815
5078
|
|
|
2816
|
-
|
|
5079
|
+
const defaultWidgetOptions$a = {
|
|
5080
|
+
correctOptions: [{
|
|
5081
|
+
content: "$x$"
|
|
5082
|
+
}],
|
|
5083
|
+
otherOptions: [{
|
|
5084
|
+
content: "$y$"
|
|
5085
|
+
}],
|
|
5086
|
+
height: "normal",
|
|
5087
|
+
layout: "horizontal"
|
|
5088
|
+
};
|
|
5089
|
+
const ordererWidgetLogic = {
|
|
5090
|
+
name: "orderer",
|
|
5091
|
+
defaultWidgetOptions: defaultWidgetOptions$a
|
|
5092
|
+
};
|
|
2817
5093
|
|
|
2818
|
-
|
|
5094
|
+
const defaultWidgetOptions$9 = {
|
|
5095
|
+
passageTitle: "",
|
|
5096
|
+
passageText: "",
|
|
5097
|
+
footnotes: "",
|
|
5098
|
+
showLineNumbers: true
|
|
5099
|
+
};
|
|
5100
|
+
const passageWidgetLogic = {
|
|
5101
|
+
name: "passage",
|
|
5102
|
+
defaultWidgetOptions: defaultWidgetOptions$9
|
|
5103
|
+
};
|
|
2819
5104
|
|
|
2820
|
-
|
|
5105
|
+
const currentVersion$1 = {
|
|
5106
|
+
major: 0,
|
|
5107
|
+
minor: 1
|
|
5108
|
+
};
|
|
5109
|
+
const defaultWidgetOptions$8 = {
|
|
5110
|
+
passageNumber: 1,
|
|
5111
|
+
referenceNumber: 1,
|
|
5112
|
+
summaryText: ""
|
|
5113
|
+
};
|
|
2821
5114
|
|
|
2822
|
-
|
|
5115
|
+
const passageRefWidgetLogic = {
|
|
5116
|
+
name: "passageRef",
|
|
5117
|
+
version: currentVersion$1,
|
|
5118
|
+
defaultWidgetOptions: defaultWidgetOptions$8,
|
|
5119
|
+
defaultAlignment: "inline"
|
|
5120
|
+
};
|
|
2823
5121
|
|
|
2824
|
-
|
|
5122
|
+
const defaultWidgetOptions$7 = {
|
|
5123
|
+
content: ""
|
|
5124
|
+
};
|
|
5125
|
+
const passageRefTargetWidgetLogic = {
|
|
5126
|
+
name: "passageRefTarget",
|
|
5127
|
+
defaultWidgetOptions: defaultWidgetOptions$7,
|
|
5128
|
+
defaultAlignment: "inline"
|
|
5129
|
+
};
|
|
2825
5130
|
|
|
2826
|
-
|
|
5131
|
+
const defaultWidgetOptions$6 = {
|
|
5132
|
+
url: "",
|
|
5133
|
+
description: ""
|
|
5134
|
+
};
|
|
5135
|
+
const phetSimulationWidgetLogic = {
|
|
5136
|
+
name: "phet-simulation",
|
|
5137
|
+
defaultWidgetOptions: defaultWidgetOptions$6
|
|
5138
|
+
};
|
|
2827
5139
|
|
|
2828
|
-
|
|
5140
|
+
const defaultWidgetOptions$5 = {
|
|
5141
|
+
scaleY: 1,
|
|
5142
|
+
maxY: 10,
|
|
5143
|
+
snapsPerLine: 2,
|
|
5144
|
+
correct: [1],
|
|
5145
|
+
starting: [1],
|
|
5146
|
+
type: "bar",
|
|
5147
|
+
labels: ["", ""],
|
|
5148
|
+
categories: [""],
|
|
5149
|
+
picSize: 30,
|
|
5150
|
+
picBoxHeight: 36,
|
|
5151
|
+
plotDimensions: [275, 200],
|
|
5152
|
+
labelInterval: 1,
|
|
5153
|
+
picUrl: null
|
|
5154
|
+
};
|
|
5155
|
+
const plotterWidgetLogic = {
|
|
5156
|
+
name: "plotter",
|
|
5157
|
+
defaultWidgetOptions: defaultWidgetOptions$5
|
|
5158
|
+
};
|
|
2829
5159
|
|
|
2830
|
-
|
|
5160
|
+
const defaultWidgetOptions$4 = {
|
|
5161
|
+
programID: "",
|
|
5162
|
+
height: 400
|
|
5163
|
+
};
|
|
5164
|
+
const pythonProgramWidgetLogic = {
|
|
5165
|
+
name: "python-program",
|
|
5166
|
+
defaultWidgetOptions: defaultWidgetOptions$4
|
|
5167
|
+
};
|
|
2831
5168
|
|
|
2832
|
-
|
|
5169
|
+
const _excluded$3 = ["noneOfTheAbove"];
|
|
5170
|
+
const currentVersion = {
|
|
5171
|
+
major: 1,
|
|
5172
|
+
minor: 0
|
|
5173
|
+
};
|
|
5174
|
+
const widgetOptionsUpgrades = {
|
|
5175
|
+
"1": v0props => {
|
|
5176
|
+
const {
|
|
5177
|
+
noneOfTheAbove
|
|
5178
|
+
} = v0props,
|
|
5179
|
+
rest = _objectWithoutPropertiesLoose(v0props, _excluded$3);
|
|
5180
|
+
if (noneOfTheAbove) {
|
|
5181
|
+
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
5182
|
+
}
|
|
5183
|
+
return _extends({}, rest, {
|
|
5184
|
+
hasNoneOfTheAbove: false
|
|
5185
|
+
});
|
|
5186
|
+
}
|
|
5187
|
+
};
|
|
5188
|
+
const defaultWidgetOptions$3 = {
|
|
5189
|
+
choices: [{}, {}, {}, {}],
|
|
5190
|
+
displayCount: null,
|
|
5191
|
+
randomize: false,
|
|
5192
|
+
hasNoneOfTheAbove: false,
|
|
5193
|
+
multipleSelect: false,
|
|
5194
|
+
countChoices: false,
|
|
5195
|
+
deselectEnabled: false
|
|
5196
|
+
};
|
|
2833
5197
|
|
|
2834
|
-
|
|
5198
|
+
const radioWidgetLogic = {
|
|
5199
|
+
name: "radio",
|
|
5200
|
+
version: currentVersion,
|
|
5201
|
+
widgetOptionsUpgrades: widgetOptionsUpgrades,
|
|
5202
|
+
defaultWidgetOptions: defaultWidgetOptions$3
|
|
5203
|
+
};
|
|
5204
|
+
|
|
5205
|
+
const defaultWidgetOptions$2 = {
|
|
5206
|
+
correct: ["$x$", "$y$", "$z$"],
|
|
5207
|
+
layout: "horizontal",
|
|
5208
|
+
padding: true
|
|
5209
|
+
};
|
|
5210
|
+
const sorterWidgetLogic = {
|
|
5211
|
+
name: "sorter",
|
|
5212
|
+
defaultWidgetOptions: defaultWidgetOptions$2
|
|
5213
|
+
};
|
|
5214
|
+
|
|
5215
|
+
const defaultRows = 4;
|
|
5216
|
+
const defaultColumns = 1;
|
|
5217
|
+
|
|
5218
|
+
// initialize a 2D array
|
|
5219
|
+
// (defaultRows x defaultColumns) of empty strings
|
|
5220
|
+
const answers = new Array(defaultRows).fill(0).map(() => new Array(defaultColumns).fill(""));
|
|
5221
|
+
const defaultWidgetOptions$1 = {
|
|
5222
|
+
headers: [""],
|
|
5223
|
+
rows: defaultRows,
|
|
5224
|
+
columns: defaultColumns,
|
|
5225
|
+
answers: answers
|
|
5226
|
+
};
|
|
5227
|
+
const tableWidgetLogic = {
|
|
5228
|
+
name: "table",
|
|
5229
|
+
defaultWidgetOptions: defaultWidgetOptions$1
|
|
5230
|
+
};
|
|
5231
|
+
|
|
5232
|
+
const defaultWidgetOptions = {
|
|
5233
|
+
location: ""
|
|
5234
|
+
};
|
|
5235
|
+
const videoWidgetLogic = {
|
|
5236
|
+
name: "video",
|
|
5237
|
+
defaultWidgetOptions,
|
|
5238
|
+
supportedAlignments: ["block", "float-left", "float-right", "full-width"],
|
|
5239
|
+
defaultAlignment: "block"
|
|
5240
|
+
};
|
|
5241
|
+
|
|
5242
|
+
const widgets = {};
|
|
5243
|
+
function registerWidget(type, logic) {
|
|
5244
|
+
widgets[type] = logic;
|
|
5245
|
+
}
|
|
5246
|
+
function isWidgetRegistered(type) {
|
|
5247
|
+
const widgetLogic = widgets[type];
|
|
5248
|
+
return !!widgetLogic;
|
|
5249
|
+
}
|
|
5250
|
+
function getCurrentVersion(type) {
|
|
5251
|
+
const widgetLogic = widgets[type];
|
|
5252
|
+
return (widgetLogic == null ? void 0 : widgetLogic.version) || {
|
|
5253
|
+
major: 0,
|
|
5254
|
+
minor: 0
|
|
5255
|
+
};
|
|
5256
|
+
}
|
|
5257
|
+
function getWidgetOptionsUpgrades(type) {
|
|
5258
|
+
const widgetLogic = widgets[type];
|
|
5259
|
+
return (widgetLogic == null ? void 0 : widgetLogic.widgetOptionsUpgrades) || {};
|
|
5260
|
+
}
|
|
5261
|
+
function getDefaultWidgetOptions(type) {
|
|
5262
|
+
const widgetLogic = widgets[type];
|
|
5263
|
+
return (widgetLogic == null ? void 0 : widgetLogic.defaultWidgetOptions) || {};
|
|
5264
|
+
}
|
|
2835
5265
|
|
|
2836
5266
|
/**
|
|
2837
|
-
*
|
|
5267
|
+
* Handling for the optional alignments for widgets
|
|
5268
|
+
* See widget-container.jsx for details on how alignments are implemented.
|
|
2838
5269
|
*/
|
|
2839
5270
|
|
|
2840
5271
|
/**
|
|
2841
|
-
*
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2845
|
-
*
|
|
5272
|
+
* Returns the list of supported alignments for the given (string) widget
|
|
5273
|
+
* type. This is used primarily at editing time to display the choices
|
|
5274
|
+
* for the user.
|
|
5275
|
+
*
|
|
5276
|
+
* Supported alignments are given as an array of strings in the exports of
|
|
5277
|
+
* a widget's module.
|
|
2846
5278
|
*/
|
|
5279
|
+
const getSupportedAlignments = type => {
|
|
5280
|
+
var _widgetLogic$supporte;
|
|
5281
|
+
const widgetLogic = widgets[type];
|
|
5282
|
+
if (!(widgetLogic != null && (_widgetLogic$supporte = widgetLogic.supportedAlignments) != null && _widgetLogic$supporte[0])) {
|
|
5283
|
+
// default alignments
|
|
5284
|
+
return ["default"];
|
|
5285
|
+
}
|
|
5286
|
+
return widgetLogic == null ? void 0 : widgetLogic.supportedAlignments;
|
|
5287
|
+
};
|
|
2847
5288
|
|
|
2848
|
-
|
|
5289
|
+
/**
|
|
5290
|
+
* For the given (string) widget type, determine the default alignment for
|
|
5291
|
+
* the widget. This is used at rendering time to go from "default" alignment
|
|
5292
|
+
* to the actual alignment displayed on the screen.
|
|
5293
|
+
*
|
|
5294
|
+
* The default alignment is given either as a string (called
|
|
5295
|
+
* `defaultAlignment`) or a function (called `getDefaultAlignment`) on
|
|
5296
|
+
* the exports of a widget's module.
|
|
5297
|
+
*/
|
|
5298
|
+
const getDefaultAlignment = type => {
|
|
5299
|
+
const widgetLogic = widgets[type];
|
|
5300
|
+
if (!(widgetLogic != null && widgetLogic.defaultAlignment)) {
|
|
5301
|
+
return "block";
|
|
5302
|
+
}
|
|
5303
|
+
return widgetLogic.defaultAlignment;
|
|
5304
|
+
};
|
|
5305
|
+
registerWidget("categorizer", categorizerWidgetLogic);
|
|
5306
|
+
registerWidget("cs-program", csProgramWidgetLogic);
|
|
5307
|
+
registerWidget("definition", definitionWidgetLogic);
|
|
5308
|
+
registerWidget("dropdown", dropdownWidgetLogic);
|
|
5309
|
+
registerWidget("explanation", explanationWidgetLogic);
|
|
5310
|
+
registerWidget("expression", expressionWidgetLogic);
|
|
5311
|
+
registerWidget("graded-group", gradedGroupWidgetLogic);
|
|
5312
|
+
registerWidget("graded-group-set", gradedGroupSetWidgetLogic);
|
|
5313
|
+
registerWidget("grapher", grapherWidgetLogic);
|
|
5314
|
+
registerWidget("group", groupWidgetLogic);
|
|
5315
|
+
registerWidget("iframe", iframeWidgetLogic);
|
|
5316
|
+
registerWidget("image", imageWidgetLogic);
|
|
5317
|
+
registerWidget("input-number", inputNumberWidgetLogic);
|
|
5318
|
+
registerWidget("interaction", interactionWidgetLogic);
|
|
5319
|
+
registerWidget("interactive-graph", interactiveGraphWidgetLogic);
|
|
5320
|
+
registerWidget("label-image", labelImageWidgetLogic);
|
|
5321
|
+
registerWidget("matcher", matcherWidgetLogic);
|
|
5322
|
+
registerWidget("matrix", matrixWidgetLogic);
|
|
5323
|
+
registerWidget("measurer", measurerWidgetLogic);
|
|
5324
|
+
registerWidget("number-line", numberLineWidgetLogic);
|
|
5325
|
+
registerWidget("numeric-input", numericInputWidgetLogic);
|
|
5326
|
+
registerWidget("orderer", ordererWidgetLogic);
|
|
5327
|
+
registerWidget("passage", passageWidgetLogic);
|
|
5328
|
+
registerWidget("passage-ref", passageRefWidgetLogic);
|
|
5329
|
+
registerWidget("passage-ref-target", passageRefTargetWidgetLogic);
|
|
5330
|
+
registerWidget("phet-simulation", phetSimulationWidgetLogic);
|
|
5331
|
+
registerWidget("plotter", plotterWidgetLogic);
|
|
5332
|
+
registerWidget("python-program", pythonProgramWidgetLogic);
|
|
5333
|
+
registerWidget("radio", radioWidgetLogic);
|
|
5334
|
+
registerWidget("sorter", sorterWidgetLogic);
|
|
5335
|
+
registerWidget("table", tableWidgetLogic);
|
|
5336
|
+
registerWidget("video", videoWidgetLogic);
|
|
5337
|
+
|
|
5338
|
+
var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
|
|
5339
|
+
__proto__: null,
|
|
5340
|
+
isWidgetRegistered: isWidgetRegistered,
|
|
5341
|
+
getCurrentVersion: getCurrentVersion,
|
|
5342
|
+
getWidgetOptionsUpgrades: getWidgetOptionsUpgrades,
|
|
5343
|
+
getDefaultWidgetOptions: getDefaultWidgetOptions,
|
|
5344
|
+
getSupportedAlignments: getSupportedAlignments,
|
|
5345
|
+
getDefaultAlignment: getDefaultAlignment
|
|
5346
|
+
});
|
|
2849
5347
|
|
|
2850
|
-
|
|
5348
|
+
const DEFAULT_STATIC = false;
|
|
5349
|
+
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
5350
|
+
const type = oldWidgetInfo.type;
|
|
5351
|
+
// NOTE(jeremy): This looks like it could be replaced by fixing types so
|
|
5352
|
+
// that `type` is non-optional. But we're seeing this in Sentry today so I
|
|
5353
|
+
// suspect we have legacy data (potentially unpublished) and we should
|
|
5354
|
+
// figure that out before depending solely on types.
|
|
5355
|
+
if (!_.isString(type)) {
|
|
5356
|
+
throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
|
|
5357
|
+
}
|
|
5358
|
+
if (!isWidgetRegistered(type)) {
|
|
5359
|
+
// If we have a widget that isn't registered, we can't upgrade it
|
|
5360
|
+
// TODO(aria): Figure out what the best thing to do here would be
|
|
5361
|
+
return oldWidgetInfo;
|
|
5362
|
+
}
|
|
2851
5363
|
|
|
2852
|
-
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
5364
|
+
// Unversioned widgets (pre-July 2014) are all implicitly 0.0
|
|
5365
|
+
const initialVersion = oldWidgetInfo.version || {
|
|
5366
|
+
major: 0,
|
|
5367
|
+
minor: 0
|
|
5368
|
+
};
|
|
5369
|
+
const latestVersion = getCurrentVersion(type);
|
|
5370
|
+
|
|
5371
|
+
// If the widget version is later than what we understand (major
|
|
5372
|
+
// version is higher than latest, or major versions are equal and minor
|
|
5373
|
+
// version is higher than latest), don't perform any upgrades.
|
|
5374
|
+
if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
|
|
5375
|
+
return oldWidgetInfo;
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
// We do a clone here so that it's safe to mutate the input parameter
|
|
5379
|
+
// in propUpgrades functions (which I will probably accidentally do at
|
|
5380
|
+
// some point, and we would like to not break when that happens).
|
|
5381
|
+
let newEditorOptions = _.clone(oldWidgetInfo.options) || {};
|
|
5382
|
+
const upgradePropsMap = getWidgetOptionsUpgrades(type);
|
|
5383
|
+
|
|
5384
|
+
// Empty props usually mean a newly created widget by the editor,
|
|
5385
|
+
// and are always considerered up-to-date.
|
|
5386
|
+
// Mostly, we'd rather not run upgrade functions on props that are
|
|
5387
|
+
// not complete.
|
|
5388
|
+
if (_.keys(newEditorOptions).length !== 0) {
|
|
5389
|
+
// We loop through all the versions after the current version of
|
|
5390
|
+
// the loaded widget, up to and including the latest version of the
|
|
5391
|
+
// loaded widget, and run the upgrade function to bring our loaded
|
|
5392
|
+
// widget's props up to that version.
|
|
5393
|
+
// There is a little subtlety here in that we call
|
|
5394
|
+
// upgradePropsMap[1] to upgrade *to* version 1,
|
|
5395
|
+
// (not from version 1).
|
|
5396
|
+
for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
|
|
5397
|
+
if (upgradePropsMap[String(nextVersion)]) {
|
|
5398
|
+
newEditorOptions = upgradePropsMap[String(nextVersion)](newEditorOptions);
|
|
5399
|
+
} else {
|
|
5400
|
+
throw new PerseusError("No upgrade found for widget. Cannot render.", Errors.Internal, {
|
|
5401
|
+
metadata: {
|
|
5402
|
+
type,
|
|
5403
|
+
fromMajorVersion: nextVersion - 1,
|
|
5404
|
+
toMajorVersion: nextVersion,
|
|
5405
|
+
oldWidgetInfo: JSON.stringify(oldWidgetInfo)
|
|
5406
|
+
}
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
|
|
5412
|
+
// Minor version upgrades (eg. new optional props) don't have
|
|
5413
|
+
// transform functions. Instead, we fill in the new props with their
|
|
5414
|
+
// defaults.
|
|
5415
|
+
const defaultOptions = getDefaultWidgetOptions(type);
|
|
5416
|
+
newEditorOptions = _extends({}, defaultOptions, newEditorOptions);
|
|
5417
|
+
let alignment = oldWidgetInfo.alignment;
|
|
5418
|
+
|
|
5419
|
+
// Widgets that support multiple alignments will "lock in" the
|
|
5420
|
+
// alignment to the alignment that would be listed first in the
|
|
5421
|
+
// select box. If the widget only supports one alignment, the
|
|
5422
|
+
// alignment value will likely just end up as "default".
|
|
5423
|
+
if (alignment == null || alignment === "default") {
|
|
5424
|
+
var _getSupportedAlignmen;
|
|
5425
|
+
alignment = (_getSupportedAlignmen = getSupportedAlignments(type)) == null ? void 0 : _getSupportedAlignmen[0];
|
|
5426
|
+
if (!alignment) {
|
|
5427
|
+
throw new PerseusError("No default alignment found when upgrading widget", Errors.Internal, {
|
|
5428
|
+
metadata: {
|
|
5429
|
+
widgetType: type
|
|
5430
|
+
}
|
|
5431
|
+
});
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
let widgetStatic = oldWidgetInfo.static;
|
|
5435
|
+
if (widgetStatic == null) {
|
|
5436
|
+
widgetStatic = DEFAULT_STATIC;
|
|
5437
|
+
}
|
|
5438
|
+
return _extends({}, oldWidgetInfo, {
|
|
5439
|
+
// maintain other info, like type
|
|
5440
|
+
// After upgrading we guarantee that the version is up-to-date
|
|
5441
|
+
version: latestVersion,
|
|
5442
|
+
// Default graded to true (so null/undefined becomes true):
|
|
5443
|
+
graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
|
|
5444
|
+
alignment: alignment,
|
|
5445
|
+
static: widgetStatic,
|
|
5446
|
+
options: newEditorOptions
|
|
5447
|
+
});
|
|
2867
5448
|
};
|
|
5449
|
+
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
5450
|
+
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
5451
|
+
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
5452
|
+
const newValues = {};
|
|
5453
|
+
if (!widgetInfo.type) {
|
|
5454
|
+
// TODO: why does widget have no type?
|
|
5455
|
+
// We don't want to derive type from widget ID
|
|
5456
|
+
// see: LEMS-1845
|
|
5457
|
+
newValues.type = widgetId.split(" ")[0];
|
|
5458
|
+
}
|
|
5459
|
+
if (!widgetInfo.alignment) {
|
|
5460
|
+
newValues.alignment = "default";
|
|
5461
|
+
}
|
|
5462
|
+
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
5463
|
+
}
|
|
5464
|
+
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
2868
5467
|
|
|
2869
|
-
|
|
5468
|
+
/**
|
|
5469
|
+
* For details on the individual options, see the
|
|
5470
|
+
* PerseusOrdererWidgetOptions type
|
|
5471
|
+
*/
|
|
2870
5472
|
|
|
2871
|
-
|
|
5473
|
+
/**
|
|
5474
|
+
* Given a PerseusOrdererWidgetOptions object, return a new object with only
|
|
5475
|
+
* the public options that should be exposed to the client.
|
|
5476
|
+
*/
|
|
5477
|
+
function getOrdererPublicWidgetOptions(options) {
|
|
5478
|
+
return {
|
|
5479
|
+
options: options.options,
|
|
5480
|
+
height: options.height,
|
|
5481
|
+
layout: options.layout
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
2872
5484
|
|
|
2873
5485
|
/**
|
|
2874
|
-
*
|
|
5486
|
+
* For details on the individual options, see the
|
|
5487
|
+
* PerseusCategorizerWidgetOptions type
|
|
2875
5488
|
*/
|
|
2876
5489
|
|
|
2877
5490
|
/**
|
|
2878
|
-
*
|
|
2879
|
-
*
|
|
2880
|
-
* Ex:
|
|
2881
|
-
* tools = {
|
|
2882
|
-
* translation: {
|
|
2883
|
-
* enabled: true
|
|
2884
|
-
* },
|
|
2885
|
-
* rotation: {
|
|
2886
|
-
* enabled: false
|
|
2887
|
-
* }
|
|
2888
|
-
* };
|
|
2889
|
-
* pluckObject(tools, "enabled") returns {
|
|
2890
|
-
* translation: true
|
|
2891
|
-
* rotation: false
|
|
2892
|
-
* }
|
|
5491
|
+
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
5492
|
+
* the public options that should be exposed to the client.
|
|
2893
5493
|
*/
|
|
2894
|
-
|
|
2895
|
-
return
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
5494
|
+
function getCategorizerPublicWidgetOptions(options) {
|
|
5495
|
+
return {
|
|
5496
|
+
items: options.items,
|
|
5497
|
+
categories: options.categories,
|
|
5498
|
+
randomizeItems: options.randomizeItems,
|
|
5499
|
+
static: options.static
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
function getCSProgramPublicWidgetOptions(options) {
|
|
5504
|
+
return options;
|
|
5505
|
+
}
|
|
2899
5506
|
|
|
2900
5507
|
/**
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
2904
|
-
* return value + 1;
|
|
2905
|
-
* });
|
|
2906
|
-
* {a: 2, b: 3}
|
|
5508
|
+
* For details on the individual options, see the
|
|
5509
|
+
* PerseusExpressionWidgetOptions type
|
|
2907
5510
|
*/
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
5511
|
+
|
|
5512
|
+
/**
|
|
5513
|
+
* Given a PerseusExpressionWidgetOptions object, return a new object with only
|
|
5514
|
+
* the public options that should be exposed to the client.
|
|
5515
|
+
*/
|
|
5516
|
+
function getExpressionPublicWidgetOptions(options) {
|
|
5517
|
+
return {
|
|
5518
|
+
buttonSets: options.buttonSets,
|
|
5519
|
+
functions: options.functions,
|
|
5520
|
+
times: options.times,
|
|
5521
|
+
visibleLabel: options.visibleLabel,
|
|
5522
|
+
ariaLabel: options.ariaLabel,
|
|
5523
|
+
buttonsVisible: options.buttonsVisible
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5527
|
+
const _excluded$2 = ["answers"];
|
|
5528
|
+
/**
|
|
5529
|
+
* For details on the individual options, see the
|
|
5530
|
+
* PerseusLabelImageWidgetOptions type
|
|
5531
|
+
*/
|
|
5532
|
+
|
|
5533
|
+
function getLabelImagePublicWidgetOptions(options) {
|
|
5534
|
+
return _extends({}, options, {
|
|
5535
|
+
markers: options.markers.map(getLabelImageMarkerPublicData)
|
|
2913
5536
|
});
|
|
2914
|
-
|
|
2915
|
-
|
|
5537
|
+
}
|
|
5538
|
+
function getLabelImageMarkerPublicData(marker) {
|
|
5539
|
+
const publicData = _objectWithoutPropertiesLoose(marker, _excluded$2);
|
|
5540
|
+
return publicData;
|
|
5541
|
+
}
|
|
5542
|
+
|
|
5543
|
+
/**
|
|
5544
|
+
* For details on the individual options, see the
|
|
5545
|
+
* PerseusSorterWidgetOptions type
|
|
5546
|
+
*/
|
|
5547
|
+
|
|
5548
|
+
/**
|
|
5549
|
+
* Given a PerseusSorterWidgetOptions object, return a new object with only
|
|
5550
|
+
* the public options that should be exposed to the client.
|
|
5551
|
+
*/
|
|
5552
|
+
function getSorterPublicWidgetOptions(options) {
|
|
5553
|
+
return {
|
|
5554
|
+
// Note(Tamara): This does not provide correct answer information any longer.
|
|
5555
|
+
// To maintain compatibility with the original widget options, we are
|
|
5556
|
+
// keeping the key the same. Represents initial state of the cards here.
|
|
5557
|
+
correct: options.correct.slice().sort(),
|
|
5558
|
+
padding: options.padding,
|
|
5559
|
+
layout: options.layout
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
/**
|
|
5564
|
+
* For details on the individual options, see the
|
|
5565
|
+
* PerseusDropdownWidgetOptions type
|
|
5566
|
+
*/
|
|
5567
|
+
|
|
5568
|
+
/**
|
|
5569
|
+
* Given a PerseusDropdownWidgetOptions object, return a new object with only
|
|
5570
|
+
* the public options that should be exposed to the client.
|
|
5571
|
+
*/
|
|
5572
|
+
function getDropdownPublicWidgetOptions(options) {
|
|
5573
|
+
return {
|
|
5574
|
+
choices: options.choices.map(choice => ({
|
|
5575
|
+
content: choice.content
|
|
5576
|
+
})),
|
|
5577
|
+
placeholder: options.placeholder,
|
|
5578
|
+
static: options.static,
|
|
5579
|
+
visibleLabel: options.visibleLabel,
|
|
5580
|
+
ariaLabel: options.ariaLabel
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
|
|
5584
|
+
const _excluded$1 = ["answers"];
|
|
5585
|
+
/**
|
|
5586
|
+
* For details on the individual options, see the
|
|
5587
|
+
* PerseusNumericInputWidgetOptions type
|
|
5588
|
+
*/
|
|
5589
|
+
|
|
5590
|
+
/**
|
|
5591
|
+
* Given a PerseusNumericInputWidgetOptions object, return a new object with only
|
|
5592
|
+
* the public options that should be exposed to the client.
|
|
5593
|
+
*/
|
|
5594
|
+
function getNumericInputPublicWidgetOptions(options) {
|
|
5595
|
+
const publicWidgetOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
|
|
5596
|
+
return publicWidgetOptions;
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5599
|
+
const _excluded = ["correctX", "correctRel"];
|
|
5600
|
+
function getNumberLinePublicWidgetOptions(options) {
|
|
5601
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded);
|
|
5602
|
+
return publicOptions;
|
|
5603
|
+
}
|
|
2916
5604
|
|
|
2917
|
-
export { Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addLibraryVersionToPerseusDebug, approximateDeepEqual, approximateEqual, deepClone, getDecimalSeparator, getMatrixSize, libVersion, lockedFigureColorNames, lockedFigureColors, lockedFigureFillStyles, mapObject, plotterPlotTypes, pluck };
|
|
5605
|
+
export { coreWidgetRegistry as CoreWidgetRegistry, Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addLibraryVersionToPerseusDebug, addWidget, approximateDeepEqual, approximateEqual, categorizerWidgetLogic as categorizerLogic, csProgramWidgetLogic as csProgramLogic, deepClone, definitionWidgetLogic as definitionLogic, dropdownWidgetLogic as dropdownLogic, explanationWidgetLogic as explanationLogic, expressionWidgetLogic as expressionLogic, getCSProgramPublicWidgetOptions, getCategorizerPublicWidgetOptions, getDecimalSeparator, getDropdownPublicWidgetOptions, getExpressionPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatrixSize, getNumberLinePublicWidgetOptions, getNumericInputPublicWidgetOptions, getOrdererPublicWidgetOptions, getSorterPublicWidgetOptions, getUpgradedWidgetOptions, getWidgetIdsFromContent, getWidgetIdsFromContentByType, gradedGroupWidgetLogic as gradedGroupLogic, gradedGroupSetWidgetLogic as gradedGroupSetLogic, grapherWidgetLogic as grapherLogic, groupWidgetLogic as groupLogic, iframeWidgetLogic as iframeLogic, imageWidgetLogic as imageLogic, inputNumberWidgetLogic as inputNumberLogic, interactionWidgetLogic as interactionLogic, interactiveGraphWidgetLogic as interactiveGraphLogic, isFailure, isSuccess, labelImageWidgetLogic as labelImageLogic, libVersion, lockedFigureColorNames, lockedFigureColors, lockedFigureFillStyles, mapObject, matcherWidgetLogic as matcherLogic, matrixWidgetLogic as matrixLogic, measurerWidgetLogic as measurerLogic, numberLineWidgetLogic as numberLineLogic, numericInputWidgetLogic as numericInputLogic, ordererWidgetLogic as ordererLogic, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem, passageWidgetLogic as passageLogic, passageRefWidgetLogic as passageRefLogic, passageRefTargetWidgetLogic as passageRefTargetLogic, phetSimulationWidgetLogic as phetSimulationLogic, plotterWidgetLogic as plotterLogic, plotterPlotTypes, pluck, pythonProgramWidgetLogic as pythonProgramLogic, radioWidgetLogic as radioLogic, sorterWidgetLogic as sorterLogic, tableWidgetLogic as tableLogic, upgradeWidgetInfoToLatestVersion, videoWidgetLogic as videoLogic };
|
|
2918
5606
|
//# sourceMappingURL=index.js.map
|