@khanacademy/perseus-core 5.4.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +192 -1320
- package/dist/index.js.map +1 -1
- package/dist/parse-perseus-json/perseus-parsers/perseus-answer-area.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer-util.d.ts +1 -1
- package/dist/widgets/dropdown/dropdown-util.d.ts +1 -1
- package/dist/widgets/expression/expression-util.d.ts +1 -1
- package/dist/widgets/interactive-graph/interactive-graph-util.d.ts +1 -1
- package/dist/widgets/label-image/label-image-util.d.ts +1 -1
- package/dist/widgets/matcher/matcher-util.d.ts +1 -1
- package/dist/widgets/matrix/matrix-util.d.ts +1 -1
- package/dist/widgets/numeric-input/numeric-input-util.d.ts +1 -1
- package/dist/widgets/orderer/orderer-util.d.ts +1 -1
- package/dist/widgets/plotter/plotter-util.d.ts +1 -1
- package/dist/widgets/radio/radio-util.d.ts +1 -1
- package/dist/widgets/sorter/sorter-util.d.ts +1 -1
- package/dist/widgets/table/table-util.d.ts +1 -1
- package/package.json +15 -6
- package/dist/es/index.js +0 -4121
- package/dist/es/index.js.map +0 -1
- package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -1,51 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var KAS = require('@khanacademy/kas');
|
|
7
|
-
var perseusCore = require('@khanacademy/perseus-core');
|
|
8
|
-
|
|
9
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
-
|
|
11
|
-
function _interopNamespace(e) {
|
|
12
|
-
if (e && e.__esModule) return e;
|
|
13
|
-
var n = Object.create(null);
|
|
14
|
-
if (e) {
|
|
15
|
-
Object.keys(e).forEach(function (k) {
|
|
16
|
-
if (k !== 'default') {
|
|
17
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () { return e[k]; }
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
n["default"] = e;
|
|
26
|
-
return Object.freeze(n);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
|
|
30
|
-
var KAS__namespace = /*#__PURE__*/_interopNamespace(KAS);
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
import _extends from '@babel/runtime/helpers/extends';
|
|
3
|
+
import * as KAS from '@khanacademy/kas';
|
|
4
|
+
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
|
|
5
|
+
import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
|
|
31
6
|
|
|
32
7
|
function getMatrixSize(matrix) {
|
|
33
8
|
const matrixSize = [1, 1];
|
|
34
|
-
|
|
35
|
-
// We need to find the widest row and tallest column to get the correct
|
|
36
|
-
// matrix size.
|
|
37
|
-
___default["default"](matrix).each((matrixRow, row) => {
|
|
9
|
+
_(matrix).each((matrixRow, row) => {
|
|
38
10
|
let rowWidth = 0;
|
|
39
|
-
|
|
11
|
+
_(matrixRow).each((matrixCol, col) => {
|
|
40
12
|
if (matrixCol != null && matrixCol.toString().length) {
|
|
41
13
|
rowWidth = col + 1;
|
|
42
14
|
}
|
|
43
15
|
});
|
|
44
|
-
|
|
45
|
-
// Matrix width:
|
|
46
16
|
matrixSize[1] = Math.max(matrixSize[1], rowWidth);
|
|
47
|
-
|
|
48
|
-
// Matrix height:
|
|
49
17
|
if (rowWidth > 0) {
|
|
50
18
|
matrixSize[0] = Math.max(matrixSize[0], row + 1);
|
|
51
19
|
}
|
|
@@ -53,47 +21,24 @@ function getMatrixSize(matrix) {
|
|
|
53
21
|
return matrixSize;
|
|
54
22
|
}
|
|
55
23
|
|
|
56
|
-
/**
|
|
57
|
-
* Get the character used for separating decimals.
|
|
58
|
-
*/
|
|
59
24
|
const getDecimalSeparator = locale => {
|
|
25
|
+
var _match$;
|
|
60
26
|
switch (locale) {
|
|
61
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
62
|
-
// locale properly.
|
|
63
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
64
|
-
//
|
|
65
|
-
// Supported locales in Chrome:
|
|
66
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
67
27
|
case "ka":
|
|
68
28
|
return ",";
|
|
69
29
|
default:
|
|
70
30
|
const numberWithDecimalSeparator = 1.1;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
74
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
75
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
76
|
-
// 0x967 is DEVANAGARI DIGIT ONE
|
|
77
|
-
// 0x9e7 is BENGALI/BANGLA DIGIT ONE
|
|
78
|
-
.match(/[^\d\u0661\u06F1\u0967\u09e7]/);
|
|
79
|
-
return match?.[0] ?? ".";
|
|
31
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator).match(/[^\d\u0661\u06F1\u0967\u09e7]/);
|
|
32
|
+
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
80
33
|
}
|
|
81
34
|
};
|
|
82
35
|
|
|
83
|
-
/**
|
|
84
|
-
* APPROXIMATE equality on numbers and primitives.
|
|
85
|
-
*/
|
|
86
36
|
function approximateEqual(x, y) {
|
|
87
37
|
if (typeof x === "number" && typeof y === "number") {
|
|
88
38
|
return Math.abs(x - y) < 1e-9;
|
|
89
39
|
}
|
|
90
40
|
return x === y;
|
|
91
41
|
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Deep APPROXIMATE equality on primitives, numbers, arrays, and objects.
|
|
95
|
-
* Recursive.
|
|
96
|
-
*/
|
|
97
42
|
function approximateDeepEqual(x, y) {
|
|
98
43
|
if (Array.isArray(x) && Array.isArray(y)) {
|
|
99
44
|
if (x.length !== y.length) {
|
|
@@ -116,11 +61,9 @@ function approximateDeepEqual(x, y) {
|
|
|
116
61
|
return false;
|
|
117
62
|
}
|
|
118
63
|
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
119
|
-
return x === y ||
|
|
120
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
64
|
+
return x === y || _.all(x, function (v, k) {
|
|
121
65
|
return approximateDeepEqual(y[k], v);
|
|
122
|
-
}) &&
|
|
123
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
66
|
+
}) && _.all(y, function (v, k) {
|
|
124
67
|
return approximateDeepEqual(x[k], v);
|
|
125
68
|
});
|
|
126
69
|
}
|
|
@@ -130,38 +73,12 @@ function approximateDeepEqual(x, y) {
|
|
|
130
73
|
return approximateEqual(x, y);
|
|
131
74
|
}
|
|
132
75
|
|
|
133
|
-
/**
|
|
134
|
-
* Add a widget placeholder using the widget ID.
|
|
135
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
136
|
-
*
|
|
137
|
-
* @param {string} id
|
|
138
|
-
* @returns {string}
|
|
139
|
-
*/
|
|
140
76
|
function addWidget(id) {
|
|
141
77
|
return `[[☃ ${id}]]`;
|
|
142
78
|
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Regex for widget placeholders in a string.
|
|
146
|
-
*
|
|
147
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
148
|
-
* Second capture group is the widget type (ex. "radio)
|
|
149
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
150
|
-
*/
|
|
151
79
|
function getWidgetRegex() {
|
|
152
80
|
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
153
81
|
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
157
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
158
|
-
*
|
|
159
|
-
* Content should contain Perseus widget placeholders,
|
|
160
|
-
* which look like: '[[☃ radio 1]]'.
|
|
161
|
-
*
|
|
162
|
-
* @param {string} content
|
|
163
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
164
|
-
*/
|
|
165
82
|
function getWidgetIdsFromContent(content) {
|
|
166
83
|
const widgets = [];
|
|
167
84
|
const localWidgetRegex = getWidgetRegex();
|
|
@@ -172,31 +89,18 @@ function getWidgetIdsFromContent(content) {
|
|
|
172
89
|
}
|
|
173
90
|
return widgets;
|
|
174
91
|
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Get a list of widget IDs from content,
|
|
178
|
-
* but only for specific widget types
|
|
179
|
-
*
|
|
180
|
-
* @param {string} type the type of widget (ie "radio")
|
|
181
|
-
* @param {string} content the string to parse
|
|
182
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
183
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
184
|
-
*/
|
|
185
92
|
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
186
93
|
const rv = [];
|
|
187
94
|
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
188
95
|
widgetIdsInContent.forEach(widgetId => {
|
|
189
96
|
const widget = widgetMap[widgetId];
|
|
190
|
-
if (widget
|
|
97
|
+
if ((widget == null ? void 0 : widget.type) === type) {
|
|
191
98
|
rv.push(widgetId);
|
|
192
99
|
}
|
|
193
100
|
});
|
|
194
101
|
return rv;
|
|
195
102
|
}
|
|
196
103
|
|
|
197
|
-
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
198
|
-
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
199
|
-
|
|
200
104
|
function deepClone(obj) {
|
|
201
105
|
if (Array.isArray(obj)) {
|
|
202
106
|
return obj.map(deepClone);
|
|
@@ -209,33 +113,22 @@ const MOVABLES = {
|
|
|
209
113
|
PARABOLA: "PARABOLA",
|
|
210
114
|
SINUSOID: "SINUSOID"
|
|
211
115
|
};
|
|
212
|
-
|
|
213
|
-
// TODO(charlie): These really need to go into a utility file as they're being
|
|
214
|
-
// used by both interactive-graph and now grapher.
|
|
215
116
|
function canonicalSineCoefficients(coeffs) {
|
|
216
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
217
|
-
// this function ensures that a, b > 0, and c is its
|
|
218
|
-
// smallest possible positive value.
|
|
219
117
|
let amplitude = coeffs[0];
|
|
220
118
|
let angularFrequency = coeffs[1];
|
|
221
119
|
let phase = coeffs[2];
|
|
222
120
|
const verticalOffset = coeffs[3];
|
|
223
|
-
|
|
224
|
-
// Guarantee a > 0
|
|
225
121
|
if (amplitude < 0) {
|
|
226
122
|
amplitude *= -1;
|
|
227
123
|
angularFrequency *= -1;
|
|
228
124
|
phase *= -1;
|
|
229
125
|
}
|
|
230
126
|
const period = 2 * Math.PI;
|
|
231
|
-
// Guarantee b > 0
|
|
232
127
|
if (angularFrequency < 0) {
|
|
233
128
|
angularFrequency *= -1;
|
|
234
129
|
phase *= -1;
|
|
235
130
|
phase += period / 2;
|
|
236
131
|
}
|
|
237
|
-
|
|
238
|
-
// Guarantee c is smallest possible positive value
|
|
239
132
|
while (phase > 0) {
|
|
240
133
|
phase -= period;
|
|
241
134
|
}
|
|
@@ -245,29 +138,21 @@ function canonicalSineCoefficients(coeffs) {
|
|
|
245
138
|
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
246
139
|
}
|
|
247
140
|
function canonicalTangentCoefficients(coeffs) {
|
|
248
|
-
// For a curve of the form f(x) = a * Tan(b * x - c) + d,
|
|
249
|
-
// this function ensures that a, b > 0, and c is its
|
|
250
|
-
// smallest possible positive value.
|
|
251
141
|
let amplitude = coeffs[0];
|
|
252
142
|
let angularFrequency = coeffs[1];
|
|
253
143
|
let phase = coeffs[2];
|
|
254
144
|
const verticalOffset = coeffs[3];
|
|
255
|
-
|
|
256
|
-
// Guarantee a > 0
|
|
257
145
|
if (amplitude < 0) {
|
|
258
146
|
amplitude *= -1;
|
|
259
147
|
angularFrequency *= -1;
|
|
260
148
|
phase *= -1;
|
|
261
149
|
}
|
|
262
150
|
const period = Math.PI;
|
|
263
|
-
// Guarantee b > 0
|
|
264
151
|
if (angularFrequency < 0) {
|
|
265
152
|
angularFrequency *= -1;
|
|
266
153
|
phase *= -1;
|
|
267
154
|
phase += period / 2;
|
|
268
155
|
}
|
|
269
|
-
|
|
270
|
-
// Guarantee c is smallest possible positive value
|
|
271
156
|
while (phase > 0) {
|
|
272
157
|
phase -= period;
|
|
273
158
|
}
|
|
@@ -283,12 +168,11 @@ const PlotDefaults = {
|
|
|
283
168
|
movable: MOVABLES.PLOT,
|
|
284
169
|
getPropsForCoeffs: function (coeffs) {
|
|
285
170
|
return {
|
|
286
|
-
|
|
287
|
-
fn: ___default["default"].partial(this.getFunctionForCoeffs, coeffs)
|
|
171
|
+
fn: _.partial(this.getFunctionForCoeffs, coeffs)
|
|
288
172
|
};
|
|
289
173
|
}
|
|
290
174
|
};
|
|
291
|
-
const Linear =
|
|
175
|
+
const Linear = _.extend({}, PlotDefaults, {
|
|
292
176
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
|
|
293
177
|
defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
|
|
294
178
|
getCoefficients: function (coords) {
|
|
@@ -315,19 +199,15 @@ const Linear = ___default["default"].extend({}, PlotDefaults, {
|
|
|
315
199
|
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
316
200
|
}
|
|
317
201
|
});
|
|
318
|
-
const Quadratic =
|
|
202
|
+
const Quadratic = _.extend({}, PlotDefaults, {
|
|
319
203
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
|
|
320
204
|
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
321
205
|
movable: MOVABLES.PARABOLA,
|
|
322
206
|
getCoefficients: function (coords) {
|
|
323
207
|
const p1 = coords[0];
|
|
324
208
|
const p2 = coords[1];
|
|
325
|
-
|
|
326
|
-
// Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
|
|
327
209
|
const h = p1[0];
|
|
328
210
|
const k = p1[1];
|
|
329
|
-
|
|
330
|
-
// Use these to calculate familiar a, b, c
|
|
331
211
|
const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
|
|
332
212
|
const b = -2 * h * a;
|
|
333
213
|
const c = a * h * h + k;
|
|
@@ -354,7 +234,7 @@ const Quadratic = ___default["default"].extend({}, PlotDefaults, {
|
|
|
354
234
|
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
355
235
|
}
|
|
356
236
|
});
|
|
357
|
-
const Sinusoid =
|
|
237
|
+
const Sinusoid = _.extend({}, PlotDefaults, {
|
|
358
238
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
|
|
359
239
|
defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
|
|
360
240
|
movable: MOVABLES.SINUSOID,
|
|
@@ -394,7 +274,7 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
|
|
|
394
274
|
return approximateDeepEqual(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
395
275
|
}
|
|
396
276
|
});
|
|
397
|
-
const Tangent =
|
|
277
|
+
const Tangent = _.extend({}, PlotDefaults, {
|
|
398
278
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
|
|
399
279
|
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
400
280
|
getCoefficients: function (coords) {
|
|
@@ -425,46 +305,27 @@ const Tangent = ___default["default"].extend({}, PlotDefaults, {
|
|
|
425
305
|
return approximateDeepEqual(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
|
|
426
306
|
}
|
|
427
307
|
});
|
|
428
|
-
const Exponential =
|
|
308
|
+
const Exponential = _.extend({}, PlotDefaults, {
|
|
429
309
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
|
|
430
310
|
defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
|
|
431
311
|
defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
|
|
432
|
-
/**
|
|
433
|
-
* Add extra constraints for movement of the points or asymptote (below):
|
|
434
|
-
* newCoord: [x, y]
|
|
435
|
-
* The end position of the point or asymptote endpoint
|
|
436
|
-
* oldCoord: [x, y]
|
|
437
|
-
* The old position of the point or asymptote endpoint
|
|
438
|
-
* coords:
|
|
439
|
-
* An array of coordinates representing the proposed end configuration
|
|
440
|
-
* of the plot coordinates.
|
|
441
|
-
* asymptote:
|
|
442
|
-
* An array of coordinates representing the proposed end configuration
|
|
443
|
-
* of the asymptote.
|
|
444
|
-
*
|
|
445
|
-
* Return: either a coordinate (to be used as the resulting coordinate of
|
|
446
|
-
* the move) or a boolean, where `true` uses newCoord as the resulting
|
|
447
|
-
* coordinate, and `false` uses oldCoord as the resulting coordinate.
|
|
448
|
-
*/
|
|
449
312
|
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
450
313
|
const y = asymptote[0][1];
|
|
451
|
-
return
|
|
314
|
+
return _.all(coords, coord => coord[1] !== y);
|
|
452
315
|
},
|
|
453
316
|
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
454
317
|
const y = newCoord[1];
|
|
455
|
-
const isValid =
|
|
318
|
+
const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
|
|
456
319
|
if (isValid) {
|
|
457
320
|
return [oldCoord[0], y];
|
|
458
321
|
}
|
|
459
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
460
|
-
// the mouse really quickly into an invalid region
|
|
461
322
|
const oldY = oldCoord[1];
|
|
462
|
-
const wasBelow =
|
|
323
|
+
const wasBelow = _.all(coords, coord => coord[1] > oldY);
|
|
463
324
|
if (wasBelow) {
|
|
464
|
-
const bottomMost =
|
|
325
|
+
const bottomMost = _.min(_.map(coords, coord => coord[1]));
|
|
465
326
|
return [oldCoord[0], bottomMost - graph.snapStep[1]];
|
|
466
327
|
}
|
|
467
|
-
const topMost =
|
|
328
|
+
const topMost = _.max(_.map(coords, coord => coord[1]));
|
|
468
329
|
return [oldCoord[0], topMost + graph.snapStep[1]];
|
|
469
330
|
},
|
|
470
331
|
allowReflectOverAsymptote: true,
|
|
@@ -493,39 +354,33 @@ const Exponential = ___default["default"].extend({}, PlotDefaults, {
|
|
|
493
354
|
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
494
355
|
}
|
|
495
356
|
});
|
|
496
|
-
const Logarithm =
|
|
357
|
+
const Logarithm = _.extend({}, PlotDefaults, {
|
|
497
358
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
|
|
498
359
|
defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
|
|
499
360
|
defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
|
|
500
361
|
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
501
362
|
const x = asymptote[0][0];
|
|
502
|
-
return
|
|
363
|
+
return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
|
|
503
364
|
},
|
|
504
365
|
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
505
366
|
const x = newCoord[0];
|
|
506
|
-
const isValid =
|
|
367
|
+
const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
|
|
507
368
|
if (isValid) {
|
|
508
369
|
return [x, oldCoord[1]];
|
|
509
370
|
}
|
|
510
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
511
|
-
// the mouse really quickly into an invalid region
|
|
512
371
|
const oldX = oldCoord[0];
|
|
513
|
-
const wasLeft =
|
|
372
|
+
const wasLeft = _.all(coords, coord => coord[0] > oldX);
|
|
514
373
|
if (wasLeft) {
|
|
515
|
-
const leftMost =
|
|
374
|
+
const leftMost = _.min(_.map(coords, coord => coord[0]));
|
|
516
375
|
return [leftMost - graph.snapStep[0], oldCoord[1]];
|
|
517
376
|
}
|
|
518
|
-
const rightMost =
|
|
377
|
+
const rightMost = _.max(_.map(coords, coord => coord[0]));
|
|
519
378
|
return [rightMost + graph.snapStep[0], oldCoord[1]];
|
|
520
379
|
},
|
|
521
380
|
allowReflectOverAsymptote: true,
|
|
522
381
|
getCoefficients: function (coords, asymptote) {
|
|
523
|
-
// It's easiest to calculate the logarithm's coefficients by thinking
|
|
524
|
-
// about it as the inverse of the exponential, so we flip x and y and
|
|
525
|
-
// perform some algebra on the coefficients. This also unifies the
|
|
526
|
-
// logic between the two 'models'.
|
|
527
382
|
const flip = coord => [coord[1], coord[0]];
|
|
528
|
-
const inverseCoeffs = Exponential.getCoefficients(
|
|
383
|
+
const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
|
|
529
384
|
if (inverseCoeffs) {
|
|
530
385
|
const c = -inverseCoeffs[2] / inverseCoeffs[0];
|
|
531
386
|
const b = 1 / inverseCoeffs[0];
|
|
@@ -550,7 +405,7 @@ const Logarithm = ___default["default"].extend({}, PlotDefaults, {
|
|
|
550
405
|
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
551
406
|
}
|
|
552
407
|
});
|
|
553
|
-
const AbsoluteValue =
|
|
408
|
+
const AbsoluteValue = _.extend({}, PlotDefaults, {
|
|
554
409
|
url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
|
|
555
410
|
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
556
411
|
getCoefficients: function (coords) {
|
|
@@ -583,8 +438,6 @@ const AbsoluteValue = ___default["default"].extend({}, PlotDefaults, {
|
|
|
583
438
|
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
584
439
|
}
|
|
585
440
|
});
|
|
586
|
-
|
|
587
|
-
/* Utility functions for dealing with graphing interfaces. */
|
|
588
441
|
const functionTypeMapping = {
|
|
589
442
|
linear: Linear,
|
|
590
443
|
quadratic: Quadratic,
|
|
@@ -594,10 +447,8 @@ const functionTypeMapping = {
|
|
|
594
447
|
logarithm: Logarithm,
|
|
595
448
|
absolute_value: AbsoluteValue
|
|
596
449
|
};
|
|
597
|
-
const allTypes =
|
|
450
|
+
const allTypes = _.keys(functionTypeMapping);
|
|
598
451
|
function functionForType(type) {
|
|
599
|
-
// @ts-expect-error: TypeScript doesn't know how to use deal with generics
|
|
600
|
-
// and conditional types in this way.
|
|
601
452
|
return functionTypeMapping[type];
|
|
602
453
|
}
|
|
603
454
|
|
|
@@ -666,8 +517,7 @@ function isRealJSONParse(jsonParse) {
|
|
|
666
517
|
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
667
518
|
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
668
519
|
}
|
|
669
|
-
function buildRandomString() {
|
|
670
|
-
let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
520
|
+
function buildRandomString(capitalize = false) {
|
|
671
521
|
let randomString = "";
|
|
672
522
|
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
673
523
|
for (let i = 0; i < randomLength; i++) {
|
|
@@ -691,8 +541,6 @@ function buildTestData(testObject) {
|
|
|
691
541
|
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
692
542
|
}
|
|
693
543
|
|
|
694
|
-
process.env.NODE_ENV === 'production';
|
|
695
|
-
|
|
696
544
|
function success(value) {
|
|
697
545
|
return {
|
|
698
546
|
type: "success",
|
|
@@ -711,11 +559,7 @@ function isFailure(result) {
|
|
|
711
559
|
function isSuccess(result) {
|
|
712
560
|
return result.type === "success";
|
|
713
561
|
}
|
|
714
|
-
|
|
715
|
-
// Result's `all` function is similar to Promise.all: given an array of
|
|
716
|
-
// results, it returns success if all succeeded, and failure if any failed.
|
|
717
|
-
function all(results) {
|
|
718
|
-
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
562
|
+
function all(results, combineFailureDetails = a => a) {
|
|
719
563
|
const values = [];
|
|
720
564
|
const failureDetails = [];
|
|
721
565
|
for (const result of results) {
|
|
@@ -772,7 +616,6 @@ function message(failure) {
|
|
|
772
616
|
}
|
|
773
617
|
function conjoin(items) {
|
|
774
618
|
switch (items.length) {
|
|
775
|
-
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
776
619
|
case 1:
|
|
777
620
|
return items[0];
|
|
778
621
|
case 2:
|
|
@@ -825,10 +668,7 @@ function constant(acceptedValue) {
|
|
|
825
668
|
};
|
|
826
669
|
}
|
|
827
670
|
|
|
828
|
-
function enumeration() {
|
|
829
|
-
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
830
|
-
acceptedValues[_key] = arguments[_key];
|
|
831
|
-
}
|
|
671
|
+
function enumeration(...acceptedValues) {
|
|
832
672
|
return (rawValue, ctx) => {
|
|
833
673
|
if (typeof rawValue === "string") {
|
|
834
674
|
const index = acceptedValues.indexOf(rawValue);
|
|
@@ -866,9 +706,7 @@ function object(schema) {
|
|
|
866
706
|
if (!isObject(rawValue)) {
|
|
867
707
|
return ctx.failure("object", rawValue);
|
|
868
708
|
}
|
|
869
|
-
const ret = {
|
|
870
|
-
...rawValue
|
|
871
|
-
};
|
|
709
|
+
const ret = _extends({}, rawValue);
|
|
872
710
|
const mismatches = [];
|
|
873
711
|
for (const [prop, propParser] of Object.entries(schema)) {
|
|
874
712
|
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
@@ -1106,54 +944,26 @@ const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
|
1106
944
|
showPrompt: string,
|
|
1107
945
|
hidePrompt: string,
|
|
1108
946
|
explanation: string,
|
|
1109
|
-
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
1110
|
-
// referenced before it is defined. There is an import cycle between
|
|
1111
|
-
// this file and widgets-map.ts that could cause it to be undefined.
|
|
1112
947
|
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
1113
948
|
static: defaulted(boolean, () => false)
|
|
1114
949
|
}));
|
|
1115
950
|
|
|
1116
|
-
const KeypadKeys = ["PLUS", "MINUS", "NEGATIVE", "TIMES", "DIVIDE", "DECIMAL", "PERIOD", "PERCENT", "CDOT", "EQUAL", "NEQ", "GT", "LT", "GEQ", "LEQ",
|
|
1117
|
-
|
|
1118
|
-
"FRAC_INCLUSIVE",
|
|
1119
|
-
// mobile native only
|
|
1120
|
-
"FRAC_EXCLUSIVE",
|
|
1121
|
-
// mobile native only
|
|
1122
|
-
"FRAC", "EXP", "EXP_2", "EXP_3", "SQRT", "CUBE_ROOT", "RADICAL", "LEFT_PAREN", "RIGHT_PAREN", "LN", "LOG", "LOG_N", "SIN", "COS",
|
|
1123
|
-
// TODO(charlie): Add in additional Greek letters.,
|
|
1124
|
-
"TAN", "PI", "THETA", "UP", "RIGHT", "DOWN", "LEFT", "BACKSPACE", "DISMISS", "JUMP_OUT_PARENTHESES", "JUMP_OUT_EXPONENT", "JUMP_OUT_BASE", "JUMP_INTO_NUMERATOR", "JUMP_OUT_NUMERATOR", "JUMP_OUT_DENOMINATOR",
|
|
1125
|
-
// Multi-functional keys.
|
|
1126
|
-
"NUM_0", "NUM_1", "NUM_2", "NUM_3", "NUM_4", "NUM_5", "NUM_6", "NUM_7", "NUM_8", "NUM_9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
|
|
1127
|
-
|
|
1128
|
-
// Used by KeypadContext to pass around a renderer reference
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* Scrape the answer forms for any variables or contants (like Pi)
|
|
1132
|
-
* that need to be included as keys on the keypad.
|
|
1133
|
-
*/
|
|
951
|
+
const KeypadKeys = ["PLUS", "MINUS", "NEGATIVE", "TIMES", "DIVIDE", "DECIMAL", "PERIOD", "PERCENT", "CDOT", "EQUAL", "NEQ", "GT", "LT", "GEQ", "LEQ", "FRAC_INCLUSIVE", "FRAC_EXCLUSIVE", "FRAC", "EXP", "EXP_2", "EXP_3", "SQRT", "CUBE_ROOT", "RADICAL", "LEFT_PAREN", "RIGHT_PAREN", "LN", "LOG", "LOG_N", "SIN", "COS", "TAN", "PI", "THETA", "UP", "RIGHT", "DOWN", "LEFT", "BACKSPACE", "DISMISS", "JUMP_OUT_PARENTHESES", "JUMP_OUT_EXPONENT", "JUMP_OUT_BASE", "JUMP_INTO_NUMERATOR", "JUMP_OUT_NUMERATOR", "JUMP_OUT_DENOMINATOR", "NUM_0", "NUM_1", "NUM_2", "NUM_3", "NUM_4", "NUM_5", "NUM_6", "NUM_7", "NUM_8", "NUM_9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
|
|
952
|
+
|
|
1134
953
|
function deriveExtraKeys(widgetOptions) {
|
|
1135
954
|
if (widgetOptions.extraKeys) {
|
|
1136
955
|
return widgetOptions.extraKeys;
|
|
1137
956
|
}
|
|
1138
|
-
|
|
1139
|
-
// If there are no extra symbols available, we include Pi anyway, so
|
|
1140
|
-
// that the "extra symbols" button doesn't appear empty.
|
|
1141
957
|
const defaultKeys = ["PI"];
|
|
1142
958
|
if (widgetOptions.answerForms == null) {
|
|
1143
959
|
return defaultKeys;
|
|
1144
960
|
}
|
|
1145
|
-
|
|
1146
|
-
// Extract any and all variables and constants from the answer forms.
|
|
1147
961
|
const uniqueExtraVariables = {};
|
|
1148
962
|
const uniqueExtraConstants = {};
|
|
1149
963
|
for (const answerForm of widgetOptions.answerForms) {
|
|
1150
|
-
const maybeExpr =
|
|
964
|
+
const maybeExpr = KAS.parse(answerForm.value, widgetOptions);
|
|
1151
965
|
if (maybeExpr.parsed) {
|
|
1152
966
|
const expr = maybeExpr.expr;
|
|
1153
|
-
|
|
1154
|
-
// The keypad expects Greek letters to be capitalized (e.g., it
|
|
1155
|
-
// requires `PI` instead of `pi`). Right now, it only supports Pi
|
|
1156
|
-
// and Theta, so we special-case.
|
|
1157
967
|
const isGreek = symbol => symbol === "pi" || symbol === "theta";
|
|
1158
968
|
const toKey = symbol => isGreek(symbol) ? symbol.toUpperCase() : symbol;
|
|
1159
969
|
const isKey = key => KeypadKeys.includes(key);
|
|
@@ -1171,9 +981,6 @@ function deriveExtraKeys(widgetOptions) {
|
|
|
1171
981
|
}
|
|
1172
982
|
}
|
|
1173
983
|
}
|
|
1174
|
-
|
|
1175
|
-
// TODO(charlie): Alert the keypad as to which of these symbols should be
|
|
1176
|
-
// treated as functions.
|
|
1177
984
|
const extraVariables = Object.keys(uniqueExtraVariables).sort();
|
|
1178
985
|
const extraConstants = Object.keys(uniqueExtraConstants).sort();
|
|
1179
986
|
const extraKeys = [...extraVariables, ...extraConstants];
|
|
@@ -1183,44 +990,19 @@ function deriveExtraKeys(widgetOptions) {
|
|
|
1183
990
|
return extraKeys;
|
|
1184
991
|
}
|
|
1185
992
|
|
|
1186
|
-
// Given a function, creates a PartialParser that converts one type to another
|
|
1187
|
-
// using that function. The returned parser never fails.
|
|
1188
993
|
function convert(f) {
|
|
1189
994
|
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
1190
995
|
}
|
|
1191
996
|
|
|
1192
997
|
const parseLegacyButtonSet = enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations", "scientific");
|
|
1193
|
-
const parseLegacyButtonSets = defaulted(array(parseLegacyButtonSet),
|
|
1194
|
-
|
|
1195
|
-
// expression.tsx. See the parse-perseus-json/README.md for
|
|
1196
|
-
// an explanation of why we want to duplicate the default here.
|
|
1197
|
-
() => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Creates a parser for a widget options type with multiple major versions. Old
|
|
1201
|
-
* versions are migrated to the latest version. The parse fails if the input
|
|
1202
|
-
* data does not match any of the versions.
|
|
1203
|
-
*
|
|
1204
|
-
* @example
|
|
1205
|
-
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
1206
|
-
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
1207
|
-
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
1208
|
-
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
1209
|
-
* .parser;
|
|
1210
|
-
*
|
|
1211
|
-
* @param latestMajorVersion the latest major version of the widget options.
|
|
1212
|
-
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
1213
|
-
* options.
|
|
1214
|
-
* @returns a builder object, to which migrations from earlier versions can be
|
|
1215
|
-
* added. Migrations must be added in "reverse chronological" order as in the
|
|
1216
|
-
* example above.
|
|
1217
|
-
*/
|
|
998
|
+
const parseLegacyButtonSets = defaulted(array(parseLegacyButtonSet), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
999
|
+
|
|
1218
1000
|
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
1219
1001
|
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
1220
1002
|
}
|
|
1221
1003
|
class VersionedWidgetOptionsParserBuilder {
|
|
1222
|
-
parser;
|
|
1223
1004
|
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
1005
|
+
this.parser = void 0;
|
|
1224
1006
|
this.migrateToLatest = migrateToLatest;
|
|
1225
1007
|
this.parseOtherVersions = parseOtherVersions;
|
|
1226
1008
|
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
@@ -1238,10 +1020,6 @@ class VersionedWidgetOptionsParserBuilder {
|
|
|
1238
1020
|
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
1239
1021
|
};
|
|
1240
1022
|
}
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
* Add a migration from an old version of the widget options.
|
|
1244
|
-
*/
|
|
1245
1023
|
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
1246
1024
|
const parseOtherVersions = this.parser;
|
|
1247
1025
|
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
@@ -1260,9 +1038,6 @@ const parseVersionedObject = object({
|
|
|
1260
1038
|
|
|
1261
1039
|
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
1262
1040
|
const parsePossiblyInvalidAnswerForm = object({
|
|
1263
|
-
// `value` is the possibly invalid part of this. It should always be a
|
|
1264
|
-
// string, but some answer forms don't have it. The Expression widget
|
|
1265
|
-
// ignores invalid values, so we can safely filter them out during parsing.
|
|
1266
1041
|
value: optional(string),
|
|
1267
1042
|
form: defaulted(boolean, () => false),
|
|
1268
1043
|
simplify: defaulted(boolean, () => false),
|
|
@@ -1276,11 +1051,9 @@ function removeInvalidAnswerForms(possiblyInvalid) {
|
|
|
1276
1051
|
value
|
|
1277
1052
|
} = answerForm;
|
|
1278
1053
|
if (value != null) {
|
|
1279
|
-
|
|
1280
|
-
valid.push({
|
|
1281
|
-
...answerForm,
|
|
1054
|
+
valid.push(_extends({}, answerForm, {
|
|
1282
1055
|
value
|
|
1283
|
-
});
|
|
1056
|
+
}));
|
|
1284
1057
|
}
|
|
1285
1058
|
}
|
|
1286
1059
|
return valid;
|
|
@@ -1316,8 +1089,7 @@ function migrateV1ToV2$1(widget) {
|
|
|
1316
1089
|
const {
|
|
1317
1090
|
options
|
|
1318
1091
|
} = widget;
|
|
1319
|
-
return {
|
|
1320
|
-
...widget,
|
|
1092
|
+
return _extends({}, widget, {
|
|
1321
1093
|
version: {
|
|
1322
1094
|
major: 2,
|
|
1323
1095
|
minor: 0
|
|
@@ -1332,7 +1104,7 @@ function migrateV1ToV2$1(widget) {
|
|
|
1332
1104
|
answerForms: options.answerForms,
|
|
1333
1105
|
extraKeys: deriveExtraKeys(options)
|
|
1334
1106
|
}
|
|
1335
|
-
};
|
|
1107
|
+
});
|
|
1336
1108
|
}
|
|
1337
1109
|
const version0$1 = optional(object({
|
|
1338
1110
|
major: constant(0),
|
|
@@ -1353,8 +1125,7 @@ function migrateV0ToV1$1(widget) {
|
|
|
1353
1125
|
const {
|
|
1354
1126
|
options
|
|
1355
1127
|
} = widget;
|
|
1356
|
-
return {
|
|
1357
|
-
...widget,
|
|
1128
|
+
return _extends({}, widget, {
|
|
1358
1129
|
version: {
|
|
1359
1130
|
major: 1,
|
|
1360
1131
|
minor: 0
|
|
@@ -1373,7 +1144,7 @@ function migrateV0ToV1$1(widget) {
|
|
|
1373
1144
|
value: options.value
|
|
1374
1145
|
}]
|
|
1375
1146
|
}
|
|
1376
|
-
};
|
|
1147
|
+
});
|
|
1377
1148
|
}
|
|
1378
1149
|
const parseExpressionWidget = versionedWidgetOptions(2, parseExpressionWidgetV2).withMigrationFrom(1, parseExpressionWidgetV1, migrateV1ToV2$1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1$1).parser;
|
|
1379
1150
|
|
|
@@ -1381,14 +1152,8 @@ const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parse
|
|
|
1381
1152
|
const parseGradedGroupWidgetOptions = object({
|
|
1382
1153
|
title: defaulted(string, () => ""),
|
|
1383
1154
|
hasHint: optional(nullable(boolean)),
|
|
1384
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
1385
|
-
// The anonymous function below ensures that we don't try to access
|
|
1386
|
-
// parsePerseusRenderer before it's defined.
|
|
1387
1155
|
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
1388
1156
|
content: string,
|
|
1389
|
-
// This module has an import cycle with parseWidgetsMap.
|
|
1390
|
-
// The anonymous function below ensures that we don't try to access
|
|
1391
|
-
// parseWidgetsMap before it's defined.
|
|
1392
1157
|
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
1393
1158
|
widgetEnabled: optional(nullable(boolean)),
|
|
1394
1159
|
immutableWidgets: optional(nullable(boolean)),
|
|
@@ -1403,13 +1168,6 @@ const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), obje
|
|
|
1403
1168
|
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
1404
1169
|
}));
|
|
1405
1170
|
|
|
1406
|
-
/**
|
|
1407
|
-
* discriminatedUnion() should be preferred over union() when parsing a
|
|
1408
|
-
* discriminated union type, because discriminatedUnion() produces more
|
|
1409
|
-
* understandable failure messages. It takes the discriminant as the source of
|
|
1410
|
-
* truth for which variant is to be parsed, and expects the other data to match
|
|
1411
|
-
* that variant.
|
|
1412
|
-
*/
|
|
1413
1171
|
function discriminatedUnionOn(discriminantKey) {
|
|
1414
1172
|
const noMoreBranches = (raw, ctx) => {
|
|
1415
1173
|
if (!isObject(raw)) {
|
|
@@ -1495,11 +1253,7 @@ const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
|
1495
1253
|
})
|
|
1496
1254
|
}));
|
|
1497
1255
|
|
|
1498
|
-
const parseGroupWidget = parseWidget(constant("group"),
|
|
1499
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
1500
|
-
// The anonymous function below ensures that we don't try to access
|
|
1501
|
-
// parsePerseusRenderer before it's defined.
|
|
1502
|
-
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
1256
|
+
const parseGroupWidget = parseWidget(constant("group"), (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
1503
1257
|
|
|
1504
1258
|
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
1505
1259
|
url: string,
|
|
@@ -1528,11 +1282,7 @@ const stringToNumber = (rawValue, ctx) => {
|
|
|
1528
1282
|
function emptyToZero(x) {
|
|
1529
1283
|
return x === "" ? 0 : x;
|
|
1530
1284
|
}
|
|
1531
|
-
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
1532
|
-
// In this specific case, empty string is equivalent to zero. An empty
|
|
1533
|
-
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
1534
|
-
// CSS will treat NaN as invalid and default to 0 instead.
|
|
1535
|
-
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
1285
|
+
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser).then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
1536
1286
|
const dimensionOrUndefined = defaulted(imageDimensionToNumber, () => undefined);
|
|
1537
1287
|
const parsePerseusImageBackground = object({
|
|
1538
1288
|
url: optional(nullable(string)),
|
|
@@ -1573,10 +1323,6 @@ const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
|
1573
1323
|
rightAlign: optional(boolean),
|
|
1574
1324
|
simplify: enumeration("required", "optional", "enforced"),
|
|
1575
1325
|
size: enumeration("normal", "small"),
|
|
1576
|
-
// TODO(benchristel): there are some content items where value is a
|
|
1577
|
-
// boolean, even though that makes no sense. We should figure out if
|
|
1578
|
-
// those content items are actually published anywhere, and consider
|
|
1579
|
-
// updating them.
|
|
1580
1326
|
value: union(number).or(string).or(booleanToString).parser,
|
|
1581
1327
|
customKeypad: optional(boolean)
|
|
1582
1328
|
}));
|
|
@@ -1710,270 +1456,8 @@ const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
|
1710
1456
|
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)
|
|
1711
1457
|
}));
|
|
1712
1458
|
|
|
1713
|
-
|
|
1714
|
-
* The Perseus "data schema" file.
|
|
1715
|
-
*
|
|
1716
|
-
* This file, and the types in it, represents the "data schema" that Perseus
|
|
1717
|
-
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
1718
|
-
* that conform to the types in this file. Similarly, the top-level renderers
|
|
1719
|
-
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
1720
|
-
*
|
|
1721
|
-
* WARNING: This file should not import any types from elsewhere so that it is
|
|
1722
|
-
* easy to reason about changes that alter the Perseus schema. This helps
|
|
1723
|
-
* ensure that it is not changed accidentally when upgrading a dependant
|
|
1724
|
-
* package or other part of Perseus code. Note that TypeScript does type
|
|
1725
|
-
* checking via something called "structural typing". This means that as long
|
|
1726
|
-
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
1727
|
-
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
1728
|
-
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
1729
|
-
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
1730
|
-
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
1731
|
-
* labels are for humans, not TypeScript. :)
|
|
1732
|
-
*
|
|
1733
|
-
* If you make changes to types in this file, be very sure that:
|
|
1734
|
-
*
|
|
1735
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
1736
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
1737
|
-
* introduce hard-to-diagnose bugs.
|
|
1738
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
1739
|
-
* the new format _as well as_ the old format.
|
|
1740
|
-
*/
|
|
1741
|
-
|
|
1742
|
-
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
1743
|
-
|
|
1744
|
-
// Same name as Mafs
|
|
1745
|
-
|
|
1746
|
-
/**
|
|
1747
|
-
* A utility type that constructs a widget map from a "registry interface".
|
|
1748
|
-
* The keys of the registry should be the widget type (aka, "categorizer" or
|
|
1749
|
-
* "radio", etc) and the value should be the option type stored in the value
|
|
1750
|
-
* of the map.
|
|
1751
|
-
*
|
|
1752
|
-
* You can think of this as a type that generates another type. We use
|
|
1753
|
-
* "registry interfaces" as a way to keep a set of widget types to their data
|
|
1754
|
-
* type in several places in Perseus. This type then allows us to generate a
|
|
1755
|
-
* map type that maps a widget id to its data type and keep strong typing by
|
|
1756
|
-
* widget id.
|
|
1757
|
-
*
|
|
1758
|
-
* For example, given a fictitious registry such as this:
|
|
1759
|
-
*
|
|
1760
|
-
* ```
|
|
1761
|
-
* interface DummyRegistry {
|
|
1762
|
-
* categorizer: { categories: ReadonlyArray<string> };
|
|
1763
|
-
* dropdown: { choices: ReadonlyArray<string> }:
|
|
1764
|
-
* }
|
|
1765
|
-
* ```
|
|
1766
|
-
*
|
|
1767
|
-
* If we create a DummyMap using this helper:
|
|
1768
|
-
*
|
|
1769
|
-
* ```
|
|
1770
|
-
* type DummyMap = MakeWidgetMap<DummyRegistry>;
|
|
1771
|
-
* ```
|
|
1772
|
-
*
|
|
1773
|
-
* We'll get a map that looks like this:
|
|
1774
|
-
*
|
|
1775
|
-
* ```
|
|
1776
|
-
* type DummyMap = {
|
|
1777
|
-
* `categorizer ${number}`: { categories: ReadonlyArray<string> };
|
|
1778
|
-
* `dropdown ${number}`: { choices: ReadonlyArray<string> };
|
|
1779
|
-
* }
|
|
1780
|
-
* ```
|
|
1781
|
-
*
|
|
1782
|
-
* We use interfaces for the registries so that they can be extended in cases
|
|
1783
|
-
* where the consuming app brings along their own widgets. Interfaces in
|
|
1784
|
-
* TypeScript are always open (ie. you can extend them) whereas types aren't.
|
|
1785
|
-
*/
|
|
1786
|
-
|
|
1787
|
-
/**
|
|
1788
|
-
* Our core set of Perseus widgets.
|
|
1789
|
-
*
|
|
1790
|
-
* This interface is the basis for "registering" all Perseus widget types.
|
|
1791
|
-
* There should be one key/value pair for each supported widget. If you create
|
|
1792
|
-
* a new widget, an entry should be added to this interface. Note that this
|
|
1793
|
-
* only registers the widget options type, you'll also need to register the
|
|
1794
|
-
* widget so that it's available at runtime (@see
|
|
1795
|
-
* {@link file://./widgets.ts#registerWidget}).
|
|
1796
|
-
*
|
|
1797
|
-
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
1798
|
-
* widgets that is the same as the widget option's `type` field. In cases where
|
|
1799
|
-
* a widget has been deprecated and replaced with the deprecated-standin
|
|
1800
|
-
* widget, it should be the original widget type!
|
|
1801
|
-
*
|
|
1802
|
-
* If you define the widget outside of this package, you can still add the new
|
|
1803
|
-
* widget to this interface by writing the following in that package that
|
|
1804
|
-
* contains the widget. TypeScript will merge that definition of the
|
|
1805
|
-
* `PerseusWidgets` with the one defined below.
|
|
1806
|
-
*
|
|
1807
|
-
* ```typescript
|
|
1808
|
-
* declare module "@khanacademy/perseus-core" {
|
|
1809
|
-
* interface PerseusWidgetTypes {
|
|
1810
|
-
* // A new widget
|
|
1811
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
1812
|
-
*
|
|
1813
|
-
* // A deprecated widget
|
|
1814
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
1815
|
-
* }
|
|
1816
|
-
* }
|
|
1817
|
-
*
|
|
1818
|
-
* // The new widget's options definition
|
|
1819
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
1820
|
-
*
|
|
1821
|
-
* // The deprecated widget's options definition
|
|
1822
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
1823
|
-
* ```
|
|
1824
|
-
*
|
|
1825
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
1826
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
1827
|
-
*
|
|
1828
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
1829
|
-
*/
|
|
1830
|
-
|
|
1831
|
-
/**
|
|
1832
|
-
* A map of widget IDs to widget options. This is most often used as the type
|
|
1833
|
-
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
1834
|
-
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
1835
|
-
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
1836
|
-
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
1837
|
-
* reading/parsing the widget id to derive any information from it, except in
|
|
1838
|
-
* the case of this map.
|
|
1839
|
-
*
|
|
1840
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
1841
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
1842
|
-
*/
|
|
1843
|
-
|
|
1844
|
-
/**
|
|
1845
|
-
* PerseusWidget is a union of all the different types of widget options that
|
|
1846
|
-
* Perseus knows about.
|
|
1847
|
-
*
|
|
1848
|
-
* Thanks to it being based on PerseusWidgetTypes interface, this union is
|
|
1849
|
-
* automatically extended to include widgets used in tests without those widget
|
|
1850
|
-
* option types seeping into our production types.
|
|
1851
|
-
*
|
|
1852
|
-
* @see MockWidget for an example
|
|
1853
|
-
*/
|
|
1854
|
-
|
|
1855
|
-
/**
|
|
1856
|
-
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
1857
|
-
* `ServerItemRenderer` and the layout is pre-set.
|
|
1858
|
-
*
|
|
1859
|
-
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
1860
|
-
* area.
|
|
1861
|
-
*/
|
|
1862
|
-
|
|
1863
|
-
/**
|
|
1864
|
-
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
1865
|
-
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
1866
|
-
*/
|
|
1867
|
-
|
|
1868
|
-
const ItemExtras = [
|
|
1869
|
-
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
1870
|
-
"calculator",
|
|
1871
|
-
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
1872
|
-
"chi2Table",
|
|
1873
|
-
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
1874
|
-
"financialCalculatorMonthlyPayment",
|
|
1875
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
1876
|
-
"financialCalculatorTotalAmount",
|
|
1877
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
1878
|
-
"financialCalculatorTimeToPayOff",
|
|
1879
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
1880
|
-
"periodicTable",
|
|
1881
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
1882
|
-
"periodicTableWithKey",
|
|
1883
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
1884
|
-
"tTable",
|
|
1885
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
1886
|
-
"zTable"];
|
|
1887
|
-
|
|
1888
|
-
/**
|
|
1889
|
-
* The type representing the common structure of all widget's options. The
|
|
1890
|
-
* `Options` generic type represents the widget-specific option data.
|
|
1891
|
-
*/
|
|
1892
|
-
|
|
1893
|
-
// prettier-ignore
|
|
1894
|
-
|
|
1895
|
-
// prettier-ignore
|
|
1896
|
-
|
|
1897
|
-
// prettier-ignore
|
|
1898
|
-
|
|
1899
|
-
// prettier-ignore
|
|
1900
|
-
|
|
1901
|
-
// prettier-ignore
|
|
1902
|
-
|
|
1903
|
-
// prettier-ignore
|
|
1904
|
-
|
|
1905
|
-
// prettier-ignore
|
|
1906
|
-
|
|
1907
|
-
// prettier-ignore
|
|
1908
|
-
|
|
1909
|
-
// prettier-ignore
|
|
1910
|
-
|
|
1911
|
-
// prettier-ignore
|
|
1912
|
-
|
|
1913
|
-
// prettier-ignore
|
|
1914
|
-
|
|
1915
|
-
// prettier-ignore
|
|
1916
|
-
|
|
1917
|
-
// prettier-ignore
|
|
1918
|
-
|
|
1919
|
-
// prettier-ignore
|
|
1920
|
-
|
|
1921
|
-
// prettier-ignore
|
|
1922
|
-
|
|
1923
|
-
// prettier-ignore
|
|
1924
|
-
|
|
1925
|
-
// prettier-ignore
|
|
1926
|
-
|
|
1927
|
-
// prettier-ignore
|
|
1928
|
-
|
|
1929
|
-
// prettier-ignore
|
|
1930
|
-
|
|
1931
|
-
// prettier-ignore
|
|
1932
|
-
|
|
1933
|
-
// prettier-ignore
|
|
1934
|
-
|
|
1935
|
-
// prettier-ignore
|
|
1936
|
-
|
|
1937
|
-
// prettier-ignore
|
|
1938
|
-
|
|
1939
|
-
// prettier-ignore
|
|
1940
|
-
|
|
1941
|
-
// prettier-ignore
|
|
1942
|
-
|
|
1943
|
-
// prettier-ignore
|
|
1944
|
-
|
|
1945
|
-
// prettier-ignore
|
|
1946
|
-
|
|
1947
|
-
// prettier-ignore
|
|
1948
|
-
|
|
1949
|
-
// prettier-ignore
|
|
1950
|
-
|
|
1951
|
-
// prettier-ignore
|
|
1952
|
-
|
|
1953
|
-
// prettier-ignore
|
|
1954
|
-
|
|
1955
|
-
// prettier-ignore
|
|
1956
|
-
|
|
1957
|
-
// prettier-ignore
|
|
1958
|
-
|
|
1959
|
-
//prettier-ignore
|
|
1960
|
-
|
|
1961
|
-
/**
|
|
1962
|
-
* A background image applied to various widgets.
|
|
1963
|
-
*/
|
|
1964
|
-
|
|
1965
|
-
/**
|
|
1966
|
-
* The type of markings to display on the graph.
|
|
1967
|
-
* - axes: shows the axes without the gride lines
|
|
1968
|
-
* - graph: shows the axes and the grid lines
|
|
1969
|
-
* - grid: shows only the grid lines
|
|
1970
|
-
* - none: shows no markings
|
|
1971
|
-
*/
|
|
1972
|
-
|
|
1459
|
+
const ItemExtras = ["calculator", "chi2Table", "financialCalculatorMonthlyPayment", "financialCalculatorTotalAmount", "financialCalculatorTimeToPayOff", "periodicTable", "periodicTableWithKey", "tTable", "zTable"];
|
|
1973
1460
|
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
1974
|
-
|
|
1975
|
-
// 2D range: xMin, xMax, yMin, yMax
|
|
1976
|
-
|
|
1977
1461
|
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
1978
1462
|
const lockedFigureColors = {
|
|
1979
1463
|
blue: "#3D7586",
|
|
@@ -1990,21 +1474,8 @@ const lockedFigureFillStyles = {
|
|
|
1990
1474
|
translucent: 0.4,
|
|
1991
1475
|
solid: 1
|
|
1992
1476
|
};
|
|
1993
|
-
|
|
1994
|
-
// Not associated with a specific figure
|
|
1995
|
-
|
|
1996
|
-
/**
|
|
1997
|
-
* Determines how unsimplified fractions are scored.
|
|
1998
|
-
*
|
|
1999
|
-
* - "required" means unsimplified fractions are considered invalid input, and
|
|
2000
|
-
* the learner can try again.
|
|
2001
|
-
* - "enforced" means unsimplified fractions are marked incorrect.
|
|
2002
|
-
* - "optional" means unsimplified fractions are accepted.
|
|
2003
|
-
*/
|
|
2004
|
-
|
|
2005
1477
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
2006
1478
|
|
|
2007
|
-
// Used to represent 2-D points and ranges
|
|
2008
1479
|
const pairOfNumbers = pair(number, number);
|
|
2009
1480
|
const parsePerseusGraphTypeAngle = object({
|
|
2010
1481
|
type: constant("angle"),
|
|
@@ -2024,22 +1495,18 @@ const parsePerseusGraphTypeCircle = object({
|
|
|
2024
1495
|
center: pairOfNumbers,
|
|
2025
1496
|
radius: number
|
|
2026
1497
|
})),
|
|
2027
|
-
// TODO: remove coord? it's legacy.
|
|
2028
1498
|
coord: optional(pairOfNumbers)
|
|
2029
1499
|
});
|
|
2030
1500
|
const parsePerseusGraphTypeLinear = object({
|
|
2031
1501
|
type: constant("linear"),
|
|
2032
1502
|
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
2033
1503
|
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
2034
|
-
// TODO: remove coord? it's legacy.
|
|
2035
1504
|
coord: optional(pairOfNumbers)
|
|
2036
1505
|
});
|
|
2037
1506
|
const parsePerseusGraphTypeLinearSystem = object({
|
|
2038
1507
|
type: constant("linear-system"),
|
|
2039
|
-
// TODO(benchristel): default coords to empty array?
|
|
2040
1508
|
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
2041
1509
|
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
2042
|
-
// TODO: remove coord? it's legacy.
|
|
2043
1510
|
coord: optional(pairOfNumbers)
|
|
2044
1511
|
});
|
|
2045
1512
|
const parsePerseusGraphTypeNone = object({
|
|
@@ -2050,7 +1517,6 @@ const parsePerseusGraphTypePoint = object({
|
|
|
2050
1517
|
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
2051
1518
|
coords: optional(nullable(array(pairOfNumbers))),
|
|
2052
1519
|
startCoords: optional(array(pairOfNumbers)),
|
|
2053
|
-
// TODO: remove coord? it's legacy.
|
|
2054
1520
|
coord: optional(pairOfNumbers)
|
|
2055
1521
|
});
|
|
2056
1522
|
const parsePerseusGraphTypePolygon = object({
|
|
@@ -2061,37 +1527,31 @@ const parsePerseusGraphTypePolygon = object({
|
|
|
2061
1527
|
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
2062
1528
|
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
2063
1529
|
startCoords: optional(array(pairOfNumbers)),
|
|
2064
|
-
// TODO: remove coord? it's legacy.
|
|
2065
1530
|
coord: optional(pairOfNumbers)
|
|
2066
1531
|
});
|
|
2067
1532
|
const parsePerseusGraphTypeQuadratic = object({
|
|
2068
1533
|
type: constant("quadratic"),
|
|
2069
1534
|
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
2070
1535
|
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
2071
|
-
// TODO: remove coord? it's legacy.
|
|
2072
1536
|
coord: optional(pairOfNumbers)
|
|
2073
1537
|
});
|
|
2074
1538
|
const parsePerseusGraphTypeRay = object({
|
|
2075
1539
|
type: constant("ray"),
|
|
2076
1540
|
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
2077
1541
|
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
2078
|
-
// TODO: remove coord? it's legacy.
|
|
2079
1542
|
coord: optional(pairOfNumbers)
|
|
2080
1543
|
});
|
|
2081
1544
|
const parsePerseusGraphTypeSegment = object({
|
|
2082
1545
|
type: constant("segment"),
|
|
2083
|
-
// TODO(benchristel): default numSegments?
|
|
2084
1546
|
numSegments: optional(number),
|
|
2085
1547
|
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
2086
1548
|
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
2087
|
-
// TODO: remove coord? it's legacy.
|
|
2088
1549
|
coord: optional(pairOfNumbers)
|
|
2089
1550
|
});
|
|
2090
1551
|
const parsePerseusGraphTypeSinusoid = object({
|
|
2091
1552
|
type: constant("sinusoid"),
|
|
2092
1553
|
coords: optional(nullable(array(pairOfNumbers))),
|
|
2093
1554
|
startCoords: optional(array(pairOfNumbers)),
|
|
2094
|
-
// TODO: remove coord? it's legacy.
|
|
2095
1555
|
coord: optional(pairOfNumbers)
|
|
2096
1556
|
});
|
|
2097
1557
|
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;
|
|
@@ -2110,7 +1570,6 @@ const parseLockedPointType = object({
|
|
|
2110
1570
|
coord: pairOfNumbers,
|
|
2111
1571
|
color: parseLockedFigureColor,
|
|
2112
1572
|
filled: boolean,
|
|
2113
|
-
// TODO(benchristel): default labels to empty array?
|
|
2114
1573
|
labels: optional(array(parseLockedLabelType)),
|
|
2115
1574
|
ariaLabel: optional(string)
|
|
2116
1575
|
});
|
|
@@ -2122,7 +1581,6 @@ const parseLockedLineType = object({
|
|
|
2122
1581
|
lineStyle: parseLockedLineStyle,
|
|
2123
1582
|
showPoint1: defaulted(boolean, () => false),
|
|
2124
1583
|
showPoint2: defaulted(boolean, () => false),
|
|
2125
|
-
// TODO(benchristel): default labels to empty array?
|
|
2126
1584
|
labels: optional(array(parseLockedLabelType)),
|
|
2127
1585
|
ariaLabel: optional(string)
|
|
2128
1586
|
});
|
|
@@ -2130,7 +1588,6 @@ const parseLockedVectorType = object({
|
|
|
2130
1588
|
type: constant("vector"),
|
|
2131
1589
|
points: pair(pairOfNumbers, pairOfNumbers),
|
|
2132
1590
|
color: parseLockedFigureColor,
|
|
2133
|
-
// TODO(benchristel): default labels to empty array?
|
|
2134
1591
|
labels: optional(array(parseLockedLabelType)),
|
|
2135
1592
|
ariaLabel: optional(string)
|
|
2136
1593
|
});
|
|
@@ -2142,7 +1599,6 @@ const parseLockedEllipseType = object({
|
|
|
2142
1599
|
color: parseLockedFigureColor,
|
|
2143
1600
|
fillStyle: parseLockedFigureFillType,
|
|
2144
1601
|
strokeStyle: parseLockedLineStyle,
|
|
2145
|
-
// TODO(benchristel): default labels to empty array?
|
|
2146
1602
|
labels: optional(array(parseLockedLabelType)),
|
|
2147
1603
|
ariaLabel: optional(string)
|
|
2148
1604
|
});
|
|
@@ -2153,12 +1609,9 @@ const parseLockedPolygonType = object({
|
|
|
2153
1609
|
showVertices: boolean,
|
|
2154
1610
|
fillStyle: parseLockedFigureFillType,
|
|
2155
1611
|
strokeStyle: parseLockedLineStyle,
|
|
2156
|
-
// TODO(benchristel): default labels to empty array?
|
|
2157
1612
|
labels: optional(array(parseLockedLabelType)),
|
|
2158
1613
|
ariaLabel: optional(string)
|
|
2159
1614
|
});
|
|
2160
|
-
|
|
2161
|
-
// Exported for testing.
|
|
2162
1615
|
const parseLockedFunctionDomain = defaulted(pair(defaulted(number, () => -Infinity), defaulted(number, () => Infinity)), () => [-Infinity, Infinity]);
|
|
2163
1616
|
const parseLockedFunctionType = object({
|
|
2164
1617
|
type: constant("function"),
|
|
@@ -2167,17 +1620,12 @@ const parseLockedFunctionType = object({
|
|
|
2167
1620
|
equation: string,
|
|
2168
1621
|
directionalAxis: enumeration("x", "y"),
|
|
2169
1622
|
domain: parseLockedFunctionDomain,
|
|
2170
|
-
// TODO(benchristel): default labels to empty array?
|
|
2171
1623
|
labels: optional(array(parseLockedLabelType)),
|
|
2172
1624
|
ariaLabel: optional(string)
|
|
2173
1625
|
});
|
|
2174
1626
|
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;
|
|
2175
1627
|
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
2176
1628
|
step: pairOfNumbers,
|
|
2177
|
-
// TODO(benchristel): rather than making gridStep and snapStep
|
|
2178
|
-
// optional, we should duplicate the defaulting logic from the
|
|
2179
|
-
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
2180
|
-
// why.
|
|
2181
1629
|
gridStep: optional(pairOfNumbers),
|
|
2182
1630
|
snapStep: optional(pairOfNumbers),
|
|
2183
1631
|
backgroundImage: optional(parsePerseusImageBackground),
|
|
@@ -2189,14 +1637,10 @@ const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), o
|
|
|
2189
1637
|
rulerLabel: optional(string),
|
|
2190
1638
|
rulerTicks: optional(number),
|
|
2191
1639
|
range: pair(pairOfNumbers, pairOfNumbers),
|
|
2192
|
-
// NOTE(benchristel): I copied the default graph from
|
|
2193
|
-
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
2194
|
-
// an explanation of why we want to duplicate the default here.
|
|
2195
1640
|
graph: defaulted(parsePerseusGraphType, () => ({
|
|
2196
1641
|
type: "linear"
|
|
2197
1642
|
})),
|
|
2198
1643
|
correct: parsePerseusGraphType,
|
|
2199
|
-
// TODO(benchristel): default lockedFigures to empty array
|
|
2200
1644
|
lockedFigures: optional(array(parseLockedFigure)),
|
|
2201
1645
|
fullGraphLabel: optional(string),
|
|
2202
1646
|
fullGraphAriaDescription: optional(string)
|
|
@@ -2239,9 +1683,6 @@ const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matri
|
|
|
2239
1683
|
}));
|
|
2240
1684
|
|
|
2241
1685
|
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
2242
|
-
// The default value for image comes from measurer.tsx.
|
|
2243
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
2244
|
-
// defaults here.
|
|
2245
1686
|
image: defaulted(parsePerseusImageBackground, () => ({
|
|
2246
1687
|
url: null,
|
|
2247
1688
|
top: 0,
|
|
@@ -2254,7 +1695,6 @@ const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
|
2254
1695
|
rulerPixels: number,
|
|
2255
1696
|
rulerLength: number,
|
|
2256
1697
|
box: pair(number, number),
|
|
2257
|
-
// TODO(benchristel): static is not used. Remove it?
|
|
2258
1698
|
static: defaulted(boolean, () => false)
|
|
2259
1699
|
}));
|
|
2260
1700
|
|
|
@@ -2273,9 +1713,6 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
|
2273
1713
|
isTickCtrl: optional(nullable(boolean)),
|
|
2274
1714
|
divisionRange: array(number),
|
|
2275
1715
|
numDivisions: optional(nullable(number)),
|
|
2276
|
-
// NOTE(benchristel): I copied the default snapDivisions from
|
|
2277
|
-
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
2278
|
-
// an explanation of why we want to duplicate the default here.
|
|
2279
1716
|
snapDivisions: defaulted(number, () => 2),
|
|
2280
1717
|
tickStep: optional(nullable(number)),
|
|
2281
1718
|
correctRel: optional(nullable(string)),
|
|
@@ -2293,10 +1730,6 @@ function deprecatedSimplifyValuesToRequired(simplify) {
|
|
|
2293
1730
|
case "required":
|
|
2294
1731
|
case "optional":
|
|
2295
1732
|
return simplify;
|
|
2296
|
-
// NOTE(benchristel): "accepted", "correct", true, false, undefined, and
|
|
2297
|
-
// null are all treated the same as "required" during scoring, so we
|
|
2298
|
-
// convert them to "required" here to preserve behavior. See the tests
|
|
2299
|
-
// in score-numeric-input.test.ts
|
|
2300
1733
|
default:
|
|
2301
1734
|
return "required";
|
|
2302
1735
|
}
|
|
@@ -2304,17 +1737,11 @@ function deprecatedSimplifyValuesToRequired(simplify) {
|
|
|
2304
1737
|
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
2305
1738
|
answers: array(object({
|
|
2306
1739
|
message: string,
|
|
2307
|
-
// TODO(benchristel): value should never be null or undefined,
|
|
2308
|
-
// but we have some content where it is anyway. If we backfill
|
|
2309
|
-
// the data, simplify this.
|
|
2310
1740
|
value: optional(nullable(number)),
|
|
2311
1741
|
status: string,
|
|
2312
1742
|
answerForms: defaulted(array(parseMathFormat), () => undefined),
|
|
2313
1743
|
strict: boolean,
|
|
2314
1744
|
maxError: optional(nullable(number)),
|
|
2315
|
-
// TODO(benchristel): simplify should never be a boolean, but we
|
|
2316
|
-
// have some content where it is anyway. If we ever backfill
|
|
2317
|
-
// the data, we should simplify `simplify`.
|
|
2318
1745
|
simplify: parseSimplify
|
|
2319
1746
|
})),
|
|
2320
1747
|
labelText: optional(string),
|
|
@@ -2328,9 +1755,6 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
|
2328
1755
|
})))
|
|
2329
1756
|
}));
|
|
2330
1757
|
|
|
2331
|
-
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
2332
|
-
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
2333
|
-
// it's defined.
|
|
2334
1758
|
function parseRenderer(rawValue, ctx) {
|
|
2335
1759
|
return parsePerseusRenderer(rawValue, ctx);
|
|
2336
1760
|
}
|
|
@@ -2372,23 +1796,14 @@ const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
|
2372
1796
|
categories: array(string),
|
|
2373
1797
|
type: enumeration(...plotterPlotTypes),
|
|
2374
1798
|
maxY: number,
|
|
2375
|
-
// The default value for scaleY comes from plotter.tsx.
|
|
2376
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
2377
|
-
// defaults here.
|
|
2378
1799
|
scaleY: defaulted(number, () => 1),
|
|
2379
1800
|
labelInterval: optional(nullable(number)),
|
|
2380
|
-
// The default value for snapsPerLine comes from plotter.tsx.
|
|
2381
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
2382
|
-
// defaults here.
|
|
2383
1801
|
snapsPerLine: defaulted(number, () => 2),
|
|
2384
1802
|
starting: array(number),
|
|
2385
1803
|
correct: array(number),
|
|
2386
1804
|
picUrl: optional(nullable(string)),
|
|
2387
1805
|
picSize: optional(nullable(number)),
|
|
2388
1806
|
picBoxHeight: optional(nullable(number)),
|
|
2389
|
-
// NOTE(benchristel): I copied the default plotDimensions from
|
|
2390
|
-
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
2391
|
-
// of why we want to duplicate the defaults here.
|
|
2392
1807
|
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
2393
1808
|
}));
|
|
2394
1809
|
|
|
@@ -2397,6 +1812,7 @@ const parsePythonProgramWidget = parseWidget(constant("python-program"), object(
|
|
|
2397
1812
|
height: number
|
|
2398
1813
|
}));
|
|
2399
1814
|
|
|
1815
|
+
const _excluded$a = ["noneOfTheAbove"];
|
|
2400
1816
|
const currentVersion$3 = {
|
|
2401
1817
|
major: 2,
|
|
2402
1818
|
minor: 0
|
|
@@ -2406,28 +1822,26 @@ function deriveNumCorrect(options) {
|
|
|
2406
1822
|
choices,
|
|
2407
1823
|
numCorrect
|
|
2408
1824
|
} = options;
|
|
2409
|
-
return numCorrect
|
|
1825
|
+
return numCorrect != null ? numCorrect : choices.filter(c => c.correct).length;
|
|
2410
1826
|
}
|
|
2411
1827
|
const widgetOptionsUpgrades$2 = {
|
|
2412
1828
|
"2": v1props => {
|
|
2413
|
-
const upgraded = {
|
|
2414
|
-
...v1props,
|
|
1829
|
+
const upgraded = _extends({}, v1props, {
|
|
2415
1830
|
numCorrect: deriveNumCorrect(v1props)
|
|
2416
|
-
};
|
|
1831
|
+
});
|
|
2417
1832
|
return upgraded;
|
|
2418
1833
|
},
|
|
2419
1834
|
"1": v0props => {
|
|
2420
1835
|
const {
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
1836
|
+
noneOfTheAbove
|
|
1837
|
+
} = v0props,
|
|
1838
|
+
rest = _objectWithoutPropertiesLoose(v0props, _excluded$a);
|
|
2424
1839
|
if (noneOfTheAbove) {
|
|
2425
1840
|
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
2426
1841
|
}
|
|
2427
|
-
return {
|
|
2428
|
-
...rest,
|
|
1842
|
+
return _extends({}, rest, {
|
|
2429
1843
|
hasNoneOfTheAbove: false
|
|
2430
|
-
};
|
|
1844
|
+
});
|
|
2431
1845
|
}
|
|
2432
1846
|
};
|
|
2433
1847
|
const defaultWidgetOptions$v = {
|
|
@@ -2440,6 +1854,7 @@ const defaultWidgetOptions$v = {
|
|
|
2440
1854
|
deselectEnabled: false
|
|
2441
1855
|
};
|
|
2442
1856
|
|
|
1857
|
+
const _excluded$9 = ["noneOfTheAbove"];
|
|
2443
1858
|
const version2 = optional(object({
|
|
2444
1859
|
major: constant(2),
|
|
2445
1860
|
minor: number
|
|
@@ -2451,10 +1866,6 @@ const parseRadioWidgetV2 = parseWidgetWithVersion(version2, constant("radio"), o
|
|
|
2451
1866
|
clue: optional(string),
|
|
2452
1867
|
correct: optional(boolean),
|
|
2453
1868
|
isNoneOfTheAbove: optional(boolean),
|
|
2454
|
-
// deprecated
|
|
2455
|
-
// There is an import cycle between radio-widget.ts and
|
|
2456
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
2457
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
2458
1869
|
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
2459
1870
|
})),
|
|
2460
1871
|
hasNoneOfTheAbove: optional(boolean),
|
|
@@ -2462,12 +1873,8 @@ const parseRadioWidgetV2 = parseWidgetWithVersion(version2, constant("radio"), o
|
|
|
2462
1873
|
randomize: optional(boolean),
|
|
2463
1874
|
multipleSelect: optional(boolean),
|
|
2464
1875
|
deselectEnabled: optional(boolean),
|
|
2465
|
-
// deprecated
|
|
2466
1876
|
onePerLine: optional(boolean),
|
|
2467
|
-
// deprecated
|
|
2468
1877
|
displayCount: optional(any),
|
|
2469
|
-
// v0 props
|
|
2470
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
2471
1878
|
noneOfTheAbove: optional(constant(false))
|
|
2472
1879
|
}));
|
|
2473
1880
|
const version1 = optional(object({
|
|
@@ -2480,10 +1887,6 @@ const parseRadioWidgetV1 = parseWidgetWithVersion(version1, constant("radio"), o
|
|
|
2480
1887
|
clue: optional(string),
|
|
2481
1888
|
correct: optional(boolean),
|
|
2482
1889
|
isNoneOfTheAbove: optional(boolean),
|
|
2483
|
-
// deprecated
|
|
2484
|
-
// There is an import cycle between radio-widget.ts and
|
|
2485
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
2486
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
2487
1890
|
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => undefined)
|
|
2488
1891
|
})),
|
|
2489
1892
|
hasNoneOfTheAbove: optional(boolean),
|
|
@@ -2491,29 +1894,23 @@ const parseRadioWidgetV1 = parseWidgetWithVersion(version1, constant("radio"), o
|
|
|
2491
1894
|
randomize: optional(boolean),
|
|
2492
1895
|
multipleSelect: optional(boolean),
|
|
2493
1896
|
deselectEnabled: optional(boolean),
|
|
2494
|
-
// deprecated
|
|
2495
1897
|
onePerLine: optional(boolean),
|
|
2496
|
-
// deprecated
|
|
2497
1898
|
displayCount: optional(any),
|
|
2498
|
-
// v0 props
|
|
2499
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
2500
1899
|
noneOfTheAbove: optional(constant(false))
|
|
2501
1900
|
}));
|
|
2502
1901
|
function migrateV1ToV2(widget) {
|
|
2503
1902
|
const {
|
|
2504
1903
|
options
|
|
2505
1904
|
} = widget;
|
|
2506
|
-
return {
|
|
2507
|
-
...widget,
|
|
1905
|
+
return _extends({}, widget, {
|
|
2508
1906
|
version: {
|
|
2509
1907
|
major: 2,
|
|
2510
1908
|
minor: 0
|
|
2511
1909
|
},
|
|
2512
|
-
options: {
|
|
2513
|
-
...options,
|
|
1910
|
+
options: _extends({}, options, {
|
|
2514
1911
|
numCorrect: deriveNumCorrect(options)
|
|
2515
|
-
}
|
|
2516
|
-
};
|
|
1912
|
+
})
|
|
1913
|
+
});
|
|
2517
1914
|
}
|
|
2518
1915
|
const version0 = optional(object({
|
|
2519
1916
|
major: constant(0),
|
|
@@ -2525,10 +1922,6 @@ const parseRadioWidgetV0 = parseWidgetWithVersion(version0, constant("radio"), o
|
|
|
2525
1922
|
clue: optional(string),
|
|
2526
1923
|
correct: optional(boolean),
|
|
2527
1924
|
isNoneOfTheAbove: optional(boolean),
|
|
2528
|
-
// deprecated
|
|
2529
|
-
// There is an import cycle between radio-widget.ts and
|
|
2530
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
2531
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
2532
1925
|
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
2533
1926
|
})),
|
|
2534
1927
|
hasNoneOfTheAbove: optional(boolean),
|
|
@@ -2536,33 +1929,24 @@ const parseRadioWidgetV0 = parseWidgetWithVersion(version0, constant("radio"), o
|
|
|
2536
1929
|
randomize: optional(boolean),
|
|
2537
1930
|
multipleSelect: optional(boolean),
|
|
2538
1931
|
deselectEnabled: optional(boolean),
|
|
2539
|
-
// deprecated
|
|
2540
1932
|
onePerLine: optional(boolean),
|
|
2541
|
-
// deprecated
|
|
2542
1933
|
displayCount: optional(any),
|
|
2543
|
-
// v0 props
|
|
2544
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
2545
1934
|
noneOfTheAbove: optional(constant(false))
|
|
2546
1935
|
}));
|
|
2547
1936
|
function migrateV0ToV1(widget) {
|
|
2548
1937
|
const {
|
|
2549
1938
|
options
|
|
2550
1939
|
} = widget;
|
|
2551
|
-
const
|
|
2552
|
-
|
|
2553
|
-
...rest
|
|
2554
|
-
} = options;
|
|
2555
|
-
return {
|
|
2556
|
-
...widget,
|
|
1940
|
+
const rest = _objectWithoutPropertiesLoose(options, _excluded$9);
|
|
1941
|
+
return _extends({}, widget, {
|
|
2557
1942
|
version: {
|
|
2558
1943
|
major: 1,
|
|
2559
1944
|
minor: 0
|
|
2560
1945
|
},
|
|
2561
|
-
options: {
|
|
2562
|
-
...rest,
|
|
1946
|
+
options: _extends({}, rest, {
|
|
2563
1947
|
hasNoneOfTheAbove: false
|
|
2564
|
-
}
|
|
2565
|
-
};
|
|
1948
|
+
})
|
|
1949
|
+
});
|
|
2566
1950
|
}
|
|
2567
1951
|
const parseRadioWidget = versionedWidgetOptions(2, parseRadioWidgetV2).withMigrationFrom(1, parseRadioWidgetV1, migrateV1ToV2).withMigrationFrom(0, parseRadioWidgetV0, migrateV0ToV1).parser;
|
|
2568
1952
|
|
|
@@ -2590,9 +1974,6 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
2590
1974
|
}
|
|
2591
1975
|
const widgetsMap = {};
|
|
2592
1976
|
for (const key of Object.keys(rawValue)) {
|
|
2593
|
-
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
2594
|
-
// but it's the only way I could find to make TypeScript check the key
|
|
2595
|
-
// against the widget type.
|
|
2596
1977
|
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
2597
1978
|
if (isFailure(entryResult)) {
|
|
2598
1979
|
return entryResult;
|
|
@@ -2600,8 +1981,7 @@ const parseWidgetsMap = (rawValue, ctx) => {
|
|
|
2600
1981
|
}
|
|
2601
1982
|
return ctx.success(widgetsMap);
|
|
2602
1983
|
};
|
|
2603
|
-
const parseWidgetsMapEntry = (
|
|
2604
|
-
let [id, widget] = _ref;
|
|
1984
|
+
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
2605
1985
|
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
2606
1986
|
if (isFailure(idComponentsResult)) {
|
|
2607
1987
|
return idComponentsResult;
|
|
@@ -2667,9 +2047,6 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
|
2667
2047
|
case "passage-ref":
|
|
2668
2048
|
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
2669
2049
|
case "passage-ref-target":
|
|
2670
|
-
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
2671
|
-
// used in test content. See:
|
|
2672
|
-
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
2673
2050
|
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
2674
2051
|
case "phet-simulation":
|
|
2675
2052
|
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
@@ -2686,8 +2063,6 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
|
2686
2063
|
case "video":
|
|
2687
2064
|
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
2688
2065
|
case "sequence":
|
|
2689
|
-
// sequence is a deprecated widget type, and the corresponding
|
|
2690
|
-
// widget component no longer exists.
|
|
2691
2066
|
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
2692
2067
|
case "lights-puzzle":
|
|
2693
2068
|
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
@@ -2699,16 +2074,8 @@ const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
|
2699
2074
|
return parseAndAssign(`${type} ${n}`, parseWidget(constant(type), any));
|
|
2700
2075
|
}
|
|
2701
2076
|
};
|
|
2702
|
-
const parseDeprecatedWidget = parseWidget(
|
|
2703
|
-
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
2704
|
-
(_, ctx) => ctx.success("deprecated-standin"),
|
|
2705
|
-
// Allow any widget options
|
|
2706
|
-
object({}));
|
|
2077
|
+
const parseDeprecatedWidget = parseWidget((_, ctx) => ctx.success("deprecated-standin"), object({}));
|
|
2707
2078
|
const parseStringToNonNegativeInt = (rawValue, ctx) => {
|
|
2708
|
-
// The article renderer seems to allow the numeric part of a widget ID to
|
|
2709
|
-
// be 0, at least for image widgets. However, if widget IDs in an exercise
|
|
2710
|
-
// contain 0, the exercise renderer will blow up. We allow 0 here for
|
|
2711
|
-
// compatibility with articles.
|
|
2712
2079
|
if (typeof rawValue !== "string" || !/^(0|[1-9][0-9]*)$/.test(rawValue)) {
|
|
2713
2080
|
return ctx.failure("a string representing a non-negative integer", rawValue);
|
|
2714
2081
|
}
|
|
@@ -2717,20 +2084,11 @@ const parseStringToNonNegativeInt = (rawValue, ctx) => {
|
|
|
2717
2084
|
const parseWidgetIdComponents = pair(string, parseStringToNonNegativeInt);
|
|
2718
2085
|
|
|
2719
2086
|
const parsePerseusRenderer = defaulted(object({
|
|
2720
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
2721
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
2722
2087
|
content: defaulted(string, () => ""),
|
|
2723
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
2724
|
-
// `group` widget can contain another renderer.
|
|
2725
|
-
// The anonymous function below ensures that we don't try to access
|
|
2726
|
-
// parseWidgetsMap before it's defined.
|
|
2727
2088
|
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
2728
2089
|
images: parseImages,
|
|
2729
|
-
// deprecated
|
|
2730
2090
|
metadata: any
|
|
2731
|
-
}),
|
|
2732
|
-
// Default value
|
|
2733
|
-
() => ({
|
|
2091
|
+
}), () => ({
|
|
2734
2092
|
content: "",
|
|
2735
2093
|
widgets: {},
|
|
2736
2094
|
images: {}
|
|
@@ -2743,25 +2101,10 @@ const parseHint = object({
|
|
|
2743
2101
|
content: string,
|
|
2744
2102
|
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
2745
2103
|
images: parseImages,
|
|
2746
|
-
// deprecated
|
|
2747
2104
|
metadata: any
|
|
2748
2105
|
});
|
|
2749
2106
|
|
|
2750
2107
|
const parsePerseusAnswerArea = pipeParsers(defaulted(object({}), () => ({}))).then(convert(toAnswerArea)).parser;
|
|
2751
|
-
|
|
2752
|
-
// Some answerAreas have extra, bogus fields, like:
|
|
2753
|
-
//
|
|
2754
|
-
// "answerArea": {
|
|
2755
|
-
// "type": "multiple",
|
|
2756
|
-
// "options": {},
|
|
2757
|
-
// "version": null,
|
|
2758
|
-
// "static": false,
|
|
2759
|
-
// "graded": false,
|
|
2760
|
-
// "alignment": "",
|
|
2761
|
-
// }
|
|
2762
|
-
//
|
|
2763
|
-
// This function filters the fields of an answerArea object, keeping only the
|
|
2764
|
-
// known ones, and converts `undefined` and `null` values to `false`.
|
|
2765
2108
|
function toAnswerArea(raw) {
|
|
2766
2109
|
return {
|
|
2767
2110
|
zTable: !!raw.zTable,
|
|
@@ -2780,41 +2123,19 @@ const parsePerseusItem$1 = object({
|
|
|
2780
2123
|
question: parsePerseusRenderer,
|
|
2781
2124
|
hints: defaulted(array(parseHint), () => []),
|
|
2782
2125
|
answerArea: parsePerseusAnswerArea,
|
|
2783
|
-
itemDataVersion: optional(object({
|
|
2126
|
+
itemDataVersion: optional(nullable(object({
|
|
2784
2127
|
major: number,
|
|
2785
2128
|
minor: number
|
|
2786
|
-
})),
|
|
2787
|
-
// Deprecated field
|
|
2129
|
+
}))),
|
|
2788
2130
|
answer: any
|
|
2789
2131
|
});
|
|
2790
2132
|
|
|
2791
|
-
/**
|
|
2792
|
-
* Helper to parse PerseusItem JSON
|
|
2793
|
-
* Why not just use JSON.parse? We want:
|
|
2794
|
-
* - To make sure types are correct
|
|
2795
|
-
* - To give us a central place to validate/transform output if needed
|
|
2796
|
-
* @deprecated - use parseAndMigratePerseusItem instead
|
|
2797
|
-
* @param {string} json - the stringified PerseusItem JSON
|
|
2798
|
-
* @returns {PerseusItem} the parsed PerseusItem object
|
|
2799
|
-
*/
|
|
2800
2133
|
function parsePerseusItem(json) {
|
|
2801
|
-
// Try to block a cheating vector which relies on monkey-patching
|
|
2802
|
-
// JSON.parse
|
|
2803
2134
|
if (isRealJSONParse(JSON.parse)) {
|
|
2804
2135
|
return JSON.parse(json);
|
|
2805
2136
|
}
|
|
2806
2137
|
throw new Error("Something went wrong.");
|
|
2807
2138
|
}
|
|
2808
|
-
/**
|
|
2809
|
-
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
2810
|
-
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
2811
|
-
* data.
|
|
2812
|
-
*
|
|
2813
|
-
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
2814
|
-
* failure, it will contain an error message describing where in the tree
|
|
2815
|
-
* parsing failed.
|
|
2816
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
2817
|
-
*/
|
|
2818
2139
|
function parseAndMigratePerseusItem(json) {
|
|
2819
2140
|
throwErrorIfCheatingDetected();
|
|
2820
2141
|
const object = JSON.parse(json);
|
|
@@ -2827,16 +2148,6 @@ function parseAndMigratePerseusItem(json) {
|
|
|
2827
2148
|
}
|
|
2828
2149
|
return result;
|
|
2829
2150
|
}
|
|
2830
|
-
|
|
2831
|
-
/**
|
|
2832
|
-
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
2833
|
-
* latest schema, and runtime-typechecks the result.
|
|
2834
|
-
*
|
|
2835
|
-
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
2836
|
-
* failure, it will contain an error message describing where in the tree
|
|
2837
|
-
* parsing failed.
|
|
2838
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
2839
|
-
*/
|
|
2840
2151
|
function parseAndMigratePerseusArticle(json) {
|
|
2841
2152
|
throwErrorIfCheatingDetected();
|
|
2842
2153
|
const object = JSON.parse(json);
|
|
@@ -2849,168 +2160,48 @@ function parseAndMigratePerseusArticle(json) {
|
|
|
2849
2160
|
}
|
|
2850
2161
|
return result;
|
|
2851
2162
|
}
|
|
2852
|
-
|
|
2853
|
-
/**
|
|
2854
|
-
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
2855
|
-
*/
|
|
2856
|
-
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
2857
2163
|
function throwErrorIfCheatingDetected() {
|
|
2858
2164
|
if (!isRealJSONParse(JSON.parse)) {
|
|
2859
2165
|
throw new Error("Something went wrong.");
|
|
2860
2166
|
}
|
|
2861
2167
|
}
|
|
2862
2168
|
|
|
2863
|
-
/**
|
|
2864
|
-
* Adds the given perseus library version information to the __perseus_debug__
|
|
2865
|
-
* object and ensures that the object is attached to `globalThis` (`window` in
|
|
2866
|
-
* browser environments).
|
|
2867
|
-
*
|
|
2868
|
-
* This allows each library to provide runtime version information to assist in
|
|
2869
|
-
* debugging in production environments.
|
|
2870
|
-
*/
|
|
2871
|
-
const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
2872
|
-
// If the library version is the default value, then we don't want to
|
|
2873
|
-
// prefix it with a "v" to indicate that it is a version number.
|
|
2874
|
-
let prefix = "v";
|
|
2875
|
-
if (libraryVersion === "__lib_version__") {
|
|
2876
|
-
prefix = "";
|
|
2877
|
-
}
|
|
2878
|
-
const formattedVersion = `${prefix}${libraryVersion}`;
|
|
2879
|
-
if (typeof globalThis !== "undefined") {
|
|
2880
|
-
globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
|
|
2881
|
-
const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
|
|
2882
|
-
if (existingVersionEntry) {
|
|
2883
|
-
// If we already have an entry and it doesn't match the registered
|
|
2884
|
-
// version, we morph the entry into an array and log a warning.
|
|
2885
|
-
if (existingVersionEntry !== formattedVersion) {
|
|
2886
|
-
// Existing entry might be an array already (oops, at least 2
|
|
2887
|
-
// versions of the library already loaded!).
|
|
2888
|
-
const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
|
|
2889
|
-
allVersions.push(formattedVersion);
|
|
2890
|
-
globalThis.__perseus_debug__[libraryName] = allVersions;
|
|
2891
|
-
|
|
2892
|
-
// eslint-disable-next-line no-console
|
|
2893
|
-
console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
|
|
2894
|
-
}
|
|
2895
|
-
} else {
|
|
2896
|
-
globalThis.__perseus_debug__[libraryName] = formattedVersion;
|
|
2897
|
-
}
|
|
2898
|
-
} else {
|
|
2899
|
-
// eslint-disable-next-line no-console
|
|
2900
|
-
console.warn(`globalThis not found found (${formattedVersion})`);
|
|
2901
|
-
}
|
|
2902
|
-
};
|
|
2903
|
-
|
|
2904
|
-
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
2905
2169
|
const libName = "@khanacademy/perseus-core";
|
|
2906
|
-
const libVersion = "
|
|
2170
|
+
const libVersion = "6.0.0";
|
|
2907
2171
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
2908
2172
|
|
|
2909
|
-
/**
|
|
2910
|
-
* @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
|
|
2911
|
-
*/
|
|
2912
2173
|
const Errors = Object.freeze({
|
|
2913
|
-
/**
|
|
2914
|
-
* @property {ErrorKind} Unknown The kind of error is not known.
|
|
2915
|
-
*/
|
|
2916
2174
|
Unknown: "Unknown",
|
|
2917
|
-
/**
|
|
2918
|
-
* @property {ErrorKind} Internal The error is internal to the executing code.
|
|
2919
|
-
*/
|
|
2920
2175
|
Internal: "Internal",
|
|
2921
|
-
/**
|
|
2922
|
-
* @property {ErrorKind} InvalidInput There was a problem with the provided
|
|
2923
|
-
* input, such as the wrong format or a null value.
|
|
2924
|
-
*/
|
|
2925
2176
|
InvalidInput: "InvalidInput",
|
|
2926
|
-
/**
|
|
2927
|
-
* @property {ErrorKind} NotAllowed There was a problem due to the state of
|
|
2928
|
-
* the system not matching the requested operation or input. For example,
|
|
2929
|
-
* trying to create a username that is valid, but is already taken by
|
|
2930
|
-
* another user. Use {@link InvalidInput} instead when the input isn't
|
|
2931
|
-
* valid regardless of the state of the system. Use {@link NotFound} when
|
|
2932
|
-
* the failure is due to not being able to find a resource.
|
|
2933
|
-
*/
|
|
2934
2177
|
NotAllowed: "NotAllowed",
|
|
2935
|
-
/**
|
|
2936
|
-
* @property {ErrorKind} TransientService There was a problem when making a
|
|
2937
|
-
* request to a service.
|
|
2938
|
-
*/
|
|
2939
2178
|
TransientService: "TransientService",
|
|
2940
|
-
/**
|
|
2941
|
-
* @property {ErrorKind} Service There was a non-transient problem when
|
|
2942
|
-
* making a request to service.
|
|
2943
|
-
*/
|
|
2944
2179
|
Service: "Service"
|
|
2945
2180
|
});
|
|
2946
2181
|
|
|
2947
|
-
/**
|
|
2948
|
-
* @type {ErrorKind} The kind of error being reported
|
|
2949
|
-
*/
|
|
2950
|
-
|
|
2951
2182
|
class PerseusError extends Error {
|
|
2952
|
-
kind;
|
|
2953
|
-
metadata;
|
|
2954
2183
|
constructor(message, kind, options) {
|
|
2955
2184
|
super(message);
|
|
2185
|
+
this.kind = void 0;
|
|
2186
|
+
this.metadata = void 0;
|
|
2956
2187
|
this.kind = kind;
|
|
2957
|
-
this.metadata = options
|
|
2188
|
+
this.metadata = options == null ? void 0 : options.metadata;
|
|
2958
2189
|
}
|
|
2959
2190
|
}
|
|
2960
2191
|
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
*/
|
|
2964
|
-
|
|
2965
|
-
/**
|
|
2966
|
-
* Does a pluck on keys inside objects in an object
|
|
2967
|
-
*
|
|
2968
|
-
* Ex:
|
|
2969
|
-
* tools = {
|
|
2970
|
-
* translation: {
|
|
2971
|
-
* enabled: true
|
|
2972
|
-
* },
|
|
2973
|
-
* rotation: {
|
|
2974
|
-
* enabled: false
|
|
2975
|
-
* }
|
|
2976
|
-
* };
|
|
2977
|
-
* pluckObject(tools, "enabled") returns {
|
|
2978
|
-
* translation: true
|
|
2979
|
-
* rotation: false
|
|
2980
|
-
* }
|
|
2981
|
-
*/
|
|
2982
|
-
const pluck = function (table, subKey) {
|
|
2983
|
-
return ___default["default"].object(___default["default"].map(table, function (value, key) {
|
|
2192
|
+
const pluck = function pluck(table, subKey) {
|
|
2193
|
+
return _.object(_.map(table, function (value, key) {
|
|
2984
2194
|
return [key, value[subKey]];
|
|
2985
2195
|
}));
|
|
2986
2196
|
};
|
|
2987
|
-
|
|
2988
|
-
/**
|
|
2989
|
-
* Maps an object to an object
|
|
2990
|
-
*
|
|
2991
|
-
* > mapObject({a: '1', b: '2'}, (value, key) => {
|
|
2992
|
-
* return value + 1;
|
|
2993
|
-
* });
|
|
2994
|
-
* {a: 2, b: 3}
|
|
2995
|
-
*/
|
|
2996
|
-
const mapObject = function (obj, lambda) {
|
|
2197
|
+
const mapObject = function mapObject(obj, lambda) {
|
|
2997
2198
|
const result = {};
|
|
2998
2199
|
Object.keys(obj).forEach(key => {
|
|
2999
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
|
|
3000
2200
|
result[key] = lambda(obj[key], key);
|
|
3001
2201
|
});
|
|
3002
2202
|
return result;
|
|
3003
2203
|
};
|
|
3004
2204
|
|
|
3005
|
-
/**
|
|
3006
|
-
* For details on the individual options, see the
|
|
3007
|
-
* PerseusCategorizerWidgetOptions type
|
|
3008
|
-
*/
|
|
3009
|
-
|
|
3010
|
-
/**
|
|
3011
|
-
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
3012
|
-
* the public options that should be exposed to the client.
|
|
3013
|
-
*/
|
|
3014
2205
|
function getCategorizerPublicWidgetOptions(options) {
|
|
3015
2206
|
return {
|
|
3016
2207
|
items: options.items,
|
|
@@ -3065,15 +2256,6 @@ const definitionWidgetLogic = {
|
|
|
3065
2256
|
defaultAlignment: "inline"
|
|
3066
2257
|
};
|
|
3067
2258
|
|
|
3068
|
-
/**
|
|
3069
|
-
* For details on the individual options, see the
|
|
3070
|
-
* PerseusDropdownWidgetOptions type
|
|
3071
|
-
*/
|
|
3072
|
-
|
|
3073
|
-
/**
|
|
3074
|
-
* Given a PerseusDropdownWidgetOptions object, return a new object with only
|
|
3075
|
-
* the public options that should be exposed to the client.
|
|
3076
|
-
*/
|
|
3077
2259
|
function getDropdownPublicWidgetOptions(options) {
|
|
3078
2260
|
return {
|
|
3079
2261
|
choices: options.choices.map(choice => ({
|
|
@@ -3154,15 +2336,6 @@ const defaultWidgetOptions$p = {
|
|
|
3154
2336
|
functions: ["f", "g", "h"]
|
|
3155
2337
|
};
|
|
3156
2338
|
|
|
3157
|
-
/**
|
|
3158
|
-
* For details on the individual options, see the
|
|
3159
|
-
* PerseusExpressionWidgetOptions type
|
|
3160
|
-
*/
|
|
3161
|
-
|
|
3162
|
-
/**
|
|
3163
|
-
* Given a PerseusExpressionWidgetOptions object, return a new object with only
|
|
3164
|
-
* the public options that should be exposed to the client.
|
|
3165
|
-
*/
|
|
3166
2339
|
function getExpressionPublicWidgetOptions(options) {
|
|
3167
2340
|
return {
|
|
3168
2341
|
buttonSets: options.buttonSets,
|
|
@@ -3204,11 +2377,9 @@ const gradedGroupSetWidgetLogic = {
|
|
|
3204
2377
|
defaultWidgetOptions: defaultWidgetOptions$n
|
|
3205
2378
|
};
|
|
3206
2379
|
|
|
2380
|
+
const _excluded$8 = ["correct"];
|
|
3207
2381
|
function getGrapherPublicWidgetOptions(options) {
|
|
3208
|
-
const
|
|
3209
|
-
correct: _,
|
|
3210
|
-
...publicOptions
|
|
3211
|
-
} = options;
|
|
2382
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$8);
|
|
3212
2383
|
return publicOptions;
|
|
3213
2384
|
}
|
|
3214
2385
|
|
|
@@ -3320,11 +2491,9 @@ const interactionWidgetLogic = {
|
|
|
3320
2491
|
defaultWidgetOptions: defaultWidgetOptions$h
|
|
3321
2492
|
};
|
|
3322
2493
|
|
|
2494
|
+
const _excluded$7 = ["correct"];
|
|
3323
2495
|
function getInteractiveGraphPublicWidgetOptions(options) {
|
|
3324
|
-
const
|
|
3325
|
-
correct: _,
|
|
3326
|
-
...publicOptions
|
|
3327
|
-
} = options;
|
|
2496
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$7);
|
|
3328
2497
|
return publicOptions;
|
|
3329
2498
|
}
|
|
3330
2499
|
|
|
@@ -3352,22 +2521,14 @@ const interactiveGraphWidgetLogic = {
|
|
|
3352
2521
|
getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
|
|
3353
2522
|
};
|
|
3354
2523
|
|
|
3355
|
-
|
|
3356
|
-
* For details on the individual options, see the
|
|
3357
|
-
* PerseusLabelImageWidgetOptions type
|
|
3358
|
-
*/
|
|
3359
|
-
|
|
2524
|
+
const _excluded$6 = ["answers"];
|
|
3360
2525
|
function getLabelImagePublicWidgetOptions(options) {
|
|
3361
|
-
return {
|
|
3362
|
-
...options,
|
|
2526
|
+
return _extends({}, options, {
|
|
3363
2527
|
markers: options.markers.map(getLabelImageMarkerPublicData)
|
|
3364
|
-
};
|
|
2528
|
+
});
|
|
3365
2529
|
}
|
|
3366
2530
|
function getLabelImageMarkerPublicData(marker) {
|
|
3367
|
-
const
|
|
3368
|
-
answers: _,
|
|
3369
|
-
...publicData
|
|
3370
|
-
} = marker;
|
|
2531
|
+
const publicData = _objectWithoutPropertiesLoose(marker, _excluded$6);
|
|
3371
2532
|
return publicData;
|
|
3372
2533
|
}
|
|
3373
2534
|
|
|
@@ -3387,62 +2548,80 @@ const labelImageWidgetLogic = {
|
|
|
3387
2548
|
getPublicWidgetOptions: getLabelImagePublicWidgetOptions
|
|
3388
2549
|
};
|
|
3389
2550
|
|
|
3390
|
-
|
|
2551
|
+
const seededRNG = function seededRNG(seed) {
|
|
2552
|
+
let randomSeed = seed;
|
|
2553
|
+
return function () {
|
|
2554
|
+
let seed = randomSeed;
|
|
2555
|
+
seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
|
|
2556
|
+
seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
|
|
2557
|
+
seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
|
|
2558
|
+
seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
|
|
2559
|
+
seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
|
|
2560
|
+
seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
|
|
2561
|
+
return (randomSeed = seed & 0xfffffff) / 0x10000000;
|
|
2562
|
+
};
|
|
2563
|
+
};
|
|
2564
|
+
function shuffle(array, randomSeed, ensurePermuted = false) {
|
|
2565
|
+
const shuffled = _.clone(array);
|
|
2566
|
+
if (!shuffled.length || _.all(shuffled, function (value) {
|
|
2567
|
+
return _.isEqual(value, shuffled[0]);
|
|
2568
|
+
})) {
|
|
2569
|
+
return shuffled;
|
|
2570
|
+
}
|
|
2571
|
+
let random;
|
|
2572
|
+
if (typeof randomSeed === "function") {
|
|
2573
|
+
random = randomSeed;
|
|
2574
|
+
} else {
|
|
2575
|
+
random = seededRNG(randomSeed);
|
|
2576
|
+
}
|
|
2577
|
+
do {
|
|
2578
|
+
for (let top = shuffled.length; top > 0; top--) {
|
|
2579
|
+
const newEnd = Math.floor(random() * top);
|
|
2580
|
+
const temp = shuffled[newEnd];
|
|
2581
|
+
shuffled[newEnd] = shuffled[top - 1];
|
|
2582
|
+
shuffled[top - 1] = temp;
|
|
2583
|
+
}
|
|
2584
|
+
} while (ensurePermuted && _.isEqual(array, shuffled));
|
|
2585
|
+
return shuffled;
|
|
2586
|
+
}
|
|
2587
|
+
const random = seededRNG(new Date().getTime() & 0xffffffff);
|
|
3391
2588
|
|
|
3392
|
-
// TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
|
|
3393
2589
|
const shuffleMatcher = props => {
|
|
3394
|
-
|
|
3395
|
-
const rng = perseusCore.seededRNG(props.problemNum);
|
|
2590
|
+
const rng = seededRNG(props.problemNum);
|
|
3396
2591
|
let left;
|
|
3397
2592
|
if (!props.orderMatters) {
|
|
3398
|
-
// If the order doesn't matter, don't shuffle the left column
|
|
3399
2593
|
left = props.left;
|
|
3400
2594
|
} else {
|
|
3401
|
-
left =
|
|
2595
|
+
left = shuffle(props.left, rng, true);
|
|
3402
2596
|
}
|
|
3403
|
-
const right =
|
|
2597
|
+
const right = shuffle(props.right, rng, true);
|
|
3404
2598
|
return {
|
|
3405
2599
|
left,
|
|
3406
2600
|
right
|
|
3407
2601
|
};
|
|
3408
2602
|
};
|
|
3409
|
-
|
|
3410
|
-
// TODO(LEMS-2841): Can shorten to shuffleMatcher after above function removed
|
|
3411
2603
|
function shuffleMatcherWithRandom(data) {
|
|
3412
|
-
// Use the same random() function to shuffle both columns sequentially
|
|
3413
2604
|
let left;
|
|
3414
2605
|
if (!data.orderMatters) {
|
|
3415
|
-
// If the order doesn't matter, don't shuffle the left column
|
|
3416
2606
|
left = data.left;
|
|
3417
2607
|
} else {
|
|
3418
|
-
left =
|
|
2608
|
+
left = shuffle(data.left, Math.random, true);
|
|
3419
2609
|
}
|
|
3420
|
-
const right =
|
|
2610
|
+
const right = shuffle(data.right, Math.random, true);
|
|
3421
2611
|
return {
|
|
3422
2612
|
left,
|
|
3423
2613
|
right
|
|
3424
2614
|
};
|
|
3425
2615
|
}
|
|
3426
|
-
|
|
3427
|
-
/**
|
|
3428
|
-
* For details on the individual options, see the
|
|
3429
|
-
* PerseusMatcherWidgetOptions type
|
|
3430
|
-
*/
|
|
3431
|
-
|
|
3432
|
-
/**
|
|
3433
|
-
* Given a PerseusMatcherWidgetOptions object, return a new object with only
|
|
3434
|
-
* the public options that should be exposed to the client.
|
|
3435
|
-
*/
|
|
3436
2616
|
function getMatcherPublicWidgetOptions(options) {
|
|
3437
2617
|
const {
|
|
3438
2618
|
left,
|
|
3439
2619
|
right
|
|
3440
2620
|
} = shuffleMatcherWithRandom(options);
|
|
3441
|
-
return {
|
|
3442
|
-
...options,
|
|
2621
|
+
return _extends({}, options, {
|
|
3443
2622
|
left: left,
|
|
3444
2623
|
right: right
|
|
3445
|
-
};
|
|
2624
|
+
});
|
|
3446
2625
|
}
|
|
3447
2626
|
|
|
3448
2627
|
const defaultWidgetOptions$e = {
|
|
@@ -3458,11 +2637,9 @@ const matcherWidgetLogic = {
|
|
|
3458
2637
|
getPublicWidgetOptions: getMatcherPublicWidgetOptions
|
|
3459
2638
|
};
|
|
3460
2639
|
|
|
2640
|
+
const _excluded$5 = ["answers"];
|
|
3461
2641
|
function getMatrixPublicWidgetOptions(options) {
|
|
3462
|
-
const
|
|
3463
|
-
answers: _,
|
|
3464
|
-
...publicOptions
|
|
3465
|
-
} = options;
|
|
2642
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$5);
|
|
3466
2643
|
return publicOptions;
|
|
3467
2644
|
}
|
|
3468
2645
|
|
|
@@ -3479,6 +2656,7 @@ const matrixWidgetLogic = {
|
|
|
3479
2656
|
getPublicWidgetOptions: getMatrixPublicWidgetOptions
|
|
3480
2657
|
};
|
|
3481
2658
|
|
|
2659
|
+
const _excluded$4 = ["imageUrl", "imageTop", "imageLeft"];
|
|
3482
2660
|
const currentVersion$1 = {
|
|
3483
2661
|
major: 1,
|
|
3484
2662
|
minor: 0
|
|
@@ -3486,19 +2664,18 @@ const currentVersion$1 = {
|
|
|
3486
2664
|
const widgetOptionsUpgrades = {
|
|
3487
2665
|
"1": v0options => {
|
|
3488
2666
|
const {
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
return {
|
|
3495
|
-
...rest,
|
|
2667
|
+
imageUrl,
|
|
2668
|
+
imageTop,
|
|
2669
|
+
imageLeft
|
|
2670
|
+
} = v0options,
|
|
2671
|
+
rest = _objectWithoutPropertiesLoose(v0options, _excluded$4);
|
|
2672
|
+
return _extends({}, rest, {
|
|
3496
2673
|
image: {
|
|
3497
2674
|
url: imageUrl,
|
|
3498
2675
|
top: imageTop,
|
|
3499
2676
|
left: imageLeft
|
|
3500
2677
|
}
|
|
3501
|
-
};
|
|
2678
|
+
});
|
|
3502
2679
|
}
|
|
3503
2680
|
};
|
|
3504
2681
|
const defaultWidgetOptions$c = {
|
|
@@ -3519,12 +2696,9 @@ const measurerWidgetLogic = {
|
|
|
3519
2696
|
defaultWidgetOptions: defaultWidgetOptions$c
|
|
3520
2697
|
};
|
|
3521
2698
|
|
|
2699
|
+
const _excluded$3 = ["correctX", "correctRel"];
|
|
3522
2700
|
function getNumberLinePublicWidgetOptions(options) {
|
|
3523
|
-
const
|
|
3524
|
-
correctX: _,
|
|
3525
|
-
correctRel: __,
|
|
3526
|
-
...publicOptions
|
|
3527
|
-
} = options;
|
|
2701
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$3);
|
|
3528
2702
|
return publicOptions;
|
|
3529
2703
|
}
|
|
3530
2704
|
|
|
@@ -3548,15 +2722,7 @@ const numberLineWidgetLogic = {
|
|
|
3548
2722
|
getPublicWidgetOptions: getNumberLinePublicWidgetOptions
|
|
3549
2723
|
};
|
|
3550
2724
|
|
|
3551
|
-
|
|
3552
|
-
* For details on the individual options, see the
|
|
3553
|
-
* PerseusNumericInputWidgetOptions type
|
|
3554
|
-
*/
|
|
3555
|
-
|
|
3556
|
-
/**
|
|
3557
|
-
* This data from `answers` is used pre-scoring to give hints
|
|
3558
|
-
* to the learner regarding the format of accepted answers
|
|
3559
|
-
*/
|
|
2725
|
+
const _excluded$2 = ["answers"];
|
|
3560
2726
|
function getNumericInputAnswerPublicData(answer) {
|
|
3561
2727
|
const {
|
|
3562
2728
|
answerForms,
|
|
@@ -3569,20 +2735,14 @@ function getNumericInputAnswerPublicData(answer) {
|
|
|
3569
2735
|
status
|
|
3570
2736
|
};
|
|
3571
2737
|
}
|
|
3572
|
-
|
|
3573
|
-
/**
|
|
3574
|
-
* Given a PerseusNumericInputWidgetOptions object, return a new object with only
|
|
3575
|
-
* the public options that should be exposed to the client.
|
|
3576
|
-
*/
|
|
3577
2738
|
function getNumericInputPublicWidgetOptions(options) {
|
|
3578
2739
|
const {
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
return {
|
|
3583
|
-
...publicWidgetOptions,
|
|
2740
|
+
answers
|
|
2741
|
+
} = options,
|
|
2742
|
+
publicWidgetOptions = _objectWithoutPropertiesLoose(options, _excluded$2);
|
|
2743
|
+
return _extends({}, publicWidgetOptions, {
|
|
3584
2744
|
answers: answers.map(getNumericInputAnswerPublicData)
|
|
3585
|
-
};
|
|
2745
|
+
});
|
|
3586
2746
|
}
|
|
3587
2747
|
|
|
3588
2748
|
const defaultWidgetOptions$a = {
|
|
@@ -3607,15 +2767,6 @@ const numericInputWidgetLogic = {
|
|
|
3607
2767
|
getPublicWidgetOptions: getNumericInputPublicWidgetOptions
|
|
3608
2768
|
};
|
|
3609
2769
|
|
|
3610
|
-
/**
|
|
3611
|
-
* For details on the individual options, see the
|
|
3612
|
-
* PerseusOrdererWidgetOptions type
|
|
3613
|
-
*/
|
|
3614
|
-
|
|
3615
|
-
/**
|
|
3616
|
-
* Given a PerseusOrdererWidgetOptions object, return a new object with only
|
|
3617
|
-
* the public options that should be exposed to the client.
|
|
3618
|
-
*/
|
|
3619
2770
|
function getOrdererPublicWidgetOptions(options) {
|
|
3620
2771
|
return {
|
|
3621
2772
|
options: options.options,
|
|
@@ -3686,20 +2837,9 @@ const phetSimulationWidgetLogic = {
|
|
|
3686
2837
|
defaultWidgetOptions: defaultWidgetOptions$5
|
|
3687
2838
|
};
|
|
3688
2839
|
|
|
3689
|
-
|
|
3690
|
-
* For details on the individual options, see the
|
|
3691
|
-
* PerseusPlotterWidgetOptions type
|
|
3692
|
-
*/
|
|
3693
|
-
|
|
3694
|
-
/**
|
|
3695
|
-
* Given a PerseusPlotterWidgetOptions object, return a new object with only
|
|
3696
|
-
* the public options that should be exposed to the client.
|
|
3697
|
-
*/
|
|
2840
|
+
const _excluded$1 = ["correct"];
|
|
3698
2841
|
function getPlotterPublicWidgetOptions(options) {
|
|
3699
|
-
const
|
|
3700
|
-
correct: _,
|
|
3701
|
-
...publicOptions
|
|
3702
|
-
} = options;
|
|
2842
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded$1);
|
|
3703
2843
|
return publicOptions;
|
|
3704
2844
|
}
|
|
3705
2845
|
|
|
@@ -3733,19 +2873,6 @@ const pythonProgramWidgetLogic = {
|
|
|
3733
2873
|
defaultWidgetOptions: defaultWidgetOptions$3
|
|
3734
2874
|
};
|
|
3735
2875
|
|
|
3736
|
-
/**
|
|
3737
|
-
* For details on the individual options, see the
|
|
3738
|
-
* PerseusRadioWidgetOptions type
|
|
3739
|
-
*/
|
|
3740
|
-
|
|
3741
|
-
/**
|
|
3742
|
-
* Only the options from each Radio choice that should be exposed to the client.
|
|
3743
|
-
*/
|
|
3744
|
-
|
|
3745
|
-
/**
|
|
3746
|
-
* Given a PerseusRadioChoice object, return a new object with only the public
|
|
3747
|
-
* data that should be included in the Radio public widget options.
|
|
3748
|
-
*/
|
|
3749
2876
|
function getRadioChoicePublicData(choice) {
|
|
3750
2877
|
const {
|
|
3751
2878
|
content,
|
|
@@ -3758,23 +2885,9 @@ function getRadioChoicePublicData(choice) {
|
|
|
3758
2885
|
widgets
|
|
3759
2886
|
};
|
|
3760
2887
|
}
|
|
3761
|
-
|
|
3762
|
-
/**
|
|
3763
|
-
* Shared functionality to determine if numCorrect is used, because:
|
|
3764
|
-
*
|
|
3765
|
-
* 1. numCorrect is conditionally used for rendering pre-scoring
|
|
3766
|
-
* 2. numCorrect also exposes information about answers
|
|
3767
|
-
*
|
|
3768
|
-
* So only include/use numCorrect when we know it's useful.
|
|
3769
|
-
*/
|
|
3770
2888
|
function usesNumCorrect(multipleSelect, countChoices, numCorrect) {
|
|
3771
2889
|
return multipleSelect && countChoices && numCorrect;
|
|
3772
2890
|
}
|
|
3773
|
-
|
|
3774
|
-
/**
|
|
3775
|
-
* Given a PerseusRadioWidgetOptions object, return a new object with only
|
|
3776
|
-
* the public options that should be exposed to the client.
|
|
3777
|
-
*/
|
|
3778
2891
|
function getRadioPublicWidgetOptions(options) {
|
|
3779
2892
|
const {
|
|
3780
2893
|
numCorrect,
|
|
@@ -3782,11 +2895,10 @@ function getRadioPublicWidgetOptions(options) {
|
|
|
3782
2895
|
multipleSelect,
|
|
3783
2896
|
countChoices
|
|
3784
2897
|
} = options;
|
|
3785
|
-
return {
|
|
3786
|
-
...options,
|
|
2898
|
+
return _extends({}, options, {
|
|
3787
2899
|
numCorrect: usesNumCorrect(multipleSelect, countChoices, numCorrect) ? numCorrect : undefined,
|
|
3788
2900
|
choices: choices.map(getRadioChoicePublicData)
|
|
3789
|
-
};
|
|
2901
|
+
});
|
|
3790
2902
|
}
|
|
3791
2903
|
|
|
3792
2904
|
const radioWidgetLogic = {
|
|
@@ -3797,28 +2909,12 @@ const radioWidgetLogic = {
|
|
|
3797
2909
|
getPublicWidgetOptions: getRadioPublicWidgetOptions
|
|
3798
2910
|
};
|
|
3799
2911
|
|
|
3800
|
-
/**
|
|
3801
|
-
* For details on the individual options, see the
|
|
3802
|
-
* PerseusSorterWidgetOptions type
|
|
3803
|
-
*/
|
|
3804
|
-
|
|
3805
|
-
/**
|
|
3806
|
-
* Given a PerseusSorterWidgetOptions object, return a new object with only
|
|
3807
|
-
* the public options that should be exposed to the client.
|
|
3808
|
-
*/
|
|
3809
2912
|
function getSorterPublicWidgetOptions(options) {
|
|
3810
|
-
const shuffledCorrect =
|
|
3811
|
-
return {
|
|
3812
|
-
...options,
|
|
3813
|
-
// Note(Tamara): This does not provide correct answer information any longer.
|
|
3814
|
-
// To maintain compatibility with the original widget options, we are
|
|
3815
|
-
// keeping the key the same. Represents initial state of the cards here.
|
|
2913
|
+
const shuffledCorrect = shuffle(options.correct, Math.random, true);
|
|
2914
|
+
return _extends({}, options, {
|
|
3816
2915
|
correct: shuffledCorrect,
|
|
3817
|
-
// Note(Tamara): This new key is only added here with "true". There isn't
|
|
3818
|
-
// a place where it is set to false. It indicates that the correct field
|
|
3819
|
-
// has been shuffled and no longer contains correct answer info.
|
|
3820
2916
|
isCorrectShuffled: true
|
|
3821
|
-
};
|
|
2917
|
+
});
|
|
3822
2918
|
}
|
|
3823
2919
|
|
|
3824
2920
|
const defaultWidgetOptions$2 = {
|
|
@@ -3832,19 +2928,14 @@ const sorterWidgetLogic = {
|
|
|
3832
2928
|
getPublicWidgetOptions: getSorterPublicWidgetOptions
|
|
3833
2929
|
};
|
|
3834
2930
|
|
|
2931
|
+
const _excluded = ["answers"];
|
|
3835
2932
|
function getTablePublicWidgetOptions(options) {
|
|
3836
|
-
const
|
|
3837
|
-
answers: _,
|
|
3838
|
-
...publicOptions
|
|
3839
|
-
} = options;
|
|
2933
|
+
const publicOptions = _objectWithoutPropertiesLoose(options, _excluded);
|
|
3840
2934
|
return publicOptions;
|
|
3841
2935
|
}
|
|
3842
2936
|
|
|
3843
2937
|
const defaultRows = 4;
|
|
3844
2938
|
const defaultColumns = 1;
|
|
3845
|
-
|
|
3846
|
-
// initialize a 2D array
|
|
3847
|
-
// (defaultRows x defaultColumns) of empty strings
|
|
3848
2939
|
const answers = new Array(defaultRows).fill(0).map(() => new Array(defaultColumns).fill(""));
|
|
3849
2940
|
const defaultWidgetOptions$1 = {
|
|
3850
2941
|
headers: [""],
|
|
@@ -3878,60 +2969,34 @@ function isWidgetRegistered(type) {
|
|
|
3878
2969
|
}
|
|
3879
2970
|
function getCurrentVersion(type) {
|
|
3880
2971
|
const widgetLogic = widgets[type];
|
|
3881
|
-
return widgetLogic
|
|
2972
|
+
return (widgetLogic == null ? void 0 : widgetLogic.version) || {
|
|
3882
2973
|
major: 0,
|
|
3883
2974
|
minor: 0
|
|
3884
2975
|
};
|
|
3885
2976
|
}
|
|
3886
|
-
|
|
3887
|
-
// TODO(LEMS-2870): getPublicWidgetOptionsFunction/PublicWidgetOptionsFunction
|
|
3888
|
-
// need better types
|
|
3889
2977
|
const getPublicWidgetOptionsFunction = name => {
|
|
3890
|
-
|
|
2978
|
+
var _widgets$name$getPubl, _widgets$name;
|
|
2979
|
+
return (_widgets$name$getPubl = (_widgets$name = widgets[name]) == null ? void 0 : _widgets$name.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
3891
2980
|
};
|
|
3892
2981
|
function getWidgetOptionsUpgrades(type) {
|
|
3893
2982
|
const widgetLogic = widgets[type];
|
|
3894
|
-
return widgetLogic
|
|
2983
|
+
return (widgetLogic == null ? void 0 : widgetLogic.widgetOptionsUpgrades) || {};
|
|
3895
2984
|
}
|
|
3896
2985
|
function getDefaultWidgetOptions(type) {
|
|
3897
2986
|
const widgetLogic = widgets[type];
|
|
3898
|
-
return widgetLogic
|
|
2987
|
+
return (widgetLogic == null ? void 0 : widgetLogic.defaultWidgetOptions) || {};
|
|
3899
2988
|
}
|
|
3900
|
-
|
|
3901
|
-
/**
|
|
3902
|
-
* Handling for the optional alignments for widgets
|
|
3903
|
-
* See widget-container.jsx for details on how alignments are implemented.
|
|
3904
|
-
*/
|
|
3905
|
-
|
|
3906
|
-
/**
|
|
3907
|
-
* Returns the list of supported alignments for the given (string) widget
|
|
3908
|
-
* type. This is used primarily at editing time to display the choices
|
|
3909
|
-
* for the user.
|
|
3910
|
-
*
|
|
3911
|
-
* Supported alignments are given as an array of strings in the exports of
|
|
3912
|
-
* a widget's module.
|
|
3913
|
-
*/
|
|
3914
2989
|
const getSupportedAlignments = type => {
|
|
2990
|
+
var _widgetLogic$supporte;
|
|
3915
2991
|
const widgetLogic = widgets[type];
|
|
3916
|
-
if (!widgetLogic
|
|
3917
|
-
// default alignments
|
|
2992
|
+
if (!(widgetLogic != null && (_widgetLogic$supporte = widgetLogic.supportedAlignments) != null && _widgetLogic$supporte[0])) {
|
|
3918
2993
|
return ["default"];
|
|
3919
2994
|
}
|
|
3920
|
-
return widgetLogic
|
|
2995
|
+
return widgetLogic == null ? void 0 : widgetLogic.supportedAlignments;
|
|
3921
2996
|
};
|
|
3922
|
-
|
|
3923
|
-
/**
|
|
3924
|
-
* For the given (string) widget type, determine the default alignment for
|
|
3925
|
-
* the widget. This is used at rendering time to go from "default" alignment
|
|
3926
|
-
* to the actual alignment displayed on the screen.
|
|
3927
|
-
*
|
|
3928
|
-
* The default alignment is given either as a string (called
|
|
3929
|
-
* `defaultAlignment`) or a function (called `getDefaultAlignment`) on
|
|
3930
|
-
* the exports of a widget's module.
|
|
3931
|
-
*/
|
|
3932
2997
|
const getDefaultAlignment = type => {
|
|
3933
2998
|
const widgetLogic = widgets[type];
|
|
3934
|
-
if (!widgetLogic
|
|
2999
|
+
if (!(widgetLogic != null && widgetLogic.defaultAlignment)) {
|
|
3935
3000
|
return "block";
|
|
3936
3001
|
}
|
|
3937
3002
|
return widgetLogic.defaultAlignment;
|
|
@@ -3971,63 +3036,35 @@ registerWidget("video", videoWidgetLogic);
|
|
|
3971
3036
|
|
|
3972
3037
|
var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
|
|
3973
3038
|
__proto__: null,
|
|
3974
|
-
isWidgetRegistered: isWidgetRegistered,
|
|
3975
3039
|
getCurrentVersion: getCurrentVersion,
|
|
3976
|
-
|
|
3977
|
-
getWidgetOptionsUpgrades: getWidgetOptionsUpgrades,
|
|
3040
|
+
getDefaultAlignment: getDefaultAlignment,
|
|
3978
3041
|
getDefaultWidgetOptions: getDefaultWidgetOptions,
|
|
3042
|
+
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
3979
3043
|
getSupportedAlignments: getSupportedAlignments,
|
|
3980
|
-
|
|
3044
|
+
getWidgetOptionsUpgrades: getWidgetOptionsUpgrades,
|
|
3045
|
+
isWidgetRegistered: isWidgetRegistered
|
|
3981
3046
|
});
|
|
3982
3047
|
|
|
3983
3048
|
const DEFAULT_STATIC = false;
|
|
3984
3049
|
const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
3985
3050
|
const type = oldWidgetInfo.type;
|
|
3986
|
-
|
|
3987
|
-
// that `type` is non-optional. But we're seeing this in Sentry today so I
|
|
3988
|
-
// suspect we have legacy data (potentially unpublished) and we should
|
|
3989
|
-
// figure that out before depending solely on types.
|
|
3990
|
-
if (!___default["default"].isString(type)) {
|
|
3051
|
+
if (!_.isString(type)) {
|
|
3991
3052
|
throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
|
|
3992
3053
|
}
|
|
3993
3054
|
if (!isWidgetRegistered(type)) {
|
|
3994
|
-
// If we have a widget that isn't registered, we can't upgrade it
|
|
3995
|
-
// TODO(aria): Figure out what the best thing to do here would be
|
|
3996
3055
|
return oldWidgetInfo;
|
|
3997
3056
|
}
|
|
3998
|
-
|
|
3999
|
-
// Unversioned widgets (pre-July 2014) are all implicitly 0.0
|
|
4000
3057
|
const initialVersion = oldWidgetInfo.version || {
|
|
4001
3058
|
major: 0,
|
|
4002
3059
|
minor: 0
|
|
4003
3060
|
};
|
|
4004
3061
|
const latestVersion = getCurrentVersion(type);
|
|
4005
|
-
|
|
4006
|
-
// If the widget version is later than what we understand (major
|
|
4007
|
-
// version is higher than latest, or major versions are equal and minor
|
|
4008
|
-
// version is higher than latest), don't perform any upgrades.
|
|
4009
3062
|
if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
|
|
4010
3063
|
return oldWidgetInfo;
|
|
4011
3064
|
}
|
|
4012
|
-
|
|
4013
|
-
// We do a clone here so that it's safe to mutate the input parameter
|
|
4014
|
-
// in propUpgrades functions (which I will probably accidentally do at
|
|
4015
|
-
// some point, and we would like to not break when that happens).
|
|
4016
|
-
let newEditorOptions = ___default["default"].clone(oldWidgetInfo.options) || {};
|
|
3065
|
+
let newEditorOptions = _.clone(oldWidgetInfo.options) || {};
|
|
4017
3066
|
const upgradePropsMap = getWidgetOptionsUpgrades(type);
|
|
4018
|
-
|
|
4019
|
-
// Empty props usually mean a newly created widget by the editor,
|
|
4020
|
-
// and are always considerered up-to-date.
|
|
4021
|
-
// Mostly, we'd rather not run upgrade functions on props that are
|
|
4022
|
-
// not complete.
|
|
4023
|
-
if (___default["default"].keys(newEditorOptions).length !== 0) {
|
|
4024
|
-
// We loop through all the versions after the current version of
|
|
4025
|
-
// the loaded widget, up to and including the latest version of the
|
|
4026
|
-
// loaded widget, and run the upgrade function to bring our loaded
|
|
4027
|
-
// widget's props up to that version.
|
|
4028
|
-
// There is a little subtlety here in that we call
|
|
4029
|
-
// upgradePropsMap[1] to upgrade *to* version 1,
|
|
4030
|
-
// (not from version 1).
|
|
3067
|
+
if (_.keys(newEditorOptions).length !== 0) {
|
|
4031
3068
|
for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
|
|
4032
3069
|
if (upgradePropsMap[String(nextVersion)]) {
|
|
4033
3070
|
newEditorOptions = upgradePropsMap[String(nextVersion)](newEditorOptions);
|
|
@@ -4043,23 +3080,12 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
|
4043
3080
|
}
|
|
4044
3081
|
}
|
|
4045
3082
|
}
|
|
4046
|
-
|
|
4047
|
-
// Minor version upgrades (eg. new optional props) don't have
|
|
4048
|
-
// transform functions. Instead, we fill in the new props with their
|
|
4049
|
-
// defaults.
|
|
4050
3083
|
const defaultOptions = getDefaultWidgetOptions(type);
|
|
4051
|
-
newEditorOptions = {
|
|
4052
|
-
...defaultOptions,
|
|
4053
|
-
...newEditorOptions
|
|
4054
|
-
};
|
|
3084
|
+
newEditorOptions = _extends({}, defaultOptions, newEditorOptions);
|
|
4055
3085
|
let alignment = oldWidgetInfo.alignment;
|
|
4056
|
-
|
|
4057
|
-
// Widgets that support multiple alignments will "lock in" the
|
|
4058
|
-
// alignment to the alignment that would be listed first in the
|
|
4059
|
-
// select box. If the widget only supports one alignment, the
|
|
4060
|
-
// alignment value will likely just end up as "default".
|
|
4061
3086
|
if (alignment == null || alignment === "default") {
|
|
4062
|
-
|
|
3087
|
+
var _getSupportedAlignmen;
|
|
3088
|
+
alignment = (_getSupportedAlignmen = getSupportedAlignments(type)) == null ? void 0 : _getSupportedAlignmen[0];
|
|
4063
3089
|
if (!alignment) {
|
|
4064
3090
|
throw new PerseusError("No default alignment found when upgrading widget", Errors.Internal, {
|
|
4065
3091
|
metadata: {
|
|
@@ -4072,200 +3098,46 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
|
|
|
4072
3098
|
if (widgetStatic == null) {
|
|
4073
3099
|
widgetStatic = DEFAULT_STATIC;
|
|
4074
3100
|
}
|
|
4075
|
-
return {
|
|
4076
|
-
...oldWidgetInfo,
|
|
4077
|
-
// maintain other info, like type
|
|
4078
|
-
// After upgrading we guarantee that the version is up-to-date
|
|
3101
|
+
return _extends({}, oldWidgetInfo, {
|
|
4079
3102
|
version: latestVersion,
|
|
4080
|
-
// Default graded to true (so null/undefined becomes true):
|
|
4081
3103
|
graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
|
|
4082
3104
|
alignment: alignment,
|
|
4083
3105
|
static: widgetStatic,
|
|
4084
3106
|
options: newEditorOptions
|
|
4085
|
-
};
|
|
3107
|
+
});
|
|
4086
3108
|
};
|
|
4087
3109
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
4088
3110
|
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
4089
3111
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
4090
3112
|
const newValues = {};
|
|
4091
3113
|
if (!widgetInfo.type) {
|
|
4092
|
-
// TODO: why does widget have no type?
|
|
4093
|
-
// We don't want to derive type from widget ID
|
|
4094
|
-
// see: LEMS-1845
|
|
4095
3114
|
newValues.type = widgetId.split(" ")[0];
|
|
4096
3115
|
}
|
|
4097
3116
|
if (!widgetInfo.alignment) {
|
|
4098
3117
|
newValues.alignment = "default";
|
|
4099
3118
|
}
|
|
4100
|
-
widgetInfo = {
|
|
4101
|
-
...widgetInfo,
|
|
4102
|
-
...newValues
|
|
4103
|
-
};
|
|
3119
|
+
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
4104
3120
|
}
|
|
4105
3121
|
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
4106
3122
|
});
|
|
4107
3123
|
}
|
|
4108
3124
|
|
|
4109
|
-
/**
|
|
4110
|
-
* Return a copy of a Perseus item with rubric data removed (ie answers)
|
|
4111
|
-
*
|
|
4112
|
-
* @param originalItem - the original, full Perseus item (which includes the rubric - aka answer data)
|
|
4113
|
-
*/
|
|
4114
3125
|
function splitPerseusItem(originalItem) {
|
|
4115
|
-
|
|
4116
|
-
const
|
|
3126
|
+
var _item$widgets;
|
|
3127
|
+
const item = _.clone(originalItem);
|
|
3128
|
+
const originalWidgets = (_item$widgets = item.widgets) != null ? _item$widgets : {};
|
|
4117
3129
|
const upgradedWidgets = getUpgradedWidgetOptions(originalWidgets);
|
|
4118
3130
|
const splitWidgets = {};
|
|
4119
3131
|
for (const [id, widget] of Object.entries(upgradedWidgets)) {
|
|
4120
3132
|
const publicWidgetOptionsFun = getPublicWidgetOptionsFunction(widget.type);
|
|
4121
|
-
splitWidgets[id] = {
|
|
4122
|
-
...widget,
|
|
3133
|
+
splitWidgets[id] = _extends({}, widget, {
|
|
4123
3134
|
options: publicWidgetOptionsFun(widget.options)
|
|
4124
|
-
};
|
|
3135
|
+
});
|
|
4125
3136
|
}
|
|
4126
|
-
return {
|
|
4127
|
-
...item,
|
|
3137
|
+
return _extends({}, item, {
|
|
4128
3138
|
widgets: splitWidgets
|
|
4129
|
-
};
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
/* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
|
|
4133
|
-
May be useful to bring other perseus package utilities here. Contains utility functions
|
|
4134
|
-
and types used across multiple widgets for randomization and shuffling. */
|
|
4135
|
-
const seededRNG = function (seed) {
|
|
4136
|
-
let randomSeed = seed;
|
|
4137
|
-
return function () {
|
|
4138
|
-
// Robert Jenkins' 32 bit integer hash function.
|
|
4139
|
-
let seed = randomSeed;
|
|
4140
|
-
seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
|
|
4141
|
-
seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
|
|
4142
|
-
seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
|
|
4143
|
-
seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
|
|
4144
|
-
seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
|
|
4145
|
-
seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
|
|
4146
|
-
return (randomSeed = seed & 0xfffffff) / 0x10000000;
|
|
4147
|
-
};
|
|
4148
|
-
};
|
|
4149
|
-
|
|
4150
|
-
// Shuffle an array using a given random seed or function.
|
|
4151
|
-
// If `ensurePermuted` is true, the input and output are guaranteed to be
|
|
4152
|
-
// distinct permutations.
|
|
4153
|
-
function shuffle(array, randomSeed) {
|
|
4154
|
-
let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
4155
|
-
// Always return a copy of the input array
|
|
4156
|
-
const shuffled = ___default["default"].clone(array);
|
|
4157
|
-
|
|
4158
|
-
// Handle edge cases (input array is empty or uniform)
|
|
4159
|
-
if (!shuffled.length || ___default["default"].all(shuffled, function (value) {
|
|
4160
|
-
return ___default["default"].isEqual(value, shuffled[0]);
|
|
4161
|
-
})) {
|
|
4162
|
-
return shuffled;
|
|
4163
|
-
}
|
|
4164
|
-
let random;
|
|
4165
|
-
if (typeof randomSeed === "function") {
|
|
4166
|
-
random = randomSeed;
|
|
4167
|
-
} else {
|
|
4168
|
-
random = seededRNG(randomSeed);
|
|
4169
|
-
}
|
|
4170
|
-
do {
|
|
4171
|
-
// Fischer-Yates shuffle
|
|
4172
|
-
for (let top = shuffled.length; top > 0; top--) {
|
|
4173
|
-
const newEnd = Math.floor(random() * top);
|
|
4174
|
-
const temp = shuffled[newEnd];
|
|
4175
|
-
|
|
4176
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
|
|
4177
|
-
shuffled[newEnd] = shuffled[top - 1];
|
|
4178
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
|
|
4179
|
-
shuffled[top - 1] = temp;
|
|
4180
|
-
}
|
|
4181
|
-
} while (ensurePermuted && ___default["default"].isEqual(array, shuffled));
|
|
4182
|
-
return shuffled;
|
|
3139
|
+
});
|
|
4183
3140
|
}
|
|
4184
|
-
const random = seededRNG(new Date().getTime() & 0xffffffff);
|
|
4185
3141
|
|
|
4186
|
-
|
|
4187
|
-
exports.Errors = Errors;
|
|
4188
|
-
exports.GrapherUtil = grapherUtil;
|
|
4189
|
-
exports.ItemExtras = ItemExtras;
|
|
4190
|
-
exports.PerseusError = PerseusError;
|
|
4191
|
-
exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
|
|
4192
|
-
exports.addWidget = addWidget;
|
|
4193
|
-
exports.approximateDeepEqual = approximateDeepEqual;
|
|
4194
|
-
exports.approximateEqual = approximateEqual;
|
|
4195
|
-
exports.categorizerLogic = categorizerWidgetLogic;
|
|
4196
|
-
exports.csProgramLogic = csProgramWidgetLogic;
|
|
4197
|
-
exports.deepClone = deepClone;
|
|
4198
|
-
exports.definitionLogic = definitionWidgetLogic;
|
|
4199
|
-
exports.deriveExtraKeys = deriveExtraKeys;
|
|
4200
|
-
exports.deriveNumCorrect = deriveNumCorrect;
|
|
4201
|
-
exports.dropdownLogic = dropdownWidgetLogic;
|
|
4202
|
-
exports.explanationLogic = explanationWidgetLogic;
|
|
4203
|
-
exports.expressionLogic = expressionWidgetLogic;
|
|
4204
|
-
exports.getCSProgramPublicWidgetOptions = getCSProgramPublicWidgetOptions;
|
|
4205
|
-
exports.getCategorizerPublicWidgetOptions = getCategorizerPublicWidgetOptions;
|
|
4206
|
-
exports.getDecimalSeparator = getDecimalSeparator;
|
|
4207
|
-
exports.getDropdownPublicWidgetOptions = getDropdownPublicWidgetOptions;
|
|
4208
|
-
exports.getExpressionPublicWidgetOptions = getExpressionPublicWidgetOptions;
|
|
4209
|
-
exports.getGrapherPublicWidgetOptions = getGrapherPublicWidgetOptions;
|
|
4210
|
-
exports.getIFramePublicWidgetOptions = getIFramePublicWidgetOptions;
|
|
4211
|
-
exports.getInteractiveGraphPublicWidgetOptions = getInteractiveGraphPublicWidgetOptions;
|
|
4212
|
-
exports.getLabelImagePublicWidgetOptions = getLabelImagePublicWidgetOptions;
|
|
4213
|
-
exports.getMatcherPublicWidgetOptions = getMatcherPublicWidgetOptions;
|
|
4214
|
-
exports.getMatrixPublicWidgetOptions = getMatrixPublicWidgetOptions;
|
|
4215
|
-
exports.getMatrixSize = getMatrixSize;
|
|
4216
|
-
exports.getNumberLinePublicWidgetOptions = getNumberLinePublicWidgetOptions;
|
|
4217
|
-
exports.getNumericInputPublicWidgetOptions = getNumericInputPublicWidgetOptions;
|
|
4218
|
-
exports.getOrdererPublicWidgetOptions = getOrdererPublicWidgetOptions;
|
|
4219
|
-
exports.getPlotterPublicWidgetOptions = getPlotterPublicWidgetOptions;
|
|
4220
|
-
exports.getRadioPublicWidgetOptions = getRadioPublicWidgetOptions;
|
|
4221
|
-
exports.getSorterPublicWidgetOptions = getSorterPublicWidgetOptions;
|
|
4222
|
-
exports.getTablePublicWidgetOptions = getTablePublicWidgetOptions;
|
|
4223
|
-
exports.getUpgradedWidgetOptions = getUpgradedWidgetOptions;
|
|
4224
|
-
exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
|
|
4225
|
-
exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
|
|
4226
|
-
exports.gradedGroupLogic = gradedGroupWidgetLogic;
|
|
4227
|
-
exports.gradedGroupSetLogic = gradedGroupSetWidgetLogic;
|
|
4228
|
-
exports.grapherLogic = grapherWidgetLogic;
|
|
4229
|
-
exports.groupLogic = groupWidgetLogic;
|
|
4230
|
-
exports.iframeLogic = iframeWidgetLogic;
|
|
4231
|
-
exports.imageLogic = imageWidgetLogic;
|
|
4232
|
-
exports.inputNumberLogic = inputNumberWidgetLogic;
|
|
4233
|
-
exports.interactionLogic = interactionWidgetLogic;
|
|
4234
|
-
exports.interactiveGraphLogic = interactiveGraphWidgetLogic;
|
|
4235
|
-
exports.isFailure = isFailure;
|
|
4236
|
-
exports.isSuccess = isSuccess;
|
|
4237
|
-
exports.labelImageLogic = labelImageWidgetLogic;
|
|
4238
|
-
exports.libVersion = libVersion;
|
|
4239
|
-
exports.lockedFigureColorNames = lockedFigureColorNames;
|
|
4240
|
-
exports.lockedFigureColors = lockedFigureColors;
|
|
4241
|
-
exports.lockedFigureFillStyles = lockedFigureFillStyles;
|
|
4242
|
-
exports.mapObject = mapObject;
|
|
4243
|
-
exports.matcherLogic = matcherWidgetLogic;
|
|
4244
|
-
exports.matrixLogic = matrixWidgetLogic;
|
|
4245
|
-
exports.measurerLogic = measurerWidgetLogic;
|
|
4246
|
-
exports.numberLineLogic = numberLineWidgetLogic;
|
|
4247
|
-
exports.numericInputLogic = numericInputWidgetLogic;
|
|
4248
|
-
exports.ordererLogic = ordererWidgetLogic;
|
|
4249
|
-
exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
|
|
4250
|
-
exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
|
|
4251
|
-
exports.parsePerseusItem = parsePerseusItem;
|
|
4252
|
-
exports.passageLogic = passageWidgetLogic;
|
|
4253
|
-
exports.passageRefLogic = passageRefWidgetLogic;
|
|
4254
|
-
exports.passageRefTargetLogic = passageRefTargetWidgetLogic;
|
|
4255
|
-
exports.phetSimulationLogic = phetSimulationWidgetLogic;
|
|
4256
|
-
exports.plotterLogic = plotterWidgetLogic;
|
|
4257
|
-
exports.plotterPlotTypes = plotterPlotTypes;
|
|
4258
|
-
exports.pluck = pluck;
|
|
4259
|
-
exports.pythonProgramLogic = pythonProgramWidgetLogic;
|
|
4260
|
-
exports.radioLogic = radioWidgetLogic;
|
|
4261
|
-
exports.random = random;
|
|
4262
|
-
exports.seededRNG = seededRNG;
|
|
4263
|
-
exports.shuffle = shuffle;
|
|
4264
|
-
exports.shuffleMatcher = shuffleMatcher;
|
|
4265
|
-
exports.sorterLogic = sorterWidgetLogic;
|
|
4266
|
-
exports.splitPerseusItem = splitPerseusItem;
|
|
4267
|
-
exports.tableLogic = tableWidgetLogic;
|
|
4268
|
-
exports.upgradeWidgetInfoToLatestVersion = upgradeWidgetInfoToLatestVersion;
|
|
4269
|
-
exports.usesNumCorrect = usesNumCorrect;
|
|
4270
|
-
exports.videoLogic = videoWidgetLogic;
|
|
3142
|
+
export { coreWidgetRegistry as CoreWidgetRegistry, Errors, grapherUtil as GrapherUtil, ItemExtras, PerseusError, PerseusExpressionAnswerFormConsidered, addWidget, approximateDeepEqual, approximateEqual, categorizerWidgetLogic as categorizerLogic, csProgramWidgetLogic as csProgramLogic, deepClone, definitionWidgetLogic as definitionLogic, deriveExtraKeys, deriveNumCorrect, dropdownWidgetLogic as dropdownLogic, explanationWidgetLogic as explanationLogic, expressionWidgetLogic as expressionLogic, getCSProgramPublicWidgetOptions, getCategorizerPublicWidgetOptions, getDecimalSeparator, getDropdownPublicWidgetOptions, getExpressionPublicWidgetOptions, getGrapherPublicWidgetOptions, getIFramePublicWidgetOptions, getInteractiveGraphPublicWidgetOptions, getLabelImagePublicWidgetOptions, getMatcherPublicWidgetOptions, getMatrixPublicWidgetOptions, getMatrixSize, getNumberLinePublicWidgetOptions, getNumericInputPublicWidgetOptions, getOrdererPublicWidgetOptions, getPlotterPublicWidgetOptions, getRadioPublicWidgetOptions, getSorterPublicWidgetOptions, getTablePublicWidgetOptions, 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, random, seededRNG, shuffle, shuffleMatcher, sorterWidgetLogic as sorterLogic, splitPerseusItem, tableWidgetLogic as tableLogic, upgradeWidgetInfoToLatestVersion, usesNumCorrect, videoWidgetLogic as videoLogic };
|
|
4271
3143
|
//# sourceMappingURL=index.js.map
|