@khanacademy/perseus-core 7.0.0 → 7.0.2

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 CHANGED
@@ -29,580 +29,17 @@ function _interopNamespaceCompat(e) {
29
29
  var ___default = /*#__PURE__*/_interopDefaultCompat(_);
30
30
  var KAS__namespace = /*#__PURE__*/_interopNamespaceCompat(KAS);
31
31
 
32
- function getMatrixSize(matrix) {
33
- 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) => {
38
- let rowWidth = 0;
39
- ___default.default(matrixRow).each((matrixCol, col) => {
40
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
41
- if (matrixCol != null && matrixCol.toString().length) {
42
- rowWidth = col + 1;
43
- }
44
- });
45
-
46
- // Matrix width:
47
- matrixSize[1] = Math.max(matrixSize[1], rowWidth);
48
-
49
- // Matrix height:
50
- if (rowWidth > 0) {
51
- matrixSize[0] = Math.max(matrixSize[0], row + 1);
52
- }
53
- });
54
- return matrixSize;
55
- }
56
-
57
- /**
58
- * Get the character used for separating decimals.
59
- */
60
- const getDecimalSeparator = locale => {
61
- switch (locale) {
62
- // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
63
- // locale properly.
64
- // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
65
- //
66
- // Supported locales in Chrome:
67
- // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
68
- case "ka":
69
- return ",";
70
- default:
71
- const numberWithDecimalSeparator = 1.1;
72
- // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
73
- // support Safari 12.
74
- const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
75
- // 0x661 is ARABIC-INDIC DIGIT ONE
76
- // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
77
- // 0x967 is DEVANAGARI DIGIT ONE
78
- // 0x9e7 is BENGALI/BANGLA DIGIT ONE
79
- .match(/[^\d\u0661\u06F1\u0967\u09e7]/);
80
- return match?.[0] ?? ".";
81
- }
82
- };
83
-
84
- /**
85
- * APPROXIMATE equality on numbers and primitives.
86
- */
87
- function approximateEqual(x, y) {
88
- if (typeof x === "number" && typeof y === "number") {
89
- return Math.abs(x - y) < 1e-9;
90
- }
91
- return x === y;
92
- }
93
-
94
- /**
95
- * Deep APPROXIMATE equality on primitives, numbers, arrays, and objects.
96
- * Recursive.
97
- */
98
- function approximateDeepEqual(x, y) {
99
- if (Array.isArray(x) && Array.isArray(y)) {
100
- if (x.length !== y.length) {
101
- return false;
102
- }
103
- for (let i = 0; i < x.length; i++) {
104
- if (!approximateDeepEqual(x[i], y[i])) {
105
- return false;
106
- }
107
- }
108
- return true;
109
- }
110
- if (Array.isArray(x) || Array.isArray(y)) {
111
- return false;
112
- }
113
- if (typeof x === "function" && typeof y === "function") {
114
- return approximateEqual(x, y);
115
- }
116
- if (typeof x === "function" || typeof y === "function") {
117
- return false;
118
- }
119
- if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
120
- return x === y || ___default.default.all(x, function (v, k) {
121
- // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
122
- return approximateDeepEqual(y[k], v);
123
- }) && ___default.default.all(y, function (v, k) {
124
- // @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
125
- return approximateDeepEqual(x[k], v);
126
- });
127
- }
128
- if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
129
- return false;
130
- }
131
- return approximateEqual(x, y);
132
- }
32
+ function getMatrixSize(matrix){const matrixSize=[1,1];___default.default(matrix).each((matrixRow,row)=>{let rowWidth=0;___default.default(matrixRow).each((matrixCol,col)=>{if(matrixCol!=null&&matrixCol.toString().length){rowWidth=col+1;}});matrixSize[1]=Math.max(matrixSize[1],rowWidth);if(rowWidth>0){matrixSize[0]=Math.max(matrixSize[0],row+1);}});return matrixSize}
133
33
 
134
- /**
135
- * Add a widget placeholder using the widget ID.
136
- * ex. addWidget("radio 1") => "[[☃ radio 1]]"
137
- *
138
- * @param {string} id
139
- * @returns {string}
140
- */
141
- function addWidget(id) {
142
- return `[[☃ ${id}]]`;
143
- }
144
-
145
- /**
146
- * Regex for widget placeholders in a string.
147
- *
148
- * First capture group is the widget ID (ex. 'radio 1')
149
- * Second capture group is the widget type (ex. "radio)
150
- * exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
151
- */
152
- function getWidgetRegex() {
153
- return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
154
- }
155
-
156
- /**
157
- * Extract all widget IDs, which includes the widget type and instance number.
158
- * example output: ['radio 1', 'categorizer 1', 'categorizor 2']
159
- *
160
- * Content should contain Perseus widget placeholders,
161
- * which look like: '[[☃ radio 1]]'.
162
- *
163
- * @param {string} content
164
- * @returns {ReadonlyArray<string>} widgetIds
165
- */
166
- function getWidgetIdsFromContent(content) {
167
- const widgets = [];
168
- const localWidgetRegex = getWidgetRegex();
169
- let match = localWidgetRegex.exec(content);
170
- while (match !== null) {
171
- widgets.push(match[1]);
172
- match = localWidgetRegex.exec(content);
173
- }
174
- return widgets;
175
- }
34
+ const getDecimalSeparator=locale=>{switch(locale){case "ka":return ",";default:const numberWithDecimalSeparator=1.1;const match=new Intl.NumberFormat(locale).format(numberWithDecimalSeparator).match(/[^\d\u0661\u06F1\u0967\u09e7]/);return match?.[0]??"."}};
176
35
 
177
- /**
178
- * Get a list of widget IDs from content,
179
- * but only for specific widget types
180
- *
181
- * @param {string} type the type of widget (ie "radio")
182
- * @param {string} content the string to parse
183
- * @param {PerseusWidgetsMap} widgetMap widget ID to widget map
184
- * @returns {ReadonlyArray<string>} the widget type (ie "radio")
185
- */
186
- function getWidgetIdsFromContentByType(type, content, widgetMap) {
187
- const rv = [];
188
- const widgetIdsInContent = getWidgetIdsFromContent(content);
189
- widgetIdsInContent.forEach(widgetId => {
190
- const widget = widgetMap[widgetId];
191
- if (widget?.type === type) {
192
- rv.push(widgetId);
193
- }
194
- });
195
- return rv;
196
- }
36
+ function approximateEqual(x,y){if(typeof x==="number"&&typeof y==="number"){return Math.abs(x-y)<1e-9}return x===y}function approximateDeepEqual(x,y){if(Array.isArray(x)&&Array.isArray(y)){if(x.length!==y.length){return false}for(let i=0;i<x.length;i++){if(!approximateDeepEqual(x[i],y[i])){return false}}return true}if(Array.isArray(x)||Array.isArray(y)){return false}if(typeof x==="function"&&typeof y==="function"){return approximateEqual(x,y)}if(typeof x==="function"||typeof y==="function"){return false}if(typeof x==="object"&&typeof y==="object"&&!!x&&!!y){return x===y||___default.default.all(x,function(v,k){return approximateDeepEqual(y[k],v)})&&___default.default.all(y,function(v,k){return approximateDeepEqual(x[k],v)})}if(typeof x==="object"&&!!x||typeof y==="object"&&!!y){return false}return approximateEqual(x,y)}
197
37
 
198
- // TODO(benchristel): in the future, we may want to make deepClone work for
199
- // Record<string, Cloneable> as well. Currently, it only does arrays.
38
+ function addWidget(id){return `[[☃ ${id}]]`}function getWidgetRegex(){return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g}function getWidgetIdsFromContent(content){const widgets=[];const localWidgetRegex=getWidgetRegex();let match=localWidgetRegex.exec(content);while(match!==null){widgets.push(match[1]);match=localWidgetRegex.exec(content);}return widgets}function getWidgetIdsFromContentByType(type,content,widgetMap){const rv=[];const widgetIdsInContent=getWidgetIdsFromContent(content);widgetIdsInContent.forEach(widgetId=>{const widget=widgetMap[widgetId];if(widget?.type===type){rv.push(widgetId);}});return rv}
200
39
 
201
- function deepClone(obj) {
202
- if (Array.isArray(obj)) {
203
- return obj.map(deepClone);
204
- }
205
- return obj;
206
- }
207
-
208
- const MOVABLES = {
209
- PLOT: "PLOT",
210
- PARABOLA: "PARABOLA",
211
- SINUSOID: "SINUSOID"
212
- };
213
-
214
- // TODO(charlie): These really need to go into a utility file as they're being
215
- // used by both interactive-graph and now grapher.
216
- function canonicalSineCoefficients(coeffs) {
217
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
218
- // this function ensures that a, b > 0, and c is its
219
- // smallest possible positive value.
220
- let amplitude = coeffs[0];
221
- let angularFrequency = coeffs[1];
222
- let phase = coeffs[2];
223
- const verticalOffset = coeffs[3];
224
-
225
- // Guarantee a > 0
226
- if (amplitude < 0) {
227
- amplitude *= -1;
228
- angularFrequency *= -1;
229
- phase *= -1;
230
- }
231
- const period = 2 * Math.PI;
232
- // Guarantee b > 0
233
- if (angularFrequency < 0) {
234
- angularFrequency *= -1;
235
- phase *= -1;
236
- phase += period / 2;
237
- }
238
-
239
- // Guarantee c is smallest possible positive value
240
- while (phase > 0) {
241
- phase -= period;
242
- }
243
- while (phase < 0) {
244
- phase += period;
245
- }
246
- return [amplitude, angularFrequency, phase, verticalOffset];
247
- }
248
- function canonicalTangentCoefficients(coeffs) {
249
- // For a curve of the form f(x) = a * Tan(b * x - c) + d,
250
- // this function ensures that a, b > 0, and c is its
251
- // smallest possible positive value.
252
- let amplitude = coeffs[0];
253
- let angularFrequency = coeffs[1];
254
- let phase = coeffs[2];
255
- const verticalOffset = coeffs[3];
256
-
257
- // Guarantee a > 0
258
- if (amplitude < 0) {
259
- amplitude *= -1;
260
- angularFrequency *= -1;
261
- phase *= -1;
262
- }
263
- const period = Math.PI;
264
- // Guarantee b > 0
265
- if (angularFrequency < 0) {
266
- angularFrequency *= -1;
267
- phase *= -1;
268
- phase += period / 2;
269
- }
270
-
271
- // Guarantee c is smallest possible positive value
272
- while (phase > 0) {
273
- phase -= period;
274
- }
275
- while (phase < 0) {
276
- phase += period;
277
- }
278
- return [amplitude, angularFrequency, phase, verticalOffset];
279
- }
280
- const PlotDefaults = {
281
- areEqual: function (coeffs1, coeffs2) {
282
- return approximateDeepEqual(coeffs1, coeffs2);
283
- },
284
- movable: MOVABLES.PLOT,
285
- getPropsForCoeffs: function (coeffs) {
286
- return {
287
- // @ts-expect-error - TS2339 - Property 'getFunctionForCoeffs' does not exist on type '{ readonly areEqual: (coeffs1: any, coeffs2: any) => boolean; readonly Movable: any; readonly getPropsForCoeffs: (coeffs: any) => any; }'.
288
- fn: ___default.default.partial(this.getFunctionForCoeffs, coeffs)
289
- };
290
- }
291
- };
292
- const Linear = ___default.default.extend({}, PlotDefaults, {
293
- url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
294
- defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
295
- getCoefficients: function (coords) {
296
- const p1 = coords[0];
297
- const p2 = coords[1];
298
- const denom = p2[0] - p1[0];
299
- const num = p2[1] - p1[1];
300
- if (denom === 0) {
301
- return;
302
- }
303
- const m = num / denom;
304
- const b = p2[1] - m * p2[0];
305
- return [m, b];
306
- },
307
- getFunctionForCoeffs: function (coeffs, x) {
308
- const m = coeffs[0];
309
- const b = coeffs[1];
310
- return m * x + b;
311
- },
312
- getEquationString: function (coords) {
313
- const coeffs = this.getCoefficients(coords);
314
- const m = coeffs[0];
315
- const b = coeffs[1];
316
- return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
317
- }
318
- });
319
- const Quadratic = ___default.default.extend({}, PlotDefaults, {
320
- url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
321
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
322
- movable: MOVABLES.PARABOLA,
323
- getCoefficients: function (coords) {
324
- const p1 = coords[0];
325
- const p2 = coords[1];
326
-
327
- // Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
328
- const h = p1[0];
329
- const k = p1[1];
330
-
331
- // Use these to calculate familiar a, b, c
332
- const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
333
- const b = -2 * h * a;
334
- const c = a * h * h + k;
335
- return [a, b, c];
336
- },
337
- getFunctionForCoeffs: function (coeffs, x) {
338
- const a = coeffs[0];
339
- const b = coeffs[1];
340
- const c = coeffs[2];
341
- return (a * x + b) * x + c;
342
- },
343
- getPropsForCoeffs: function (coeffs) {
344
- return {
345
- a: coeffs[0],
346
- b: coeffs[1],
347
- c: coeffs[2]
348
- };
349
- },
350
- getEquationString: function (coords) {
351
- const coeffs = this.getCoefficients(coords);
352
- const a = coeffs[0];
353
- const b = coeffs[1];
354
- const c = coeffs[2];
355
- return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
356
- }
357
- });
358
- const Sinusoid = ___default.default.extend({}, PlotDefaults, {
359
- url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
360
- defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
361
- movable: MOVABLES.SINUSOID,
362
- getCoefficients: function (coords) {
363
- const p1 = coords[0];
364
- const p2 = coords[1];
365
- const a = p2[1] - p1[1];
366
- const b = Math.PI / (2 * (p2[0] - p1[0]));
367
- const c = p1[0] * b;
368
- const d = p1[1];
369
- return [a, b, c, d];
370
- },
371
- getFunctionForCoeffs: function (coeffs, x) {
372
- const a = coeffs[0];
373
- const b = coeffs[1];
374
- const c = coeffs[2];
375
- const d = coeffs[3];
376
- return a * Math.sin(b * x - c) + d;
377
- },
378
- getPropsForCoeffs: function (coeffs) {
379
- return {
380
- a: coeffs[0],
381
- b: coeffs[1],
382
- c: coeffs[2],
383
- d: coeffs[3]
384
- };
385
- },
386
- getEquationString: function (coords) {
387
- const coeffs = this.getCoefficients(coords);
388
- const a = coeffs[0];
389
- const b = coeffs[1];
390
- const c = coeffs[2];
391
- const d = coeffs[3];
392
- return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
393
- },
394
- areEqual: function (coeffs1, coeffs2) {
395
- return approximateDeepEqual(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
396
- }
397
- });
398
- const Tangent = ___default.default.extend({}, PlotDefaults, {
399
- url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
400
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
401
- getCoefficients: function (coords) {
402
- const p1 = coords[0];
403
- const p2 = coords[1];
404
- const a = p2[1] - p1[1];
405
- const b = Math.PI / (4 * (p2[0] - p1[0]));
406
- const c = p1[0] * b;
407
- const d = p1[1];
408
- return [a, b, c, d];
409
- },
410
- getFunctionForCoeffs: function (coeffs, x) {
411
- const a = coeffs[0];
412
- const b = coeffs[1];
413
- const c = coeffs[2];
414
- const d = coeffs[3];
415
- return a * Math.tan(b * x - c) + d;
416
- },
417
- getEquationString: function (coords) {
418
- const coeffs = this.getCoefficients(coords);
419
- const a = coeffs[0];
420
- const b = coeffs[1];
421
- const c = coeffs[2];
422
- const d = coeffs[3];
423
- return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
424
- },
425
- areEqual: function (coeffs1, coeffs2) {
426
- return approximateDeepEqual(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
427
- }
428
- });
429
- const Exponential = ___default.default.extend({}, PlotDefaults, {
430
- url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
431
- defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
432
- defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
433
- /**
434
- * Add extra constraints for movement of the points or asymptote (below):
435
- * newCoord: [x, y]
436
- * The end position of the point or asymptote endpoint
437
- * oldCoord: [x, y]
438
- * The old position of the point or asymptote endpoint
439
- * coords:
440
- * An array of coordinates representing the proposed end configuration
441
- * of the plot coordinates.
442
- * asymptote:
443
- * An array of coordinates representing the proposed end configuration
444
- * of the asymptote.
445
- *
446
- * Return: either a coordinate (to be used as the resulting coordinate of
447
- * the move) or a boolean, where `true` uses newCoord as the resulting
448
- * coordinate, and `false` uses oldCoord as the resulting coordinate.
449
- */
450
- extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
451
- const y = asymptote[0][1];
452
- return ___default.default.all(coords, coord => coord[1] !== y);
453
- },
454
- extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
455
- const y = newCoord[1];
456
- const isValid = ___default.default.all(coords, coord => coord[1] > y) || ___default.default.all(coords, coord => coord[1] < y);
457
- if (isValid) {
458
- return [oldCoord[0], y];
459
- }
460
- // Snap the asymptote as close as possible, i.e., if the user moves
461
- // the mouse really quickly into an invalid region
462
- const oldY = oldCoord[1];
463
- const wasBelow = ___default.default.all(coords, coord => coord[1] > oldY);
464
- if (wasBelow) {
465
- const bottomMost = ___default.default.min(___default.default.map(coords, coord => coord[1]));
466
- return [oldCoord[0], bottomMost - graph.snapStep[1]];
467
- }
468
- const topMost = ___default.default.max(___default.default.map(coords, coord => coord[1]));
469
- return [oldCoord[0], topMost + graph.snapStep[1]];
470
- },
471
- allowReflectOverAsymptote: true,
472
- getCoefficients: function (coords, asymptote) {
473
- const p1 = coords[0];
474
- const p2 = coords[1];
475
- const c = asymptote[0][1];
476
- const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
477
- const a = (p1[1] - c) / Math.exp(b * p1[0]);
478
- return [a, b, c];
479
- },
480
- getFunctionForCoeffs: function (coeffs, x) {
481
- const a = coeffs[0];
482
- const b = coeffs[1];
483
- const c = coeffs[2];
484
- return a * Math.exp(b * x) + c;
485
- },
486
- getEquationString: function (coords, asymptote) {
487
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
488
- if (!asymptote) {
489
- return null;
490
- }
491
- const coeffs = this.getCoefficients(coords, asymptote);
492
- const a = coeffs[0];
493
- const b = coeffs[1];
494
- const c = coeffs[2];
495
- return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
496
- }
497
- });
498
- const Logarithm = ___default.default.extend({}, PlotDefaults, {
499
- url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
500
- defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
501
- defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
502
- extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
503
- const x = asymptote[0][0];
504
- return ___default.default.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
505
- },
506
- extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
507
- const x = newCoord[0];
508
- const isValid = ___default.default.all(coords, coord => coord[0] > x) || ___default.default.all(coords, coord => coord[0] < x);
509
- if (isValid) {
510
- return [x, oldCoord[1]];
511
- }
512
- // Snap the asymptote as close as possible, i.e., if the user moves
513
- // the mouse really quickly into an invalid region
514
- const oldX = oldCoord[0];
515
- const wasLeft = ___default.default.all(coords, coord => coord[0] > oldX);
516
- if (wasLeft) {
517
- const leftMost = ___default.default.min(___default.default.map(coords, coord => coord[0]));
518
- return [leftMost - graph.snapStep[0], oldCoord[1]];
519
- }
520
- const rightMost = ___default.default.max(___default.default.map(coords, coord => coord[0]));
521
- return [rightMost + graph.snapStep[0], oldCoord[1]];
522
- },
523
- allowReflectOverAsymptote: true,
524
- getCoefficients: function (coords, asymptote) {
525
- // It's easiest to calculate the logarithm's coefficients by thinking
526
- // about it as the inverse of the exponential, so we flip x and y and
527
- // perform some algebra on the coefficients. This also unifies the
528
- // logic between the two 'models'.
529
- const flip = coord => [coord[1], coord[0]];
530
- const inverseCoeffs = Exponential.getCoefficients(___default.default.map(coords, flip), ___default.default.map(asymptote, flip));
531
- if (inverseCoeffs) {
532
- const c = -inverseCoeffs[2] / inverseCoeffs[0];
533
- const b = 1 / inverseCoeffs[0];
534
- const a = 1 / inverseCoeffs[1];
535
- return [a, b, c];
536
- }
537
- },
538
- getFunctionForCoeffs: function (coeffs, x, asymptote) {
539
- const a = coeffs[0];
540
- const b = coeffs[1];
541
- const c = coeffs[2];
542
- return a * Math.log(b * x + c);
543
- },
544
- getEquationString: function (coords, asymptote) {
545
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
546
- if (!asymptote) {
547
- return null;
548
- }
549
- const coeffs = this.getCoefficients(coords, asymptote);
550
- const a = coeffs[0];
551
- const b = coeffs[1];
552
- const c = coeffs[2];
553
- return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
554
- }
555
- });
556
- const AbsoluteValue = ___default.default.extend({}, PlotDefaults, {
557
- url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
558
- defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
559
- getCoefficients: function (coords) {
560
- const p1 = coords[0];
561
- const p2 = coords[1];
562
- const denom = p2[0] - p1[0];
563
- const num = p2[1] - p1[1];
564
- if (denom === 0) {
565
- return;
566
- }
567
- let m = Math.abs(num / denom);
568
- if (p2[1] < p1[1]) {
569
- m *= -1;
570
- }
571
- const horizontalOffset = p1[0];
572
- const verticalOffset = p1[1];
573
- return [m, horizontalOffset, verticalOffset];
574
- },
575
- getFunctionForCoeffs: function (coeffs, x) {
576
- const m = coeffs[0];
577
- const horizontalOffset = coeffs[1];
578
- const verticalOffset = coeffs[2];
579
- return m * Math.abs(x - horizontalOffset) + verticalOffset;
580
- },
581
- getEquationString: function (coords) {
582
- const coeffs = this.getCoefficients(coords);
583
- const m = coeffs[0];
584
- const horizontalOffset = coeffs[1];
585
- const verticalOffset = coeffs[2];
586
- return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
587
- }
588
- });
40
+ function deepClone(obj){if(Array.isArray(obj)){return obj.map(deepClone)}return obj}
589
41
 
590
- /* Utility functions for dealing with graphing interfaces. */
591
- const functionTypeMapping = {
592
- linear: Linear,
593
- quadratic: Quadratic,
594
- sinusoid: Sinusoid,
595
- tangent: Tangent,
596
- exponential: Exponential,
597
- logarithm: Logarithm,
598
- absolute_value: AbsoluteValue
599
- };
600
- const allTypes = ___default.default.keys(functionTypeMapping);
601
- function functionForType(type) {
602
- // @ts-expect-error: TypeScript doesn't know how to use deal with generics
603
- // and conditional types in this way.
604
- return functionTypeMapping[type];
605
- }
42
+ const MOVABLES={PLOT:"PLOT",PARABOLA:"PARABOLA",SINUSOID:"SINUSOID"};function canonicalSineCoefficients(coeffs){let amplitude=coeffs[0];let angularFrequency=coeffs[1];let phase=coeffs[2];const verticalOffset=coeffs[3];if(amplitude<0){amplitude*=-1;angularFrequency*=-1;phase*=-1;}const period=2*Math.PI;if(angularFrequency<0){angularFrequency*=-1;phase*=-1;phase+=period/2;}while(phase>0){phase-=period;}while(phase<0){phase+=period;}return [amplitude,angularFrequency,phase,verticalOffset]}function canonicalTangentCoefficients(coeffs){let amplitude=coeffs[0];let angularFrequency=coeffs[1];let phase=coeffs[2];const verticalOffset=coeffs[3];if(amplitude<0){amplitude*=-1;angularFrequency*=-1;phase*=-1;}const period=Math.PI;if(angularFrequency<0){angularFrequency*=-1;phase*=-1;phase+=period/2;}while(phase>0){phase-=period;}while(phase<0){phase+=period;}return [amplitude,angularFrequency,phase,verticalOffset]}const PlotDefaults={areEqual:function(coeffs1,coeffs2){return approximateDeepEqual(coeffs1,coeffs2)},movable:MOVABLES.PLOT,getPropsForCoeffs:function(coeffs){return {fn:___default.default.partial(this.getFunctionForCoeffs,coeffs)}}};const Linear=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",defaultCoords:[[.25,.75],[.75,.75]],getCoefficients:function(coords){const p1=coords[0];const p2=coords[1];const denom=p2[0]-p1[0];const num=p2[1]-p1[1];if(denom===0){return}const m=num/denom;const b=p2[1]-m*p2[0];return [m,b]},getFunctionForCoeffs:function(coeffs,x){const m=coeffs[0];const b=coeffs[1];return m*x+b},getEquationString:function(coords){const coeffs=this.getCoefficients(coords);const m=coeffs[0];const b=coeffs[1];return "y = "+m.toFixed(3)+"x + "+b.toFixed(3)}});const Quadratic=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",defaultCoords:[[.5,.5],[.75,.75]],movable:MOVABLES.PARABOLA,getCoefficients:function(coords){const p1=coords[0];const p2=coords[1];const h=p1[0];const k=p1[1];const a=(p2[1]-k)/((p2[0]-h)*(p2[0]-h));const b=-2*h*a;const c=a*h*h+k;return [a,b,c]},getFunctionForCoeffs:function(coeffs,x){const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return (a*x+b)*x+c},getPropsForCoeffs:function(coeffs){return {a:coeffs[0],b:coeffs[1],c:coeffs[2]}},getEquationString:function(coords){const coeffs=this.getCoefficients(coords);const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return "y = "+a.toFixed(3)+"x^2 + "+b.toFixed(3)+"x + "+c.toFixed(3)}});const Sinusoid=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",defaultCoords:[[.5,.5],[.6,.6]],movable:MOVABLES.SINUSOID,getCoefficients:function(coords){const p1=coords[0];const p2=coords[1];const a=p2[1]-p1[1];const b=Math.PI/(2*(p2[0]-p1[0]));const c=p1[0]*b;const d=p1[1];return [a,b,c,d]},getFunctionForCoeffs:function(coeffs,x){const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];const d=coeffs[3];return a*Math.sin(b*x-c)+d},getPropsForCoeffs:function(coeffs){return {a:coeffs[0],b:coeffs[1],c:coeffs[2],d:coeffs[3]}},getEquationString:function(coords){const coeffs=this.getCoefficients(coords);const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];const d=coeffs[3];return "y = "+a.toFixed(3)+" sin("+b.toFixed(3)+"x - "+c.toFixed(3)+") + "+d.toFixed(3)},areEqual:function(coeffs1,coeffs2){return approximateDeepEqual(canonicalSineCoefficients(coeffs1),canonicalSineCoefficients(coeffs2))}});const Tangent=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",defaultCoords:[[.5,.5],[.75,.75]],getCoefficients:function(coords){const p1=coords[0];const p2=coords[1];const a=p2[1]-p1[1];const b=Math.PI/(4*(p2[0]-p1[0]));const c=p1[0]*b;const d=p1[1];return [a,b,c,d]},getFunctionForCoeffs:function(coeffs,x){const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];const d=coeffs[3];return a*Math.tan(b*x-c)+d},getEquationString:function(coords){const coeffs=this.getCoefficients(coords);const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];const d=coeffs[3];return "y = "+a.toFixed(3)+" sin("+b.toFixed(3)+"x - "+c.toFixed(3)+") + "+d.toFixed(3)},areEqual:function(coeffs1,coeffs2){return approximateDeepEqual(canonicalTangentCoefficients(coeffs1),canonicalTangentCoefficients(coeffs2))}});const Exponential=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",defaultCoords:[[.5,.55],[.75,.75]],defaultAsymptote:[[0,.5],[1,.5]],extraCoordConstraint:function(newCoord,oldCoord,coords,asymptote,graph){const y=asymptote[0][1];return ___default.default.all(coords,coord=>coord[1]!==y)},extraAsymptoteConstraint:function(newCoord,oldCoord,coords,asymptote,graph){const y=newCoord[1];const isValid=___default.default.all(coords,coord=>coord[1]>y)||___default.default.all(coords,coord=>coord[1]<y);if(isValid){return [oldCoord[0],y]}const oldY=oldCoord[1];const wasBelow=___default.default.all(coords,coord=>coord[1]>oldY);if(wasBelow){const bottomMost=___default.default.min(___default.default.map(coords,coord=>coord[1]));return [oldCoord[0],bottomMost-graph.snapStep[1]]}const topMost=___default.default.max(___default.default.map(coords,coord=>coord[1]));return [oldCoord[0],topMost+graph.snapStep[1]]},allowReflectOverAsymptote:true,getCoefficients:function(coords,asymptote){const p1=coords[0];const p2=coords[1];const c=asymptote[0][1];const b=Math.log((p1[1]-c)/(p2[1]-c))/(p1[0]-p2[0]);const a=(p1[1]-c)/Math.exp(b*p1[0]);return [a,b,c]},getFunctionForCoeffs:function(coeffs,x){const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return a*Math.exp(b*x)+c},getEquationString:function(coords,asymptote){if(!asymptote){return null}const coeffs=this.getCoefficients(coords,asymptote);const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return "y = "+a.toFixed(3)+"e^("+b.toFixed(3)+"x) + "+c.toFixed(3)}});const Logarithm=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",defaultCoords:[[.55,.5],[.75,.75]],defaultAsymptote:[[.5,0],[.5,1]],extraCoordConstraint:function(newCoord,oldCoord,coords,asymptote,graph){const x=asymptote[0][0];return ___default.default.all(coords,coord=>coord[0]!==x)&&coords[0][1]!==coords[1][1]},extraAsymptoteConstraint:function(newCoord,oldCoord,coords,asymptote,graph){const x=newCoord[0];const isValid=___default.default.all(coords,coord=>coord[0]>x)||___default.default.all(coords,coord=>coord[0]<x);if(isValid){return [x,oldCoord[1]]}const oldX=oldCoord[0];const wasLeft=___default.default.all(coords,coord=>coord[0]>oldX);if(wasLeft){const leftMost=___default.default.min(___default.default.map(coords,coord=>coord[0]));return [leftMost-graph.snapStep[0],oldCoord[1]]}const rightMost=___default.default.max(___default.default.map(coords,coord=>coord[0]));return [rightMost+graph.snapStep[0],oldCoord[1]]},allowReflectOverAsymptote:true,getCoefficients:function(coords,asymptote){const flip=coord=>[coord[1],coord[0]];const inverseCoeffs=Exponential.getCoefficients(___default.default.map(coords,flip),___default.default.map(asymptote,flip));if(inverseCoeffs){const c=-inverseCoeffs[2]/inverseCoeffs[0];const b=1/inverseCoeffs[0];const a=1/inverseCoeffs[1];return [a,b,c]}},getFunctionForCoeffs:function(coeffs,x,asymptote){const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return a*Math.log(b*x+c)},getEquationString:function(coords,asymptote){if(!asymptote){return null}const coeffs=this.getCoefficients(coords,asymptote);const a=coeffs[0];const b=coeffs[1];const c=coeffs[2];return "y = ln("+a.toFixed(3)+"x + "+b.toFixed(3)+") + "+c.toFixed(3)}});const AbsoluteValue=___default.default.extend({},PlotDefaults,{url:"https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",defaultCoords:[[.5,.5],[.75,.75]],getCoefficients:function(coords){const p1=coords[0];const p2=coords[1];const denom=p2[0]-p1[0];const num=p2[1]-p1[1];if(denom===0){return}let m=Math.abs(num/denom);if(p2[1]<p1[1]){m*=-1;}const horizontalOffset=p1[0];const verticalOffset=p1[1];return [m,horizontalOffset,verticalOffset]},getFunctionForCoeffs:function(coeffs,x){const m=coeffs[0];const horizontalOffset=coeffs[1];const verticalOffset=coeffs[2];return m*Math.abs(x-horizontalOffset)+verticalOffset},getEquationString:function(coords){const coeffs=this.getCoefficients(coords);const m=coeffs[0];const horizontalOffset=coeffs[1];const verticalOffset=coeffs[2];return "y = "+m.toFixed(3)+"| x - "+horizontalOffset.toFixed(3)+"| + "+verticalOffset.toFixed(3)}});const functionTypeMapping={linear:Linear,quadratic:Quadratic,sinusoid:Sinusoid,tangent:Tangent,exponential:Exponential,logarithm:Logarithm,absolute_value:AbsoluteValue};const allTypes=___default.default.keys(functionTypeMapping);function functionForType(type){return functionTypeMapping[type]}
606
43
 
607
44
  var grapherUtil = /*#__PURE__*/Object.freeze({
608
45
  __proto__: null,
@@ -611,3386 +48,269 @@ var grapherUtil = /*#__PURE__*/Object.freeze({
611
48
  functionForType: functionForType
612
49
  });
613
50
 
614
- function isRealJSONParse(jsonParse) {
615
- const randomPhrase = buildRandomPhrase();
616
- const randomHintPhrase = buildRandomPhrase();
617
- const randomString = buildRandomString();
618
- const testingObject = JSON.stringify({
619
- answerArea: {
620
- calculator: false,
621
- chi2Table: false,
622
- financialCalculatorMonthlyPayment: false,
623
- financialCalculatorTimeToPayOff: false,
624
- financialCalculatorTotalAmount: false,
625
- periodicTable: false,
626
- periodicTableWithKey: false,
627
- tTable: false,
628
- zTable: false
629
- },
630
- hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
631
- itemDataVersion: {
632
- major: 0,
633
- minor: 1
634
- },
635
- question: {
636
- content: `${randomPhrase}`,
637
- images: {},
638
- widgets: {
639
- expression1: {
640
- alignment: "default",
641
- graded: false,
642
- options: {
643
- answerForms: [{
644
- considered: "wrong",
645
- form: false,
646
- key: 0,
647
- simplify: false,
648
- value: `${randomString}`
649
- }],
650
- ariaLabel: "Answer",
651
- buttonSets: ["basic"],
652
- functions: ["f", "g", "h"],
653
- static: true,
654
- times: false,
655
- visibleLabel: "Answer"
656
- },
657
- static: true,
658
- type: "expression",
659
- version: {
660
- major: 1,
661
- minor: 0
662
- }
663
- }
664
- }
665
- }
666
- });
667
- const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
668
- const parsedTestJSON = jsonParse(testJSON);
669
- const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
670
- return approximateDeepEqual(parsedTestItemData, testingObject);
671
- }
672
- function buildRandomString() {
673
- let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
674
- let randomString = "";
675
- const randomLength = Math.floor(Math.random() * 8) + 3;
676
- for (let i = 0; i < randomLength; i++) {
677
- const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
678
- randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
679
- }
680
- return randomString;
681
- }
682
- function buildRandomPhrase() {
683
- const phrases = [];
684
- const randomLength = Math.floor(Math.random() * 10) + 5;
685
- for (let i = 0; i < randomLength; i++) {
686
- phrases.push(buildRandomString(i === 0));
687
- }
688
- const modifierStart = ["**", "$"];
689
- const modifierEnd = ["**", "$"];
690
- const modifierIndex = Math.floor(Math.random() * modifierStart.length);
691
- return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
692
- }
693
- function buildTestData(testObject) {
694
- return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
695
- }
51
+ function isRealJSONParse(jsonParse){const randomPhrase=buildRandomPhrase();const randomHintPhrase=buildRandomPhrase();const randomString=buildRandomString();const testingObject=JSON.stringify({answerArea:{calculator:false,chi2Table:false,financialCalculatorMonthlyPayment:false,financialCalculatorTimeToPayOff:false,financialCalculatorTotalAmount:false,periodicTable:false,periodicTableWithKey:false,tTable:false,zTable:false},hints:[randomHintPhrase,`=${Math.floor(Math.random()*50)+1}`],itemDataVersion:{major:0,minor:1},question:{content:`${randomPhrase}`,images:{},widgets:{expression1:{alignment:"default",graded:false,options:{answerForms:[{considered:"wrong",form:false,key:0,simplify:false,value:`${randomString}`}],ariaLabel:"Answer",buttonSets:["basic"],functions:["f","g","h"],static:true,times:false,visibleLabel:"Answer"},static:true,type:"expression",version:{major:1,minor:0}}}}});const testJSON=buildTestData(testingObject.replace(/"/g,'\\"'));const parsedTestJSON=jsonParse(testJSON);const parsedTestItemData=parsedTestJSON.data.assessmentItem.item.itemData;return approximateDeepEqual(parsedTestItemData,testingObject)}function buildRandomString(capitalize=false){let randomString="";const randomLength=Math.floor(Math.random()*8)+3;for(let i=0;i<randomLength;i++){const randomLetter=String.fromCharCode(97+Math.floor(Math.random()*26));randomString+=capitalize&&i===0?randomLetter.toUpperCase():randomLetter;}return randomString}function buildRandomPhrase(){const phrases=[];const randomLength=Math.floor(Math.random()*10)+5;for(let i=0;i<randomLength;i++){phrases.push(buildRandomString(i===0));}const modifierStart=["**","$"];const modifierEnd=["**","$"];const modifierIndex=Math.floor(Math.random()*modifierStart.length);return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`}function buildTestData(testObject){return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`}
696
52
 
697
- function success(value) {
698
- return {
699
- type: "success",
700
- value
701
- };
702
- }
703
- function failure(detail) {
704
- return {
705
- type: "failure",
706
- detail
707
- };
708
- }
709
- function isFailure(result) {
710
- return result.type === "failure";
711
- }
712
- function isSuccess(result) {
713
- return result.type === "success";
714
- }
53
+ function success(value){return {type:"success",value}}function failure(detail){return {type:"failure",detail}}function isFailure(result){return result.type==="failure"}function isSuccess(result){return result.type==="success"}function all(results,combineFailureDetails=a=>a){const values=[];const failureDetails=[];for(const result of results){if(result.type==="success"){values.push(result.value);}else {failureDetails.push(result.detail);}}if(failureDetails.length>0){return failure(failureDetails.reduce(combineFailureDetails))}return success(values)}
715
54
 
716
- // Result's `all` function is similar to Promise.all: given an array of
717
- // results, it returns success if all succeeded, and failure if any failed.
718
- function all(results) {
719
- let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
720
- const values = [];
721
- const failureDetails = [];
722
- for (const result of results) {
723
- if (result.type === "success") {
724
- values.push(result.value);
725
- } else {
726
- failureDetails.push(result.detail);
727
- }
728
- }
729
- if (failureDetails.length > 0) {
730
- return failure(failureDetails.reduce(combineFailureDetails));
731
- }
732
- return success(values);
733
- }
55
+ class ErrorTrackingParseContext{failure(expected,badValue){return failure([{expected:wrapInArray(expected),badValue,path:this.path}])}forSubtree(key){return new ErrorTrackingParseContext([...this.path,key])}success(value){return success(value)}constructor(path){this.path=path;}}function wrapInArray(a){return Array.isArray(a)?a:[a]}
734
56
 
735
- class ErrorTrackingParseContext {
736
- constructor(path) {
737
- this.path = path;
738
- }
739
- failure(expected, badValue) {
740
- return failure([{
741
- expected: wrapInArray(expected),
742
- badValue,
743
- path: this.path
744
- }]);
745
- }
746
- forSubtree(key) {
747
- return new ErrorTrackingParseContext([...this.path, key]);
748
- }
749
- success(value) {
750
- return success(value);
751
- }
752
- }
753
- function wrapInArray(a) {
754
- return Array.isArray(a) ? a : [a];
755
- }
57
+ function formatPath(path){return "(root)"+path.map(formatPathSegment).join("")}function formatPathSegment(segment){if(typeof segment==="string"){return validIdentifier.test(segment)?"."+segment:`[${JSON.stringify(segment)}]`}return `[${segment.toString()}]`}const validIdentifier=/^[A-Za-z$_][A-Za-z$_0-9]*$/;
756
58
 
757
- function formatPath(path) {
758
- return "(root)" + path.map(formatPathSegment).join("");
759
- }
760
- function formatPathSegment(segment) {
761
- if (typeof segment === "string") {
762
- return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
763
- }
764
- return `[${segment.toString()}]`;
765
- }
766
- const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
59
+ function message(failure){const expected=conjoin(failure.expected);const path=formatPath(failure.path);const badValue=JSON.stringify(failure.badValue);return `At ${path} -- expected ${expected}, but got ${badValue}`}function conjoin(items){switch(items.length){case 1:return items[0];case 2:return items.join(" or ");default:{const allButLast=items.slice(0,items.length-1);const last=items[items.length-1];return allButLast.join(", ")+", or "+last}}}
767
60
 
768
- function message(failure) {
769
- const expected = conjoin(failure.expected);
770
- const path = formatPath(failure.path);
771
- const badValue = JSON.stringify(failure.badValue);
772
- return `At ${path} -- expected ${expected}, but got ${badValue}`;
773
- }
774
- function conjoin(items) {
775
- switch (items.length) {
776
- // TODO(benchristel): handle 0 if this is reused elsewhere.
777
- case 1:
778
- return items[0];
779
- case 2:
780
- return items.join(" or ");
781
- default:
782
- {
783
- const allButLast = items.slice(0, items.length - 1);
784
- const last = items[items.length - 1];
785
- return allButLast.join(", ") + ", or " + last;
786
- }
787
- }
788
- }
61
+ function parse(value,parser){const result=parser(value,new ErrorTrackingParseContext([]));if(isFailure(result)){return failure(result.detail.map(message).join("; "))}return result}
789
62
 
790
- function parse(value, parser) {
791
- const result = parser(value, new ErrorTrackingParseContext([]));
792
- if (isFailure(result)) {
793
- return failure(result.detail.map(message).join("; "));
794
- }
795
- return result;
796
- }
63
+ const any=(rawValue,ctx)=>ctx.success(rawValue);
797
64
 
798
- const any = (rawValue, ctx) => ctx.success(rawValue);
65
+ function array(elementParser){return (rawValue,ctx)=>{if(!Array.isArray(rawValue)){return ctx.failure("array",rawValue)}const elementResults=rawValue.map((elem,i)=>elementParser(elem,ctx.forSubtree(i)));return all(elementResults,concat)}}function concat(a,b){return [...a,...b]}
799
66
 
800
- function array(elementParser) {
801
- return (rawValue, ctx) => {
802
- if (!Array.isArray(rawValue)) {
803
- return ctx.failure("array", rawValue);
804
- }
805
- const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
806
- return all(elementResults, concat);
807
- };
808
- }
809
- function concat(a, b) {
810
- return [...a, ...b];
811
- }
67
+ function boolean(rawValue,ctx){if(typeof rawValue==="boolean"){return ctx.success(rawValue)}return ctx.failure("boolean",rawValue)}
812
68
 
813
- function boolean(rawValue, ctx) {
814
- if (typeof rawValue === "boolean") {
815
- return ctx.success(rawValue);
816
- }
817
- return ctx.failure("boolean", rawValue);
818
- }
69
+ function constant(acceptedValue){return (rawValue,ctx)=>{if(rawValue!==acceptedValue){return ctx.failure(String(JSON.stringify(acceptedValue)),rawValue)}return ctx.success(acceptedValue)}}
819
70
 
820
- function constant(acceptedValue) {
821
- return (rawValue, ctx) => {
822
- if (rawValue !== acceptedValue) {
823
- return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
824
- }
825
- return ctx.success(acceptedValue);
826
- };
827
- }
71
+ function enumeration(...acceptedValues){return (rawValue,ctx)=>{if(typeof rawValue==="string"){const index=acceptedValues.indexOf(rawValue);if(index>-1){return ctx.success(acceptedValues[index])}}const expected=acceptedValues.map(v=>JSON.stringify(v));return ctx.failure(expected,rawValue)}}
828
72
 
829
- function enumeration() {
830
- for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
831
- acceptedValues[_key] = arguments[_key];
832
- }
833
- return (rawValue, ctx) => {
834
- if (typeof rawValue === "string") {
835
- const index = acceptedValues.indexOf(rawValue);
836
- if (index > -1) {
837
- return ctx.success(acceptedValues[index]);
838
- }
839
- }
840
- const expected = acceptedValues.map(v => JSON.stringify(v));
841
- return ctx.failure(expected, rawValue);
842
- };
843
- }
73
+ function isObject(x){return x!=null&&Object.getPrototypeOf(x)===Object.prototype}
844
74
 
845
- function isObject(x) {
846
- return x != null && Object.getPrototypeOf(x) === Object.prototype;
847
- }
75
+ function nullable(parseValue){return (rawValue,ctx)=>{if(rawValue===null){return ctx.success(rawValue)}return parseValue(rawValue,ctx)}}
848
76
 
849
- function nullable(parseValue) {
850
- return (rawValue, ctx) => {
851
- if (rawValue === null) {
852
- return ctx.success(rawValue);
853
- }
854
- return parseValue(rawValue, ctx);
855
- };
856
- }
77
+ const number=(rawValue,ctx)=>{if(typeof rawValue==="number"){return ctx.success(rawValue)}return ctx.failure("number",rawValue)};
857
78
 
858
- const number = (rawValue, ctx) => {
859
- if (typeof rawValue === "number") {
860
- return ctx.success(rawValue);
861
- }
862
- return ctx.failure("number", rawValue);
863
- };
864
-
865
- function object(schema) {
866
- return (rawValue, ctx) => {
867
- if (!isObject(rawValue)) {
868
- return ctx.failure("object", rawValue);
869
- }
870
- const ret = {
871
- ...rawValue
872
- };
873
- const mismatches = [];
874
- for (const [prop, propParser] of Object.entries(schema)) {
875
- const result = propParser(rawValue[prop], ctx.forSubtree(prop));
876
- if (isSuccess(result)) {
877
- if (result.value !== undefined || prop in rawValue) {
878
- ret[prop] = result.value;
879
- }
880
- } else {
881
- mismatches.push(...result.detail);
882
- }
883
- }
884
- if (mismatches.length > 0) {
885
- return failure(mismatches);
886
- }
887
- return ctx.success(ret);
888
- };
889
- }
79
+ function object(schema){return (rawValue,ctx)=>{if(!isObject(rawValue)){return ctx.failure("object",rawValue)}const ret={...rawValue};const mismatches=[];for(const[prop,propParser]of Object.entries(schema)){const result=propParser(rawValue[prop],ctx.forSubtree(prop));if(isSuccess(result)){if(result.value!==undefined||prop in rawValue){ret[prop]=result.value;}}else {mismatches.push(...result.detail);}}if(mismatches.length>0){return failure(mismatches)}return ctx.success(ret)}}
890
80
 
891
- function optional(parseValue) {
892
- return (rawValue, ctx) => {
893
- if (rawValue === undefined) {
894
- return ctx.success(rawValue);
895
- }
896
- return parseValue(rawValue, ctx);
897
- };
898
- }
81
+ function optional(parseValue){return (rawValue,ctx)=>{if(rawValue===undefined){return ctx.success(rawValue)}return parseValue(rawValue,ctx)}}
899
82
 
900
- function pair(parseA, parseB) {
901
- return (rawValue, ctx) => {
902
- if (!Array.isArray(rawValue)) {
903
- return ctx.failure("array", rawValue);
904
- }
905
- if (rawValue.length !== 2) {
906
- return ctx.failure("array of length 2", rawValue);
907
- }
908
- const [rawA, rawB] = rawValue;
909
- const resultA = parseA(rawA, ctx.forSubtree(0));
910
- if (isFailure(resultA)) {
911
- return resultA;
912
- }
913
- const resultB = parseB(rawB, ctx.forSubtree(1));
914
- if (isFailure(resultB)) {
915
- return resultB;
916
- }
917
- return ctx.success([resultA.value, resultB.value]);
918
- };
919
- }
83
+ function pair(parseA,parseB){return (rawValue,ctx)=>{if(!Array.isArray(rawValue)){return ctx.failure("array",rawValue)}if(rawValue.length!==2){return ctx.failure("array of length 2",rawValue)}const[rawA,rawB]=rawValue;const resultA=parseA(rawA,ctx.forSubtree(0));if(isFailure(resultA)){return resultA}const resultB=parseB(rawB,ctx.forSubtree(1));if(isFailure(resultB)){return resultB}return ctx.success([resultA.value,resultB.value])}}
920
84
 
921
- function pipeParsers(p) {
922
- return new ParserPipeline(p);
923
- }
924
- class ParserPipeline {
925
- constructor(parser) {
926
- this.parser = parser;
927
- }
928
- then(nextParser) {
929
- return new ParserPipeline(composeParsers(this.parser, nextParser));
930
- }
931
- }
932
- function composeParsers(parserA, parserB) {
933
- return (rawValue, ctx) => {
934
- const partialResult = parserA(rawValue, ctx);
935
- if (isFailure(partialResult)) {
936
- return partialResult;
937
- }
938
- return parserB(partialResult.value, ctx);
939
- };
940
- }
85
+ function pipeParsers(p){return new ParserPipeline(p)}class ParserPipeline{then(nextParser){return new ParserPipeline(composeParsers(this.parser,nextParser))}constructor(parser){this.parser=parser;}}function composeParsers(parserA,parserB){return (rawValue,ctx)=>{const partialResult=parserA(rawValue,ctx);if(isFailure(partialResult)){return partialResult}return parserB(partialResult.value,ctx)}}
941
86
 
942
- function record(parseKey, parseValue) {
943
- return (rawValue, ctx) => {
944
- if (!isObject(rawValue)) {
945
- return ctx.failure("object", rawValue);
946
- }
947
- const result = {};
948
- const mismatches = [];
949
- for (const [key, value] of Object.entries(rawValue)) {
950
- const entryCtx = ctx.forSubtree(key);
951
- const keyResult = parseKey(key, entryCtx);
952
- if (isFailure(keyResult)) {
953
- mismatches.push(...keyResult.detail);
954
- }
955
- const valueResult = parseValue(value, entryCtx);
956
- if (isFailure(valueResult)) {
957
- mismatches.push(...valueResult.detail);
958
- }
959
- if (isSuccess(keyResult) && isSuccess(valueResult)) {
960
- result[keyResult.value] = valueResult.value;
961
- }
962
- }
963
- if (mismatches.length > 0) {
964
- return failure(mismatches);
965
- }
966
- return ctx.success(result);
967
- };
968
- }
87
+ function record(parseKey,parseValue){return (rawValue,ctx)=>{if(!isObject(rawValue)){return ctx.failure("object",rawValue)}const result={};const mismatches=[];for(const[key,value]of Object.entries(rawValue)){const entryCtx=ctx.forSubtree(key);const keyResult=parseKey(key,entryCtx);if(isFailure(keyResult)){mismatches.push(...keyResult.detail);}const valueResult=parseValue(value,entryCtx);if(isFailure(valueResult)){mismatches.push(...valueResult.detail);}if(isSuccess(keyResult)&&isSuccess(valueResult)){result[keyResult.value]=valueResult.value;}}if(mismatches.length>0){return failure(mismatches)}return ctx.success(result)}}
969
88
 
970
- const string = (rawValue, ctx) => {
971
- if (typeof rawValue === "string") {
972
- return ctx.success(rawValue);
973
- }
974
- return ctx.failure("string", rawValue);
975
- };
976
-
977
- function trio(parseA, parseB, parseC) {
978
- return (rawValue, ctx) => {
979
- if (!Array.isArray(rawValue)) {
980
- return ctx.failure("array", rawValue);
981
- }
982
- if (rawValue.length !== 3) {
983
- return ctx.failure("array of length 3", rawValue);
984
- }
985
- const resultA = parseA(rawValue[0], ctx.forSubtree(0));
986
- if (isFailure(resultA)) {
987
- return resultA;
988
- }
989
- const resultB = parseB(rawValue[1], ctx.forSubtree(1));
990
- if (isFailure(resultB)) {
991
- return resultB;
992
- }
993
- const resultC = parseC(rawValue[2], ctx.forSubtree(2));
994
- if (isFailure(resultC)) {
995
- return resultC;
996
- }
997
- return ctx.success([resultA.value, resultB.value, resultC.value]);
998
- };
999
- }
89
+ const string=(rawValue,ctx)=>{if(typeof rawValue==="string"){return ctx.success(rawValue)}return ctx.failure("string",rawValue)};
1000
90
 
1001
- function union(parseBranch) {
1002
- return new UnionBuilder(parseBranch);
1003
- }
1004
- class UnionBuilder {
1005
- constructor(parser) {
1006
- this.parser = parser;
1007
- }
1008
- or(newBranch) {
1009
- return new UnionBuilder(either(this.parser, newBranch));
1010
- }
1011
- }
1012
- function either(parseA, parseB) {
1013
- return (rawValue, ctx) => {
1014
- const resultA = parseA(rawValue, ctx);
1015
- if (isSuccess(resultA)) {
1016
- return resultA;
1017
- }
1018
- return parseB(rawValue, ctx);
1019
- };
1020
- }
91
+ function trio(parseA,parseB,parseC){return (rawValue,ctx)=>{if(!Array.isArray(rawValue)){return ctx.failure("array",rawValue)}if(rawValue.length!==3){return ctx.failure("array of length 3",rawValue)}const resultA=parseA(rawValue[0],ctx.forSubtree(0));if(isFailure(resultA)){return resultA}const resultB=parseB(rawValue[1],ctx.forSubtree(1));if(isFailure(resultB)){return resultB}const resultC=parseC(rawValue[2],ctx.forSubtree(2));if(isFailure(resultC)){return resultC}return ctx.success([resultA.value,resultB.value,resultC.value])}}
1021
92
 
1022
- function defaulted(parser, fallback) {
1023
- return (rawValue, ctx) => {
1024
- if (rawValue == null) {
1025
- return success(fallback(rawValue));
1026
- }
1027
- return parser(rawValue, ctx);
1028
- };
1029
- }
93
+ function union(parseBranch){return new UnionBuilder(parseBranch)}class UnionBuilder{or(newBranch){return new UnionBuilder(either(this.parser,newBranch))}constructor(parser){this.parser=parser;}}function either(parseA,parseB){return (rawValue,ctx)=>{const resultA=parseA(rawValue,ctx);if(isSuccess(resultA)){return resultA}return parseB(rawValue,ctx)}}
1030
94
 
1031
- const parseImages = defaulted(record(string, object({
1032
- width: number,
1033
- height: number
1034
- })), () => ({}));
1035
-
1036
- function parseWidget(parseType, parseOptions) {
1037
- return object({
1038
- type: parseType,
1039
- static: optional(boolean),
1040
- graded: optional(boolean),
1041
- alignment: optional(string),
1042
- options: parseOptions,
1043
- key: optional(nullable(number)),
1044
- version: optional(object({
1045
- major: number,
1046
- minor: number
1047
- }))
1048
- });
1049
- }
1050
- function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
1051
- return object({
1052
- type: parseType,
1053
- static: optional(boolean),
1054
- graded: optional(boolean),
1055
- alignment: optional(string),
1056
- options: parseOptions,
1057
- key: optional(nullable(number)),
1058
- version: parseVersion
1059
- });
1060
- }
95
+ function defaulted(parser,fallback){return (rawValue,ctx)=>{if(rawValue==null){return success(fallback(rawValue))}return parser(rawValue,ctx)}}
1061
96
 
1062
- const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
1063
- items: array(string),
1064
- categories: array(string),
1065
- randomizeItems: defaulted(boolean, () => false),
1066
- static: defaulted(boolean, () => false),
1067
- values: array(defaulted(number, () => 0)),
1068
- highlightLint: optional(boolean),
1069
- linterContext: optional(object({
1070
- contentType: string,
1071
- paths: array(string),
1072
- stack: array(string)
1073
- }))
1074
- }));
1075
-
1076
- const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
1077
- programID: string,
1078
- programType: any,
1079
- settings: array(object({
1080
- name: string,
1081
- value: string
1082
- })),
1083
- showEditor: boolean,
1084
- showButtons: boolean,
1085
- height: number,
1086
- static: defaulted(boolean, () => false)
1087
- }));
1088
-
1089
- const parseDefinitionWidget = parseWidget(constant("definition"), object({
1090
- togglePrompt: string,
1091
- definition: string,
1092
- static: defaulted(boolean, () => false)
1093
- }));
1094
-
1095
- const parseDropdownWidget = parseWidget(constant("dropdown"), object({
1096
- placeholder: defaulted(string, () => ""),
1097
- ariaLabel: optional(string),
1098
- visibleLabel: optional(string),
1099
- static: defaulted(boolean, () => false),
1100
- choices: array(object({
1101
- content: string,
1102
- correct: boolean
1103
- }))
1104
- }));
1105
-
1106
- const parseExplanationWidget = parseWidget(constant("explanation"), object({
1107
- showPrompt: string,
1108
- hidePrompt: string,
1109
- explanation: string,
1110
- // We wrap parseWidgetsMap in a function here to make sure it is not
1111
- // referenced before it is defined. There is an import cycle between
1112
- // this file and widgets-map.ts that could cause it to be undefined.
1113
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
1114
- static: defaulted(boolean, () => false)
1115
- }));
1116
-
1117
- const KeypadKeys = ["PLUS", "MINUS", "NEGATIVE", "TIMES", "DIVIDE", "DECIMAL", "PERIOD", "PERCENT", "CDOT", "EQUAL", "NEQ", "GT", "LT", "GEQ", "LEQ",
1118
- // mobile native only
1119
- "FRAC_INCLUSIVE",
1120
- // mobile native only
1121
- "FRAC_EXCLUSIVE",
1122
- // mobile native only
1123
- "FRAC", "EXP", "EXP_2", "EXP_3", "SQRT", "CUBE_ROOT", "RADICAL", "LEFT_PAREN", "RIGHT_PAREN", "LN", "LOG", "LOG_N", "SIN", "COS",
1124
- // TODO(charlie): Add in additional Greek letters.,
1125
- "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",
1126
- // Multi-functional keys.
1127
- "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"];
1128
-
1129
- // Used by KeypadContext to pass around a renderer reference
1130
-
1131
- /**
1132
- * Scrape the answer forms for any variables or contants (like Pi)
1133
- * that need to be included as keys on the keypad.
1134
- */
1135
- function deriveExtraKeys(widgetOptions) {
1136
- if (widgetOptions.extraKeys) {
1137
- return widgetOptions.extraKeys;
1138
- }
1139
-
1140
- // If there are no extra symbols available, we include Pi anyway, so
1141
- // that the "extra symbols" button doesn't appear empty.
1142
- const defaultKeys = ["PI"];
1143
- if (widgetOptions.answerForms == null) {
1144
- return defaultKeys;
1145
- }
1146
-
1147
- // Extract any and all variables and constants from the answer forms.
1148
- const uniqueExtraVariables = {};
1149
- const uniqueExtraConstants = {};
1150
- for (const answerForm of widgetOptions.answerForms) {
1151
- const maybeExpr = KAS__namespace.parse(answerForm.value, widgetOptions);
1152
- if (maybeExpr.parsed) {
1153
- const expr = maybeExpr.expr;
1154
-
1155
- // The keypad expects Greek letters to be capitalized (e.g., it
1156
- // requires `PI` instead of `pi`). Right now, it only supports Pi
1157
- // and Theta, so we special-case.
1158
- const isGreek = symbol => symbol === "pi" || symbol === "theta";
1159
- const toKey = symbol => isGreek(symbol) ? symbol.toUpperCase() : symbol;
1160
- const isKey = key => KeypadKeys.includes(key);
1161
- for (const variable of expr.getVars()) {
1162
- const maybeKey = toKey(variable);
1163
- if (isKey(maybeKey)) {
1164
- uniqueExtraVariables[maybeKey] = true;
1165
- }
1166
- }
1167
- for (const constant of expr.getConsts()) {
1168
- const maybeKey = toKey(constant);
1169
- if (isKey(maybeKey)) {
1170
- uniqueExtraConstants[maybeKey] = true;
1171
- }
1172
- }
1173
- }
1174
- }
1175
-
1176
- // TODO(charlie): Alert the keypad as to which of these symbols should be
1177
- // treated as functions.
1178
- const extraVariables = Object.keys(uniqueExtraVariables).sort();
1179
- const extraConstants = Object.keys(uniqueExtraConstants).sort();
1180
- const extraKeys = [...extraVariables, ...extraConstants];
1181
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1182
- if (!extraKeys.length) {
1183
- return defaultKeys;
1184
- }
1185
- return extraKeys;
1186
- }
97
+ const parseImages=defaulted(record(string,object({width:number,height:number})),()=>({}));
1187
98
 
1188
- // Given a function, creates a PartialParser that converts one type to another
1189
- // using that function. The returned parser never fails.
1190
- function convert(f) {
1191
- return (rawValue, ctx) => ctx.success(f(rawValue));
1192
- }
99
+ function parseWidget(parseType,parseOptions){return object({type:parseType,static:optional(boolean),graded:optional(boolean),alignment:optional(string),options:parseOptions,key:optional(nullable(number)),version:optional(object({major:number,minor:number}))})}function parseWidgetWithVersion(parseVersion,parseType,parseOptions){return object({type:parseType,static:optional(boolean),graded:optional(boolean),alignment:optional(string),options:parseOptions,key:optional(nullable(number)),version:parseVersion})}
1193
100
 
1194
- const parseLegacyButtonSet = enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations", "scientific");
1195
- const parseLegacyButtonSets = defaulted(array(parseLegacyButtonSet),
1196
- // NOTE(benchristel): I copied the default buttonSets from
1197
- // expression.tsx. See the parse-perseus-json/README.md for
1198
- // an explanation of why we want to duplicate the default here.
1199
- () => ["basic", "trig", "prealgebra", "logarithms"]);
1200
-
1201
- /**
1202
- * Creates a parser for a widget options type with multiple major versions. Old
1203
- * versions are migrated to the latest version. The parse fails if the input
1204
- * data does not match any of the versions.
1205
- *
1206
- * @example
1207
- * const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
1208
- * .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
1209
- * .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
1210
- * .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
1211
- * .parser;
1212
- *
1213
- * @param latestMajorVersion the latest major version of the widget options.
1214
- * @param parseLatest a {@link Parser} for the latest version of the widget
1215
- * options.
1216
- * @returns a builder object, to which migrations from earlier versions can be
1217
- * added. Migrations must be added in "reverse chronological" order as in the
1218
- * example above.
1219
- */
1220
- function versionedWidgetOptions(latestMajorVersion, parseLatest) {
1221
- return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
1222
- }
1223
- class VersionedWidgetOptionsParserBuilder {
1224
- parser;
1225
- constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
1226
- this.migrateToLatest = migrateToLatest;
1227
- this.parseOtherVersions = parseOtherVersions;
1228
- const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
1229
- this.parser = (raw, ctx) => {
1230
- if (!isObject(raw)) {
1231
- return ctx.failure("object", raw);
1232
- }
1233
- const versionParseResult = parseVersionedObject(raw, ctx);
1234
- if (isFailure(versionParseResult)) {
1235
- return versionParseResult;
1236
- }
1237
- if (versionParseResult.value.version.major !== majorVersion) {
1238
- return this.parseOtherVersions(raw, ctx);
1239
- }
1240
- return parseThisVersionAndMigrateToLatest(raw, ctx);
1241
- };
1242
- }
1243
-
1244
- /**
1245
- * Add a migration from an old version of the widget options.
1246
- */
1247
- withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
1248
- const parseOtherVersions = this.parser;
1249
- const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
1250
- return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
1251
- }
1252
- }
1253
- const parseVersionedObject = object({
1254
- version: defaulted(object({
1255
- major: number,
1256
- minor: number
1257
- }), () => ({
1258
- major: 0,
1259
- minor: 0
1260
- }))
1261
- });
101
+ const parseCategorizerWidget=parseWidget(constant("categorizer"),object({items:array(string),categories:array(string),randomizeItems:defaulted(boolean,()=>false),static:defaulted(boolean,()=>false),values:array(defaulted(number,()=>0)),highlightLint:optional(boolean),linterContext:optional(object({contentType:string,paths:array(string),stack:array(string)}))}));
1262
102
 
1263
- const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
1264
- const parsePossiblyInvalidAnswerForm = object({
1265
- // `value` is the possibly invalid part of this. It should always be a
1266
- // string, but some answer forms don't have it. The Expression widget
1267
- // ignores invalid values, so we can safely filter them out during parsing.
1268
- value: optional(string),
1269
- form: defaulted(boolean, () => false),
1270
- simplify: defaulted(boolean, () => false),
1271
- considered: enumeration("correct", "wrong", "ungraded"),
1272
- key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
1273
- });
1274
- function removeInvalidAnswerForms(possiblyInvalid) {
1275
- const valid = [];
1276
- for (const answerForm of possiblyInvalid) {
1277
- const {
1278
- value
1279
- } = answerForm;
1280
- if (value != null) {
1281
- // Copying the object seems to be needed to make TypeScript happy
1282
- valid.push({
1283
- ...answerForm,
1284
- value
1285
- });
1286
- }
1287
- }
1288
- return valid;
1289
- }
1290
- const version2$1 = object({
1291
- major: constant(2),
1292
- minor: number
1293
- });
1294
- const parseExpressionWidgetV2 = parseWidgetWithVersion(version2$1, constant("expression"), object({
1295
- answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
1296
- functions: array(string),
1297
- times: boolean,
1298
- visibleLabel: optional(string),
1299
- ariaLabel: optional(string),
1300
- buttonSets: parseLegacyButtonSets,
1301
- buttonsVisible: optional(enumeration("always", "never", "focused")),
1302
- extraKeys: array(enumeration(...KeypadKeys))
1303
- }));
1304
- const version1$1 = object({
1305
- major: constant(1),
1306
- minor: number
1307
- });
1308
- const parseExpressionWidgetV1 = parseWidgetWithVersion(version1$1, constant("expression"), object({
1309
- answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
1310
- functions: array(string),
1311
- times: boolean,
1312
- visibleLabel: optional(string),
1313
- ariaLabel: optional(string),
1314
- buttonSets: parseLegacyButtonSets,
1315
- buttonsVisible: optional(enumeration("always", "never", "focused"))
1316
- }));
1317
- function migrateV1ToV2$1(widget) {
1318
- const {
1319
- options
1320
- } = widget;
1321
- return {
1322
- ...widget,
1323
- version: {
1324
- major: 2,
1325
- minor: 0
1326
- },
1327
- options: {
1328
- times: options.times,
1329
- buttonSets: options.buttonSets,
1330
- functions: options.functions,
1331
- buttonsVisible: options.buttonsVisible,
1332
- visibleLabel: options.visibleLabel,
1333
- ariaLabel: options.ariaLabel,
1334
- answerForms: options.answerForms,
1335
- extraKeys: deriveExtraKeys(options)
1336
- }
1337
- };
1338
- }
1339
- const version0$1 = optional(object({
1340
- major: constant(0),
1341
- minor: number
1342
- }));
1343
- const parseExpressionWidgetV0 = parseWidgetWithVersion(version0$1, constant("expression"), object({
1344
- functions: array(string),
1345
- times: boolean,
1346
- visibleLabel: optional(string),
1347
- ariaLabel: optional(string),
1348
- form: boolean,
1349
- simplify: boolean,
1350
- value: string,
1351
- buttonSets: parseLegacyButtonSets,
1352
- buttonsVisible: optional(enumeration("always", "never", "focused"))
1353
- }));
1354
- function migrateV0ToV1$1(widget) {
1355
- const {
1356
- options
1357
- } = widget;
1358
- return {
1359
- ...widget,
1360
- version: {
1361
- major: 1,
1362
- minor: 0
1363
- },
1364
- options: {
1365
- times: options.times,
1366
- buttonSets: options.buttonSets,
1367
- functions: options.functions,
1368
- buttonsVisible: options.buttonsVisible,
1369
- visibleLabel: options.visibleLabel,
1370
- ariaLabel: options.ariaLabel,
1371
- answerForms: [{
1372
- considered: "correct",
1373
- form: options.form,
1374
- simplify: options.simplify,
1375
- value: options.value
1376
- }]
1377
- }
1378
- };
1379
- }
1380
- const parseExpressionWidget = versionedWidgetOptions(2, parseExpressionWidgetV2).withMigrationFrom(1, parseExpressionWidgetV1, migrateV1ToV2$1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1$1).parser;
1381
-
1382
- const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
1383
- const parseGradedGroupWidgetOptions = object({
1384
- title: defaulted(string, () => ""),
1385
- hasHint: optional(nullable(boolean)),
1386
- // This module has an import cycle with parsePerseusRenderer.
1387
- // The anonymous function below ensures that we don't try to access
1388
- // parsePerseusRenderer before it's defined.
1389
- hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
1390
- content: string,
1391
- // This module has an import cycle with parseWidgetsMap.
1392
- // The anonymous function below ensures that we don't try to access
1393
- // parseWidgetsMap before it's defined.
1394
- widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
1395
- widgetEnabled: optional(nullable(boolean)),
1396
- immutableWidgets: optional(nullable(boolean)),
1397
- images: record(string, object({
1398
- width: number,
1399
- height: number
1400
- }))
1401
- });
1402
- const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
1403
-
1404
- const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
1405
- gradedGroups: array(parseGradedGroupWidgetOptions)
1406
- }));
1407
-
1408
- /**
1409
- * discriminatedUnion() should be preferred over union() when parsing a
1410
- * discriminated union type, because discriminatedUnion() produces more
1411
- * understandable failure messages. It takes the discriminant as the source of
1412
- * truth for which variant is to be parsed, and expects the other data to match
1413
- * that variant.
1414
- */
1415
- function discriminatedUnionOn(discriminantKey) {
1416
- const noMoreBranches = (raw, ctx) => {
1417
- if (!isObject(raw)) {
1418
- return ctx.failure("object", raw);
1419
- }
1420
- return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
1421
- };
1422
- return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
1423
- }
1424
- class DiscriminatedUnionBuilder {
1425
- constructor(discriminantKey, parser) {
1426
- this.discriminantKey = discriminantKey;
1427
- this.parser = parser;
1428
- }
1429
- withBranch(discriminantValue, parseNewVariant) {
1430
- const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
1431
- return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
1432
- }
1433
- }
1434
- function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
1435
- return (raw, ctx) => {
1436
- if (!isObject(raw)) {
1437
- return ctx.failure("object", raw);
1438
- }
1439
- if (raw[discriminantKey] === discriminantValue) {
1440
- return parseVariant(raw, ctx);
1441
- }
1442
- return parseOtherBranches(raw, ctx);
1443
- };
1444
- }
103
+ const parseCSProgramWidget=parseWidget(constant("cs-program"),object({programID:string,programType:any,settings:array(object({name:string,value:string})),showEditor:boolean,showButtons:boolean,height:number,static:defaulted(boolean,()=>false)}));
1445
104
 
1446
- const pairOfNumbers$3 = pair(number, number);
1447
- const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
1448
- const parseGrapherWidget = parseWidget(constant("grapher"), object({
1449
- availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
1450
- correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
1451
- type: constant("absolute_value"),
1452
- coords: nullable(pairOfPoints)
1453
- })).withBranch("exponential", object({
1454
- type: constant("exponential"),
1455
- asymptote: pairOfPoints,
1456
- coords: nullable(pairOfPoints)
1457
- })).withBranch("linear", object({
1458
- type: constant("linear"),
1459
- coords: nullable(pairOfPoints)
1460
- })).withBranch("logarithm", object({
1461
- type: constant("logarithm"),
1462
- asymptote: pairOfPoints,
1463
- coords: nullable(pairOfPoints)
1464
- })).withBranch("quadratic", object({
1465
- type: constant("quadratic"),
1466
- coords: nullable(pairOfPoints)
1467
- })).withBranch("sinusoid", object({
1468
- type: constant("sinusoid"),
1469
- coords: nullable(pairOfPoints)
1470
- })).withBranch("tangent", object({
1471
- type: constant("tangent"),
1472
- coords: nullable(pairOfPoints)
1473
- })).parser,
1474
- graph: object({
1475
- backgroundImage: object({
1476
- bottom: optional(number),
1477
- height: optional(number),
1478
- left: optional(number),
1479
- scale: optional(number),
1480
- url: optional(nullable(string)),
1481
- width: optional(number)
1482
- }),
1483
- box: optional(pairOfNumbers$3),
1484
- editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
1485
- gridStep: optional(pairOfNumbers$3),
1486
- labels: pair(string, string),
1487
- markings: enumeration("graph", "none", "grid"),
1488
- range: pair(pairOfNumbers$3, pairOfNumbers$3),
1489
- rulerLabel: constant(""),
1490
- rulerTicks: number,
1491
- showProtractor: optional(boolean),
1492
- showRuler: optional(boolean),
1493
- showTooltips: optional(boolean),
1494
- snapStep: optional(pairOfNumbers$3),
1495
- step: pairOfNumbers$3,
1496
- valid: optional(union(boolean).or(string).parser)
1497
- })
1498
- }));
1499
-
1500
- const parseGroupWidget = parseWidget(constant("group"),
1501
- // This module has an import cycle with parsePerseusRenderer.
1502
- // The anonymous function below ensures that we don't try to access
1503
- // parsePerseusRenderer before it's defined.
1504
- (rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
1505
-
1506
- const parseIframeWidget = parseWidget(constant("iframe"), object({
1507
- url: string,
1508
- settings: optional(array(object({
1509
- name: string,
1510
- value: string
1511
- }))),
1512
- width: union(number).or(string).parser,
1513
- height: union(number).or(string).parser,
1514
- allowFullScreen: defaulted(boolean, () => false),
1515
- allowTopNavigation: optional(boolean),
1516
- static: defaulted(boolean, () => false)
1517
- }));
1518
-
1519
- const stringToNumber = (rawValue, ctx) => {
1520
- if (typeof rawValue === "number") {
1521
- return ctx.success(rawValue);
1522
- }
1523
- const parsedNumber = +rawValue;
1524
- if (rawValue === "" || isNaN(parsedNumber)) {
1525
- return ctx.failure("a number or numeric string", rawValue);
1526
- }
1527
- return ctx.success(parsedNumber);
1528
- };
1529
-
1530
- function emptyToZero(x) {
1531
- return x === "" ? 0 : x;
1532
- }
1533
- const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
1534
- // In this specific case, empty string is equivalent to zero. An empty
1535
- // string parses to either NaN (using parseInt) or 0 (using unary +) and
1536
- // CSS will treat NaN as invalid and default to 0 instead.
1537
- .then(convert(emptyToZero)).then(stringToNumber).parser;
1538
- const dimensionOrUndefined = defaulted(imageDimensionToNumber, () => undefined);
1539
- const parsePerseusImageBackground = object({
1540
- url: optional(nullable(string)),
1541
- width: dimensionOrUndefined,
1542
- height: dimensionOrUndefined,
1543
- top: dimensionOrUndefined,
1544
- left: dimensionOrUndefined,
1545
- bottom: dimensionOrUndefined,
1546
- scale: dimensionOrUndefined
1547
- });
105
+ const parseDefinitionWidget=parseWidget(constant("definition"),object({togglePrompt:string,definition:string,static:defaulted(boolean,()=>false)}));
1548
106
 
1549
- const pairOfNumbers$2 = pair(number, number);
1550
- const parseImageWidget = parseWidget(constant("image"), object({
1551
- title: optional(string),
1552
- caption: optional(string),
1553
- alt: optional(string),
1554
- backgroundImage: parsePerseusImageBackground,
1555
- static: optional(boolean),
1556
- labels: optional(array(object({
1557
- content: string,
1558
- alignment: string,
1559
- coordinates: array(number)
1560
- }))),
1561
- range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
1562
- box: optional(pairOfNumbers$2)
1563
- }));
1564
-
1565
- const booleanToString = (rawValue, ctx) => {
1566
- if (typeof rawValue === "boolean") {
1567
- return ctx.success(String(rawValue));
1568
- }
1569
- return ctx.failure("boolean", rawValue);
1570
- };
1571
- const parseInputNumberWidget = parseWidget(constant("input-number"), object({
1572
- answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
1573
- inexact: optional(boolean),
1574
- maxError: optional(union(number).or(string).parser),
1575
- rightAlign: optional(boolean),
1576
- simplify: enumeration("required", "optional", "enforced"),
1577
- size: enumeration("normal", "small"),
1578
- // TODO(benchristel): there are some content items where value is a
1579
- // boolean, even though that makes no sense. We should figure out if
1580
- // those content items are actually published anywhere, and consider
1581
- // updating them.
1582
- value: union(number).or(string).or(booleanToString).parser,
1583
- customKeypad: optional(boolean)
1584
- }));
1585
-
1586
- const pairOfNumbers$1 = pair(number, number);
1587
- const stringOrEmpty = defaulted(string, () => "");
1588
- const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
1589
- const parseFunctionElement = object({
1590
- type: constant("function"),
1591
- key: parseKey,
1592
- options: object({
1593
- value: string,
1594
- funcName: string,
1595
- rangeMin: string,
1596
- rangeMax: string,
1597
- color: string,
1598
- strokeDasharray: string,
1599
- strokeWidth: number
1600
- })
1601
- });
1602
- const parseLabelElement = object({
1603
- type: constant("label"),
1604
- key: parseKey,
1605
- options: object({
1606
- label: string,
1607
- color: string,
1608
- coordX: string,
1609
- coordY: string
1610
- })
1611
- });
1612
- const parseLineElement = object({
1613
- type: constant("line"),
1614
- key: parseKey,
1615
- options: object({
1616
- color: string,
1617
- startX: string,
1618
- startY: string,
1619
- endX: string,
1620
- endY: string,
1621
- strokeDasharray: string,
1622
- strokeWidth: number,
1623
- arrows: string
1624
- })
1625
- });
1626
- const parseMovableLineElement = object({
1627
- type: constant("movable-line"),
1628
- key: parseKey,
1629
- options: object({
1630
- startX: string,
1631
- startY: string,
1632
- startSubscript: number,
1633
- endX: string,
1634
- endY: string,
1635
- endSubscript: number,
1636
- constraint: string,
1637
- snap: number,
1638
- constraintFn: string,
1639
- constraintXMin: string,
1640
- constraintXMax: string,
1641
- constraintYMin: string,
1642
- constraintYMax: string
1643
- })
1644
- });
1645
- const parseMovablePointElement = object({
1646
- type: constant("movable-point"),
1647
- key: parseKey,
1648
- options: object({
1649
- startX: string,
1650
- startY: string,
1651
- varSubscript: number,
1652
- constraint: string,
1653
- snap: number,
1654
- constraintFn: string,
1655
- constraintXMin: stringOrEmpty,
1656
- constraintXMax: stringOrEmpty,
1657
- constraintYMin: stringOrEmpty,
1658
- constraintYMax: stringOrEmpty
1659
- })
1660
- });
1661
- const parseParametricElement = object({
1662
- type: constant("parametric"),
1663
- key: parseKey,
1664
- options: object({
1665
- x: string,
1666
- y: string,
1667
- rangeMin: string,
1668
- rangeMax: string,
1669
- color: string,
1670
- strokeDasharray: string,
1671
- strokeWidth: number
1672
- })
1673
- });
1674
- const parsePointElement = object({
1675
- type: constant("point"),
1676
- key: parseKey,
1677
- options: object({
1678
- color: string,
1679
- coordX: string,
1680
- coordY: string
1681
- })
1682
- });
1683
- const parseRectangleElement = object({
1684
- type: constant("rectangle"),
1685
- key: parseKey,
1686
- options: object({
1687
- color: string,
1688
- coordX: string,
1689
- coordY: string,
1690
- width: string,
1691
- height: string
1692
- })
1693
- });
1694
- const parseInteractionWidget = parseWidget(constant("interaction"), object({
1695
- static: defaulted(boolean, () => false),
1696
- graph: object({
1697
- editableSettings: optional(array(enumeration("canvas", "graph"))),
1698
- box: pairOfNumbers$1,
1699
- labels: array(string),
1700
- range: pair(pairOfNumbers$1, pairOfNumbers$1),
1701
- gridStep: pairOfNumbers$1,
1702
- markings: enumeration("graph", "grid", "none"),
1703
- snapStep: optional(pairOfNumbers$1),
1704
- valid: optional(union(boolean).or(string).parser),
1705
- backgroundImage: optional(parsePerseusImageBackground),
1706
- showProtractor: optional(boolean),
1707
- showRuler: optional(boolean),
1708
- rulerLabel: optional(string),
1709
- rulerTicks: optional(number),
1710
- tickStep: pairOfNumbers$1
1711
- }),
1712
- 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)
1713
- }));
1714
-
1715
- /**
1716
- * The Perseus "data schema" file.
1717
- *
1718
- * This file, and the types in it, represents the "data schema" that Perseus
1719
- * uses. The @khanacademy/perseus-editor package edits and produces objects
1720
- * that conform to the types in this file. Similarly, the top-level renderers
1721
- * in @khanacademy/perseus, consume objects that conform to these types.
1722
- *
1723
- * WARNING: This file should not import any types from elsewhere so that it is
1724
- * easy to reason about changes that alter the Perseus schema. This helps
1725
- * ensure that it is not changed accidentally when upgrading a dependant
1726
- * package or other part of Perseus code. Note that TypeScript does type
1727
- * checking via something called "structural typing". This means that as long
1728
- * as the shape of a type matches, the name it goes by doesn't matter. As a
1729
- * result, a `Coord` type that looks like this `[x: number, y: number]` is
1730
- * _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
1731
- * number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
1732
- * number, y: number]` is compatible with `[min: number, max: number]`. The
1733
- * labels are for humans, not TypeScript. :)
1734
- *
1735
- * If you make changes to types in this file, be very sure that:
1736
- *
1737
- * a) the changes are backwards compatible. If they are not, old data from
1738
- * previous versions of the "schema" could become unrenderable, or worse,
1739
- * introduce hard-to-diagnose bugs.
1740
- * b) the parsing code (`util/parse-perseus-json/`) is updated to handle
1741
- * the new format _as well as_ the old format.
1742
- */
1743
-
1744
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
1745
-
1746
- // Same name as Mafs
1747
-
1748
- /**
1749
- * A utility type that constructs a widget map from a "registry interface".
1750
- * The keys of the registry should be the widget type (aka, "categorizer" or
1751
- * "radio", etc) and the value should be the option type stored in the value
1752
- * of the map.
1753
- *
1754
- * You can think of this as a type that generates another type. We use
1755
- * "registry interfaces" as a way to keep a set of widget types to their data
1756
- * type in several places in Perseus. This type then allows us to generate a
1757
- * map type that maps a widget id to its data type and keep strong typing by
1758
- * widget id.
1759
- *
1760
- * For example, given a fictitious registry such as this:
1761
- *
1762
- * ```
1763
- * interface DummyRegistry {
1764
- * categorizer: { categories: ReadonlyArray<string> };
1765
- * dropdown: { choices: ReadonlyArray<string> }:
1766
- * }
1767
- * ```
1768
- *
1769
- * If we create a DummyMap using this helper:
1770
- *
1771
- * ```
1772
- * type DummyMap = MakeWidgetMap<DummyRegistry>;
1773
- * ```
1774
- *
1775
- * We'll get a map that looks like this:
1776
- *
1777
- * ```
1778
- * type DummyMap = {
1779
- * `categorizer ${number}`: { categories: ReadonlyArray<string> };
1780
- * `dropdown ${number}`: { choices: ReadonlyArray<string> };
1781
- * }
1782
- * ```
1783
- *
1784
- * We use interfaces for the registries so that they can be extended in cases
1785
- * where the consuming app brings along their own widgets. Interfaces in
1786
- * TypeScript are always open (ie. you can extend them) whereas types aren't.
1787
- */
1788
-
1789
- /**
1790
- * Our core set of Perseus widgets.
1791
- *
1792
- * This interface is the basis for "registering" all Perseus widget types.
1793
- * There should be one key/value pair for each supported widget. If you create
1794
- * a new widget, an entry should be added to this interface. Note that this
1795
- * only registers the widget options type, you'll also need to register the
1796
- * widget so that it's available at runtime (@see
1797
- * {@link file://./widgets.ts#registerWidget}).
1798
- *
1799
- * Importantly, the key should be the name that is used in widget IDs. For most
1800
- * widgets that is the same as the widget option's `type` field. In cases where
1801
- * a widget has been deprecated and replaced with the deprecated-standin
1802
- * widget, it should be the original widget type!
1803
- *
1804
- * If you define the widget outside of this package, you can still add the new
1805
- * widget to this interface by writing the following in that package that
1806
- * contains the widget. TypeScript will merge that definition of the
1807
- * `PerseusWidgets` with the one defined below.
1808
- *
1809
- * ```typescript
1810
- * declare module "@khanacademy/perseus-core" {
1811
- * interface PerseusWidgetTypes {
1812
- * // A new widget
1813
- * "new-awesomeness": MyAwesomeNewWidget;
1814
- *
1815
- * // A deprecated widget
1816
- * "super-old-widget": DeprecatedStandinWidget;
1817
- * }
1818
- * }
1819
- *
1820
- * // The new widget's options definition
1821
- * type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
1822
- *
1823
- * // The deprecated widget's options definition
1824
- * type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
1825
- * ```
1826
- *
1827
- * This interface can be extended through the magic of TypeScript "Declaration
1828
- * merging". Specifically, we augment this module and extend this interface.
1829
- *
1830
- * @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
1831
- */
1832
-
1833
- /**
1834
- * A map of widget IDs to widget options. This is most often used as the type
1835
- * for a set of widgets defined in a `PerseusItem` but can also be useful to
1836
- * represent a function parameter where only `widgets` from a `PerseusItem` are
1837
- * needed. Today Widget IDs are made up of the widget type and an incrementing
1838
- * integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
1839
- * reading/parsing the widget id to derive any information from it, except in
1840
- * the case of this map.
1841
- *
1842
- * @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
1843
- * by augmenting the PerseusWidgetTypes with new widget types!
1844
- */
1845
-
1846
- /**
1847
- * PerseusWidget is a union of all the different types of widget options that
1848
- * Perseus knows about.
1849
- *
1850
- * Thanks to it being based on PerseusWidgetTypes interface, this union is
1851
- * automatically extended to include widgets used in tests without those widget
1852
- * option types seeping into our production types.
1853
- *
1854
- * @see MockWidget for an example
1855
- */
1856
-
1857
- /**
1858
- * A "PerseusItem" is a classic Perseus item. It is rendered by the
1859
- * `ServerItemRenderer` and the layout is pre-set.
1860
- *
1861
- * To render more complex Perseus items, see the `Item` type in the multi item
1862
- * area.
1863
- */
1864
-
1865
- /**
1866
- * A "PerseusArticle" is an item that is meant to be rendered as an article.
1867
- * This item is never scored and is rendered by the `ArticleRenderer`.
1868
- */
1869
-
1870
- const ItemExtras = [
1871
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
1872
- "calculator",
1873
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
1874
- "chi2Table",
1875
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
1876
- "financialCalculatorMonthlyPayment",
1877
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
1878
- "financialCalculatorTotalAmount",
1879
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
1880
- "financialCalculatorTimeToPayOff",
1881
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
1882
- "periodicTable",
1883
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
1884
- "periodicTableWithKey",
1885
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
1886
- "tTable",
1887
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
1888
- "zTable"];
1889
-
1890
- /**
1891
- * The type representing the common structure of all widget's options. The
1892
- * `Options` generic type represents the widget-specific option data.
1893
- */
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
107
+ const parseDropdownWidget=parseWidget(constant("dropdown"),object({placeholder:defaulted(string,()=>""),ariaLabel:optional(string),visibleLabel:optional(string),static:defaulted(boolean,()=>false),choices:array(object({content:string,correct:boolean}))}));
1922
108
 
1923
- // prettier-ignore
109
+ const parseExplanationWidget=parseWidget(constant("explanation"),object({showPrompt:string,hidePrompt:string,explanation:string,widgets:defaulted((rawVal,ctx)=>parseWidgetsMap(rawVal,ctx),()=>({})),static:defaulted(boolean,()=>false)}));
1924
110
 
1925
- // prettier-ignore
111
+ 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"];
1926
112
 
1927
- // prettier-ignore
113
+ function deriveExtraKeys(widgetOptions){if(widgetOptions.extraKeys){return widgetOptions.extraKeys}const defaultKeys=["PI"];if(widgetOptions.answerForms==null){return defaultKeys}const uniqueExtraVariables={};const uniqueExtraConstants={};for(const answerForm of widgetOptions.answerForms){const maybeExpr=KAS__namespace.parse(answerForm.value,widgetOptions);if(maybeExpr.parsed){const expr=maybeExpr.expr;const isGreek=symbol=>symbol==="pi"||symbol==="theta";const toKey=symbol=>isGreek(symbol)?symbol.toUpperCase():symbol;const isKey=key=>KeypadKeys.includes(key);for(const variable of expr.getVars()){const maybeKey=toKey(variable);if(isKey(maybeKey)){uniqueExtraVariables[maybeKey]=true;}}for(const constant of expr.getConsts()){const maybeKey=toKey(constant);if(isKey(maybeKey)){uniqueExtraConstants[maybeKey]=true;}}}}const extraVariables=Object.keys(uniqueExtraVariables).sort();const extraConstants=Object.keys(uniqueExtraConstants).sort();const extraKeys=[...extraVariables,...extraConstants];if(!extraKeys.length){return defaultKeys}return extraKeys}
1928
114
 
1929
- // prettier-ignore
115
+ function convert(f){return (rawValue,ctx)=>ctx.success(f(rawValue))}
1930
116
 
1931
- // prettier-ignore
117
+ const parseLegacyButtonSet=enumeration("basic","basic+div","trig","prealgebra","logarithms","basic relations","advanced relations","scientific");const parseLegacyButtonSets=defaulted(array(parseLegacyButtonSet),()=>["basic","trig","prealgebra","logarithms"]);
1932
118
 
1933
- // prettier-ignore
119
+ function versionedWidgetOptions(latestMajorVersion,parseLatest){return new VersionedWidgetOptionsParserBuilder(latestMajorVersion,parseLatest,latest=>latest,(raw,ctx)=>ctx.failure("widget options with a known version number",raw))}class VersionedWidgetOptionsParserBuilder{withMigrationFrom(majorVersion,parseOldVersion,migrateToNextVersion){const parseOtherVersions=this.parser;const migrateToLatest=old=>this.migrateToLatest(migrateToNextVersion(old));return new VersionedWidgetOptionsParserBuilder(majorVersion,parseOldVersion,migrateToLatest,parseOtherVersions)}constructor(majorVersion,parseThisVersion,migrateToLatest,parseOtherVersions){this.migrateToLatest=migrateToLatest;this.parseOtherVersions=parseOtherVersions;const parseThisVersionAndMigrateToLatest=pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;this.parser=(raw,ctx)=>{if(!isObject(raw)){return ctx.failure("object",raw)}const versionParseResult=parseVersionedObject(raw,ctx);if(isFailure(versionParseResult)){return versionParseResult}if(versionParseResult.value.version.major!==majorVersion){return this.parseOtherVersions(raw,ctx)}return parseThisVersionAndMigrateToLatest(raw,ctx)};}}const parseVersionedObject=object({version:defaulted(object({major:number,minor:number}),()=>({major:0,minor:0}))});
1934
120
 
1935
- // prettier-ignore
121
+ const stringOrNumberOrNullOrUndefined=union(string).or(number).or(constant(null)).or(constant(undefined)).parser;const parsePossiblyInvalidAnswerForm=object({value:optional(string),form:defaulted(boolean,()=>false),simplify:defaulted(boolean,()=>false),considered:enumeration("correct","wrong","ungraded"),key:pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser});function removeInvalidAnswerForms(possiblyInvalid){const valid=[];for(const answerForm of possiblyInvalid){const{value}=answerForm;if(value!=null){valid.push({...answerForm,value});}}return valid}const version2$1=object({major:constant(2),minor:number});const parseExpressionWidgetV2=parseWidgetWithVersion(version2$1,constant("expression"),object({answerForms:pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,functions:array(string),times:boolean,visibleLabel:optional(string),ariaLabel:optional(string),buttonSets:parseLegacyButtonSets,buttonsVisible:optional(enumeration("always","never","focused")),extraKeys:array(enumeration(...KeypadKeys))}));const version1$1=object({major:constant(1),minor:number});const parseExpressionWidgetV1=parseWidgetWithVersion(version1$1,constant("expression"),object({answerForms:pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,functions:array(string),times:boolean,visibleLabel:optional(string),ariaLabel:optional(string),buttonSets:parseLegacyButtonSets,buttonsVisible:optional(enumeration("always","never","focused"))}));function migrateV1ToV2$1(widget){const{options}=widget;return {...widget,version:{major:2,minor:0},options:{times:options.times,buttonSets:options.buttonSets,functions:options.functions,buttonsVisible:options.buttonsVisible,visibleLabel:options.visibleLabel,ariaLabel:options.ariaLabel,answerForms:options.answerForms,extraKeys:deriveExtraKeys(options)}}}const version0$1=optional(object({major:constant(0),minor:number}));const parseExpressionWidgetV0=parseWidgetWithVersion(version0$1,constant("expression"),object({functions:array(string),times:boolean,visibleLabel:optional(string),ariaLabel:optional(string),form:boolean,simplify:boolean,value:string,buttonSets:parseLegacyButtonSets,buttonsVisible:optional(enumeration("always","never","focused"))}));function migrateV0ToV1$1(widget){const{options}=widget;return {...widget,version:{major:1,minor:0},options:{times:options.times,buttonSets:options.buttonSets,functions:options.functions,buttonsVisible:options.buttonsVisible,visibleLabel:options.visibleLabel,ariaLabel:options.ariaLabel,answerForms:[{considered:"correct",form:options.form,simplify:options.simplify,value:options.value}]}}}const parseExpressionWidget=versionedWidgetOptions(2,parseExpressionWidgetV2).withMigrationFrom(1,parseExpressionWidgetV1,migrateV1ToV2$1).withMigrationFrom(0,parseExpressionWidgetV0,migrateV0ToV1$1).parser;
1936
122
 
1937
- // prettier-ignore
123
+ const falseToNull=pipeParsers(constant(false)).then(convert(()=>null)).parser;const parseGradedGroupWidgetOptions=object({title:defaulted(string,()=>""),hasHint:optional(nullable(boolean)),hint:union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal,ctx)=>parsePerseusRenderer(rawVal,ctx)).parser,content:string,widgets:(rawVal,ctx)=>parseWidgetsMap(rawVal,ctx),widgetEnabled:optional(nullable(boolean)),immutableWidgets:optional(nullable(boolean)),images:record(string,object({width:number,height:number}))});const parseGradedGroupWidget=parseWidget(constant("graded-group"),parseGradedGroupWidgetOptions);
1938
124
 
1939
- // prettier-ignore
125
+ const parseGradedGroupSetWidget=parseWidget(constant("graded-group-set"),object({gradedGroups:array(parseGradedGroupWidgetOptions)}));
1940
126
 
1941
- // prettier-ignore
127
+ function discriminatedUnionOn(discriminantKey){const noMoreBranches=(raw,ctx)=>{if(!isObject(raw)){return ctx.failure("object",raw)}return ctx.forSubtree(discriminantKey).failure("a valid value",raw[discriminantKey])};return new DiscriminatedUnionBuilder(discriminantKey,noMoreBranches)}class DiscriminatedUnionBuilder{withBranch(discriminantValue,parseNewVariant){const parseNewBranch=discriminatedUnionBranch(this.discriminantKey,discriminantValue,parseNewVariant,this.parser);return new DiscriminatedUnionBuilder(this.discriminantKey,parseNewBranch)}constructor(discriminantKey,parser){this.discriminantKey=discriminantKey;this.parser=parser;}}function discriminatedUnionBranch(discriminantKey,discriminantValue,parseVariant,parseOtherBranches){return (raw,ctx)=>{if(!isObject(raw)){return ctx.failure("object",raw)}if(raw[discriminantKey]===discriminantValue){return parseVariant(raw,ctx)}return parseOtherBranches(raw,ctx)}}
1942
128
 
1943
- // prettier-ignore
129
+ const pairOfNumbers$3=pair(number,number);const pairOfPoints=pair(pairOfNumbers$3,pairOfNumbers$3);const parseGrapherWidget=parseWidget(constant("grapher"),object({availableTypes:array(enumeration("absolute_value","exponential","linear","logarithm","quadratic","sinusoid","tangent")),correct:discriminatedUnionOn("type").withBranch("absolute_value",object({type:constant("absolute_value"),coords:nullable(pairOfPoints)})).withBranch("exponential",object({type:constant("exponential"),asymptote:pairOfPoints,coords:nullable(pairOfPoints)})).withBranch("linear",object({type:constant("linear"),coords:nullable(pairOfPoints)})).withBranch("logarithm",object({type:constant("logarithm"),asymptote:pairOfPoints,coords:nullable(pairOfPoints)})).withBranch("quadratic",object({type:constant("quadratic"),coords:nullable(pairOfPoints)})).withBranch("sinusoid",object({type:constant("sinusoid"),coords:nullable(pairOfPoints)})).withBranch("tangent",object({type:constant("tangent"),coords:nullable(pairOfPoints)})).parser,graph:object({backgroundImage:object({bottom:optional(number),height:optional(number),left:optional(number),scale:optional(number),url:optional(nullable(string)),width:optional(number)}),box:optional(pairOfNumbers$3),editableSettings:optional(array(enumeration("graph","snap","image","measure"))),gridStep:optional(pairOfNumbers$3),labels:pair(string,string),markings:enumeration("graph","none","grid"),range:pair(pairOfNumbers$3,pairOfNumbers$3),rulerLabel:constant(""),rulerTicks:number,showProtractor:optional(boolean),showRuler:optional(boolean),showTooltips:optional(boolean),snapStep:optional(pairOfNumbers$3),step:pairOfNumbers$3,valid:optional(union(boolean).or(string).parser)})}));
1944
130
 
1945
- // prettier-ignore
131
+ const parseGroupWidget=parseWidget(constant("group"),(rawVal,ctx)=>parsePerseusRenderer(rawVal,ctx));
1946
132
 
1947
- // prettier-ignore
133
+ const parseIframeWidget=parseWidget(constant("iframe"),object({url:string,settings:optional(array(object({name:string,value:string}))),width:union(number).or(string).parser,height:union(number).or(string).parser,allowFullScreen:defaulted(boolean,()=>false),allowTopNavigation:optional(boolean),static:defaulted(boolean,()=>false)}));
1948
134
 
1949
- // prettier-ignore
135
+ const stringToNumber=(rawValue,ctx)=>{if(typeof rawValue==="number"){return ctx.success(rawValue)}const parsedNumber=+rawValue;if(rawValue===""||isNaN(parsedNumber)){return ctx.failure("a number or numeric string",rawValue)}return ctx.success(parsedNumber)};
1950
136
 
1951
- // prettier-ignore
137
+ function emptyToZero(x){return x===""?0:x}const imageDimensionToNumber=pipeParsers(union(number).or(string).parser).then(convert(emptyToZero)).then(stringToNumber).parser;const dimensionOrUndefined=defaulted(imageDimensionToNumber,()=>undefined);const parsePerseusImageBackground=object({url:optional(nullable(string)),width:dimensionOrUndefined,height:dimensionOrUndefined,top:dimensionOrUndefined,left:dimensionOrUndefined,bottom:dimensionOrUndefined,scale:dimensionOrUndefined});
1952
138
 
1953
- // prettier-ignore
139
+ const pairOfNumbers$2=pair(number,number);const parseImageWidget=parseWidget(constant("image"),object({title:optional(string),caption:optional(string),alt:optional(string),backgroundImage:parsePerseusImageBackground,static:optional(boolean),labels:optional(array(object({content:string,alignment:string,coordinates:array(number)}))),range:optional(pair(pairOfNumbers$2,pairOfNumbers$2)),box:optional(pairOfNumbers$2)}));
1954
140
 
1955
- // prettier-ignore
141
+ const booleanToString=(rawValue,ctx)=>{if(typeof rawValue==="boolean"){return ctx.success(String(rawValue))}return ctx.failure("boolean",rawValue)};const parseInputNumberWidget=parseWidget(constant("input-number"),object({answerType:optional(enumeration("number","decimal","integer","rational","improper","mixed","percent","pi")),inexact:optional(boolean),maxError:optional(union(number).or(string).parser),rightAlign:optional(boolean),simplify:enumeration("required","optional","enforced"),size:enumeration("normal","small"),value:union(number).or(string).or(booleanToString).parser,customKeypad:optional(boolean)}));
1956
142
 
1957
- // prettier-ignore
143
+ const pairOfNumbers$1=pair(number,number);const stringOrEmpty=defaulted(string,()=>"");const parseKey=pipeParsers(optional(string)).then(convert(String)).parser;const parseFunctionElement=object({type:constant("function"),key:parseKey,options:object({value:string,funcName:string,rangeMin:string,rangeMax:string,color:string,strokeDasharray:string,strokeWidth:number})});const parseLabelElement=object({type:constant("label"),key:parseKey,options:object({label:string,color:string,coordX:string,coordY:string})});const parseLineElement=object({type:constant("line"),key:parseKey,options:object({color:string,startX:string,startY:string,endX:string,endY:string,strokeDasharray:string,strokeWidth:number,arrows:string})});const parseMovableLineElement=object({type:constant("movable-line"),key:parseKey,options:object({startX:string,startY:string,startSubscript:number,endX:string,endY:string,endSubscript:number,constraint:string,snap:number,constraintFn:string,constraintXMin:string,constraintXMax:string,constraintYMin:string,constraintYMax:string})});const parseMovablePointElement=object({type:constant("movable-point"),key:parseKey,options:object({startX:string,startY:string,varSubscript:number,constraint:string,snap:number,constraintFn:string,constraintXMin:stringOrEmpty,constraintXMax:stringOrEmpty,constraintYMin:stringOrEmpty,constraintYMax:stringOrEmpty})});const parseParametricElement=object({type:constant("parametric"),key:parseKey,options:object({x:string,y:string,rangeMin:string,rangeMax:string,color:string,strokeDasharray:string,strokeWidth:number})});const parsePointElement=object({type:constant("point"),key:parseKey,options:object({color:string,coordX:string,coordY:string})});const parseRectangleElement=object({type:constant("rectangle"),key:parseKey,options:object({color:string,coordX:string,coordY:string,width:string,height:string})});const parseInteractionWidget=parseWidget(constant("interaction"),object({static:defaulted(boolean,()=>false),graph:object({editableSettings:optional(array(enumeration("canvas","graph"))),box:pairOfNumbers$1,labels:array(string),range:pair(pairOfNumbers$1,pairOfNumbers$1),gridStep:pairOfNumbers$1,markings:enumeration("graph","grid","none"),snapStep:optional(pairOfNumbers$1),valid:optional(union(boolean).or(string).parser),backgroundImage:optional(parsePerseusImageBackground),showProtractor:optional(boolean),showRuler:optional(boolean),rulerLabel:optional(string),rulerTicks:optional(number),tickStep:pairOfNumbers$1}),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)}));
1958
144
 
1959
- // prettier-ignore
145
+ const ItemExtras=["calculator","chi2Table","financialCalculatorMonthlyPayment","financialCalculatorTotalAmount","financialCalculatorTimeToPayOff","periodicTable","periodicTableWithKey","tTable","zTable"];const PerseusExpressionAnswerFormConsidered=["correct","wrong","ungraded"];const lockedFigureColorNames=["blue","green","grayH","purple","pink","orange","red"];const lockedFigureColors={blue:"#3D7586",green:"#447A53",grayH:"#3B3D45",purple:"#594094",pink:"#B25071",red:"#D92916",orange:"#946700"};const lockedFigureFillStyles={none:0,white:1,translucent:.4,solid:1};const plotterPlotTypes=["bar","line","pic","histogram","dotplot"];
1960
146
 
1961
- //prettier-ignore
147
+ const pairOfNumbers=pair(number,number);const parsePerseusGraphTypeAngle=object({type:constant("angle"),showAngles:optional(boolean),allowReflexAngles:optional(boolean),angleOffsetDeg:optional(number),snapDegrees:optional(number),match:optional(constant("congruent")),coords:optional(trio(pairOfNumbers,pairOfNumbers,pairOfNumbers)),startCoords:optional(trio(pairOfNumbers,pairOfNumbers,pairOfNumbers))});const parsePerseusGraphTypeCircle=object({type:constant("circle"),center:optional(pairOfNumbers),radius:optional(number),startCoords:optional(object({center:pairOfNumbers,radius:number})),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeLinear=object({type:constant("linear"),coords:optional(nullable(pair(pairOfNumbers,pairOfNumbers))),startCoords:optional(pair(pairOfNumbers,pairOfNumbers)),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeLinearSystem=object({type:constant("linear-system"),coords:optional(nullable(array(pair(pairOfNumbers,pairOfNumbers)))),startCoords:optional(array(pair(pairOfNumbers,pairOfNumbers))),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeNone=object({type:constant("none")});const parsePerseusGraphTypePoint=object({type:constant("point"),numPoints:optional(union(number).or(constant("unlimited")).parser),coords:optional(nullable(array(pairOfNumbers))),startCoords:optional(array(pairOfNumbers)),coord:optional(pairOfNumbers)});const parsePerseusGraphTypePolygon=object({type:constant("polygon"),numSides:optional(union(number).or(constant("unlimited")).parser),showAngles:optional(boolean),showSides:optional(boolean),snapTo:optional(enumeration("grid","angles","sides")),match:optional(enumeration("similar","congruent","approx","exact")),startCoords:optional(array(pairOfNumbers)),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeQuadratic=object({type:constant("quadratic"),coords:optional(nullable(trio(pairOfNumbers,pairOfNumbers,pairOfNumbers))),startCoords:optional(trio(pairOfNumbers,pairOfNumbers,pairOfNumbers)),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeRay=object({type:constant("ray"),coords:optional(nullable(pair(pairOfNumbers,pairOfNumbers))),startCoords:optional(pair(pairOfNumbers,pairOfNumbers)),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeSegment=object({type:constant("segment"),numSegments:optional(number),coords:optional(nullable(array(pair(pairOfNumbers,pairOfNumbers)))),startCoords:optional(array(pair(pairOfNumbers,pairOfNumbers))),coord:optional(pairOfNumbers)});const parsePerseusGraphTypeSinusoid=object({type:constant("sinusoid"),coords:optional(nullable(array(pairOfNumbers))),startCoords:optional(array(pairOfNumbers)),coord:optional(pairOfNumbers)});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;const parseLockedFigureColor=enumeration(...lockedFigureColorNames);const parseLockedFigureFillType=enumeration("none","white","translucent","solid");const parseLockedLineStyle=enumeration("solid","dashed");const parseLockedLabelType=object({type:constant("label"),coord:pairOfNumbers,text:string,color:parseLockedFigureColor,size:enumeration("small","medium","large")});const parseLockedPointType=object({type:constant("point"),coord:pairOfNumbers,color:parseLockedFigureColor,filled:boolean,labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});const parseLockedLineType=object({type:constant("line"),kind:enumeration("line","ray","segment"),points:pair(parseLockedPointType,parseLockedPointType),color:parseLockedFigureColor,lineStyle:parseLockedLineStyle,showPoint1:defaulted(boolean,()=>false),showPoint2:defaulted(boolean,()=>false),labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});const parseLockedVectorType=object({type:constant("vector"),points:pair(pairOfNumbers,pairOfNumbers),color:parseLockedFigureColor,labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});const parseLockedEllipseType=object({type:constant("ellipse"),center:pairOfNumbers,radius:pairOfNumbers,angle:number,color:parseLockedFigureColor,fillStyle:parseLockedFigureFillType,strokeStyle:parseLockedLineStyle,labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});const parseLockedPolygonType=object({type:constant("polygon"),points:array(pairOfNumbers),color:parseLockedFigureColor,showVertices:boolean,fillStyle:parseLockedFigureFillType,strokeStyle:parseLockedLineStyle,labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});const parseLockedFunctionDomain=defaulted(pair(defaulted(number,()=>-Infinity),defaulted(number,()=>Infinity)),()=>[-Infinity,Infinity]);const parseLockedFunctionType=object({type:constant("function"),color:parseLockedFigureColor,strokeStyle:parseLockedLineStyle,equation:string,directionalAxis:enumeration("x","y"),domain:parseLockedFunctionDomain,labels:optional(array(parseLockedLabelType)),ariaLabel:optional(string)});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;const parseInteractiveGraphWidget=parseWidget(constant("interactive-graph"),object({step:pairOfNumbers,gridStep:optional(pairOfNumbers),snapStep:optional(pairOfNumbers),backgroundImage:optional(parsePerseusImageBackground),markings:enumeration("graph","grid","none"),labels:optional(array(string)),labelLocation:optional(enumeration("onAxis","alongEdge")),showProtractor:boolean,showRuler:optional(boolean),showTooltips:optional(boolean),rulerLabel:optional(string),rulerTicks:optional(number),range:pair(pairOfNumbers,pairOfNumbers),graph:defaulted(parsePerseusGraphType,()=>({type:"linear"})),correct:parsePerseusGraphType,lockedFigures:optional(array(parseLockedFigure)),fullGraphLabel:optional(string),fullGraphAriaDescription:optional(string)}));
1962
148
 
1963
- /**
1964
- * A background image applied to various widgets.
1965
- */
149
+ const parseLabelImageWidget=parseWidget(constant("label-image"),object({choices:array(string),imageUrl:string,imageAlt:string,imageHeight:number,imageWidth:number,markers:array(object({answers:array(string),label:string,x:number,y:number})),hideChoicesFromInstructions:boolean,multipleAnswers:boolean,static:defaulted(boolean,()=>false)}));
1966
150
 
1967
- /**
1968
- * The type of markings to display on the graph.
1969
- * - axes: shows the axes without the gride lines
1970
- * - graph: shows the axes and the grid lines
1971
- * - grid: shows only the grid lines
1972
- * - none: shows no markings
1973
- */
151
+ const parseMatcherWidget=parseWidget(constant("matcher"),object({labels:array(string),left:array(string),right:array(string),orderMatters:boolean,padding:boolean}));
1974
152
 
1975
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
153
+ const numberOrString=union(number).or(string).parser;const numeric=pipeParsers(defaulted(numberOrString,()=>NaN)).then(stringToNumber).parser;const parseMatrixWidget=parseWidget(defaulted(constant("matrix"),()=>"matrix"),object({prefix:optional(string),suffix:optional(string),answers:array(array(numeric)),cursorPosition:optional(array(number)),matrixBoardSize:array(number),static:optional(boolean)}));
1976
154
 
1977
- // 2D range: xMin, xMax, yMin, yMax
155
+ const parseMeasurerWidget=parseWidget(constant("measurer"),object({image:defaulted(parsePerseusImageBackground,()=>({url:null,top:0,left:0})),showProtractor:boolean,showRuler:boolean,rulerLabel:string,rulerTicks:number,rulerPixels:number,rulerLength:number,box:pair(number,number),static:defaulted(boolean,()=>false)}));
1978
156
 
1979
- const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
1980
- const lockedFigureColors = {
1981
- blue: "#3D7586",
1982
- green: "#447A53",
1983
- grayH: "#3B3D45",
1984
- purple: "#594094",
1985
- pink: "#B25071",
1986
- red: "#D92916",
1987
- orange: "#946700"
1988
- };
1989
- const lockedFigureFillStyles = {
1990
- none: 0,
1991
- white: 1,
1992
- translucent: 0.4,
1993
- solid: 1
1994
- };
157
+ const parseMoleculeRendererWidget=parseWidget(constant("molecule-renderer"),object({widgetId:string,rotationAngle:optional(number),smiles:optional(string)}));
1995
158
 
1996
- // Not associated with a specific figure
159
+ const emptyStringToNull=pipeParsers(constant("")).then(convert(()=>null)).parser;const parseNumberLineWidget=parseWidget(constant("number-line"),object({range:array(number),labelRange:array(nullable(union(number).or(emptyStringToNull).parser)),labelStyle:string,labelTicks:boolean,isTickCtrl:optional(nullable(boolean)),divisionRange:array(number),numDivisions:optional(nullable(number)),snapDivisions:defaulted(number,()=>2),tickStep:optional(nullable(number)),correctRel:optional(nullable(string)),correctX:nullable(number),initialX:optional(nullable(number)),showTooltips:optional(boolean),static:defaulted(boolean,()=>false)}));
1997
160
 
1998
- /**
1999
- * Determines how unsimplified fractions are scored.
2000
- *
2001
- * - "required" means unsimplified fractions are considered invalid input, and
2002
- * the learner can try again.
2003
- * - "enforced" means unsimplified fractions are marked incorrect.
2004
- * - "optional" means unsimplified fractions are accepted.
2005
- */
161
+ const parseMathFormat=enumeration("integer","mixed","improper","proper","decimal","percent","pi");const parseSimplify=pipeParsers(union(constant(null)).or(constant(undefined)).or(boolean).or(constant("required")).or(constant("correct")).or(constant("enforced")).or(constant("optional")).or(constant("accepted")).parser).then(convert(deprecatedSimplifyValuesToRequired)).parser;function deprecatedSimplifyValuesToRequired(simplify){switch(simplify){case "enforced":case "required":case "optional":return simplify;default:return "required"}}const parseNumericInputWidget=parseWidget(constant("numeric-input"),object({answers:array(object({message:string,value:optional(nullable(number)),status:string,answerForms:defaulted(array(parseMathFormat),()=>undefined),strict:boolean,maxError:optional(nullable(number)),simplify:parseSimplify})),labelText:optional(string),size:string,coefficient:defaulted(boolean,()=>false),rightAlign:optional(boolean),static:defaulted(boolean,()=>false),answerForms:optional(array(object({name:parseMathFormat,simplify:parseSimplify})))}));
2006
162
 
2007
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
163
+ function parseRenderer(rawValue,ctx){return parsePerseusRenderer(rawValue,ctx)}const largeToAuto=(height,ctx)=>{if(height==="large"){return ctx.success("auto")}return ctx.success(height)};const parseOrdererWidget=parseWidget(constant("orderer"),object({options:defaulted(array(parseRenderer),()=>[]),correctOptions:array(parseRenderer),otherOptions:array(parseRenderer),height:pipeParsers(enumeration("normal","auto","large")).then(largeToAuto).parser,layout:defaulted(enumeration("horizontal","vertical"),()=>"horizontal")}));
2008
164
 
2009
- // Used to represent 2-D points and ranges
2010
- const pairOfNumbers = pair(number, number);
2011
- const parsePerseusGraphTypeAngle = object({
2012
- type: constant("angle"),
2013
- showAngles: optional(boolean),
2014
- allowReflexAngles: optional(boolean),
2015
- angleOffsetDeg: optional(number),
2016
- snapDegrees: optional(number),
2017
- match: optional(constant("congruent")),
2018
- coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
2019
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
2020
- });
2021
- const parsePerseusGraphTypeCircle = object({
2022
- type: constant("circle"),
2023
- center: optional(pairOfNumbers),
2024
- radius: optional(number),
2025
- startCoords: optional(object({
2026
- center: pairOfNumbers,
2027
- radius: number
2028
- })),
2029
- // TODO: remove coord? it's legacy.
2030
- coord: optional(pairOfNumbers)
2031
- });
2032
- const parsePerseusGraphTypeLinear = object({
2033
- type: constant("linear"),
2034
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
2035
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
2036
- // TODO: remove coord? it's legacy.
2037
- coord: optional(pairOfNumbers)
2038
- });
2039
- const parsePerseusGraphTypeLinearSystem = object({
2040
- type: constant("linear-system"),
2041
- // TODO(benchristel): default coords to empty array?
2042
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
2043
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
2044
- // TODO: remove coord? it's legacy.
2045
- coord: optional(pairOfNumbers)
2046
- });
2047
- const parsePerseusGraphTypeNone = object({
2048
- type: constant("none")
2049
- });
2050
- const parsePerseusGraphTypePoint = object({
2051
- type: constant("point"),
2052
- numPoints: optional(union(number).or(constant("unlimited")).parser),
2053
- coords: optional(nullable(array(pairOfNumbers))),
2054
- startCoords: optional(array(pairOfNumbers)),
2055
- // TODO: remove coord? it's legacy.
2056
- coord: optional(pairOfNumbers)
2057
- });
2058
- const parsePerseusGraphTypePolygon = object({
2059
- type: constant("polygon"),
2060
- numSides: optional(union(number).or(constant("unlimited")).parser),
2061
- showAngles: optional(boolean),
2062
- showSides: optional(boolean),
2063
- snapTo: optional(enumeration("grid", "angles", "sides")),
2064
- match: optional(enumeration("similar", "congruent", "approx", "exact")),
2065
- startCoords: optional(array(pairOfNumbers)),
2066
- // TODO: remove coord? it's legacy.
2067
- coord: optional(pairOfNumbers)
2068
- });
2069
- const parsePerseusGraphTypeQuadratic = object({
2070
- type: constant("quadratic"),
2071
- coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
2072
- startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
2073
- // TODO: remove coord? it's legacy.
2074
- coord: optional(pairOfNumbers)
2075
- });
2076
- const parsePerseusGraphTypeRay = object({
2077
- type: constant("ray"),
2078
- coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
2079
- startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
2080
- // TODO: remove coord? it's legacy.
2081
- coord: optional(pairOfNumbers)
2082
- });
2083
- const parsePerseusGraphTypeSegment = object({
2084
- type: constant("segment"),
2085
- // TODO(benchristel): default numSegments?
2086
- numSegments: optional(number),
2087
- coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
2088
- startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
2089
- // TODO: remove coord? it's legacy.
2090
- coord: optional(pairOfNumbers)
2091
- });
2092
- const parsePerseusGraphTypeSinusoid = object({
2093
- type: constant("sinusoid"),
2094
- coords: optional(nullable(array(pairOfNumbers))),
2095
- startCoords: optional(array(pairOfNumbers)),
2096
- // TODO: remove coord? it's legacy.
2097
- coord: optional(pairOfNumbers)
2098
- });
2099
- 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;
2100
- const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
2101
- const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
2102
- const parseLockedLineStyle = enumeration("solid", "dashed");
2103
- const parseLockedLabelType = object({
2104
- type: constant("label"),
2105
- coord: pairOfNumbers,
2106
- text: string,
2107
- color: parseLockedFigureColor,
2108
- size: enumeration("small", "medium", "large")
2109
- });
2110
- const parseLockedPointType = object({
2111
- type: constant("point"),
2112
- coord: pairOfNumbers,
2113
- color: parseLockedFigureColor,
2114
- filled: boolean,
2115
- // TODO(benchristel): default labels to empty array?
2116
- labels: optional(array(parseLockedLabelType)),
2117
- ariaLabel: optional(string)
2118
- });
2119
- const parseLockedLineType = object({
2120
- type: constant("line"),
2121
- kind: enumeration("line", "ray", "segment"),
2122
- points: pair(parseLockedPointType, parseLockedPointType),
2123
- color: parseLockedFigureColor,
2124
- lineStyle: parseLockedLineStyle,
2125
- showPoint1: defaulted(boolean, () => false),
2126
- showPoint2: defaulted(boolean, () => false),
2127
- // TODO(benchristel): default labels to empty array?
2128
- labels: optional(array(parseLockedLabelType)),
2129
- ariaLabel: optional(string)
2130
- });
2131
- const parseLockedVectorType = object({
2132
- type: constant("vector"),
2133
- points: pair(pairOfNumbers, pairOfNumbers),
2134
- color: parseLockedFigureColor,
2135
- // TODO(benchristel): default labels to empty array?
2136
- labels: optional(array(parseLockedLabelType)),
2137
- ariaLabel: optional(string)
2138
- });
2139
- const parseLockedEllipseType = object({
2140
- type: constant("ellipse"),
2141
- center: pairOfNumbers,
2142
- radius: pairOfNumbers,
2143
- angle: number,
2144
- color: parseLockedFigureColor,
2145
- fillStyle: parseLockedFigureFillType,
2146
- strokeStyle: parseLockedLineStyle,
2147
- // TODO(benchristel): default labels to empty array?
2148
- labels: optional(array(parseLockedLabelType)),
2149
- ariaLabel: optional(string)
2150
- });
2151
- const parseLockedPolygonType = object({
2152
- type: constant("polygon"),
2153
- points: array(pairOfNumbers),
2154
- color: parseLockedFigureColor,
2155
- showVertices: boolean,
2156
- fillStyle: parseLockedFigureFillType,
2157
- strokeStyle: parseLockedLineStyle,
2158
- // TODO(benchristel): default labels to empty array?
2159
- labels: optional(array(parseLockedLabelType)),
2160
- ariaLabel: optional(string)
2161
- });
165
+ const parsePassageRefWidget=parseWidget(constant("passage-ref"),object({passageNumber:number,referenceNumber:number,summaryText:optional(string)}));
2162
166
 
2163
- // Exported for testing.
2164
- const parseLockedFunctionDomain = defaulted(pair(defaulted(number, () => -Infinity), defaulted(number, () => Infinity)), () => [-Infinity, Infinity]);
2165
- const parseLockedFunctionType = object({
2166
- type: constant("function"),
2167
- color: parseLockedFigureColor,
2168
- strokeStyle: parseLockedLineStyle,
2169
- equation: string,
2170
- directionalAxis: enumeration("x", "y"),
2171
- domain: parseLockedFunctionDomain,
2172
- // TODO(benchristel): default labels to empty array?
2173
- labels: optional(array(parseLockedLabelType)),
2174
- ariaLabel: optional(string)
2175
- });
2176
- 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;
2177
- const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
2178
- step: pairOfNumbers,
2179
- // TODO(benchristel): rather than making gridStep and snapStep
2180
- // optional, we should duplicate the defaulting logic from the
2181
- // InteractiveGraph component. See parse-perseus-json/README.md for
2182
- // why.
2183
- gridStep: optional(pairOfNumbers),
2184
- snapStep: optional(pairOfNumbers),
2185
- backgroundImage: optional(parsePerseusImageBackground),
2186
- markings: enumeration("graph", "grid", "none"),
2187
- labels: optional(array(string)),
2188
- showProtractor: boolean,
2189
- showRuler: optional(boolean),
2190
- showTooltips: optional(boolean),
2191
- rulerLabel: optional(string),
2192
- rulerTicks: optional(number),
2193
- range: pair(pairOfNumbers, pairOfNumbers),
2194
- // NOTE(benchristel): I copied the default graph from
2195
- // interactive-graph.tsx. See the parse-perseus-json/README.md for
2196
- // an explanation of why we want to duplicate the default here.
2197
- graph: defaulted(parsePerseusGraphType, () => ({
2198
- type: "linear"
2199
- })),
2200
- correct: parsePerseusGraphType,
2201
- // TODO(benchristel): default lockedFigures to empty array
2202
- lockedFigures: optional(array(parseLockedFigure)),
2203
- fullGraphLabel: optional(string),
2204
- fullGraphAriaDescription: optional(string)
2205
- }));
2206
-
2207
- const parseLabelImageWidget = parseWidget(constant("label-image"), object({
2208
- choices: array(string),
2209
- imageUrl: string,
2210
- imageAlt: string,
2211
- imageHeight: number,
2212
- imageWidth: number,
2213
- markers: array(object({
2214
- answers: array(string),
2215
- label: string,
2216
- x: number,
2217
- y: number
2218
- })),
2219
- hideChoicesFromInstructions: boolean,
2220
- multipleAnswers: boolean,
2221
- static: defaulted(boolean, () => false)
2222
- }));
2223
-
2224
- const parseMatcherWidget = parseWidget(constant("matcher"), object({
2225
- labels: array(string),
2226
- left: array(string),
2227
- right: array(string),
2228
- orderMatters: boolean,
2229
- padding: boolean
2230
- }));
2231
-
2232
- const numberOrString = union(number).or(string).parser;
2233
- const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
2234
- const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
2235
- prefix: optional(string),
2236
- suffix: optional(string),
2237
- answers: array(array(numeric)),
2238
- cursorPosition: optional(array(number)),
2239
- matrixBoardSize: array(number),
2240
- static: optional(boolean)
2241
- }));
2242
-
2243
- const parseMeasurerWidget = parseWidget(constant("measurer"), object({
2244
- // The default value for image comes from measurer.tsx.
2245
- // See parse-perseus-json/README.md for why we want to duplicate the
2246
- // defaults here.
2247
- image: defaulted(parsePerseusImageBackground, () => ({
2248
- url: null,
2249
- top: 0,
2250
- left: 0
2251
- })),
2252
- showProtractor: boolean,
2253
- showRuler: boolean,
2254
- rulerLabel: string,
2255
- rulerTicks: number,
2256
- rulerPixels: number,
2257
- rulerLength: number,
2258
- box: pair(number, number),
2259
- // TODO(benchristel): static is not used. Remove it?
2260
- static: defaulted(boolean, () => false)
2261
- }));
2262
-
2263
- const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
2264
- widgetId: string,
2265
- rotationAngle: optional(number),
2266
- smiles: optional(string)
2267
- }));
2268
-
2269
- const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
2270
- const parseNumberLineWidget = parseWidget(constant("number-line"), object({
2271
- range: array(number),
2272
- labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
2273
- labelStyle: string,
2274
- labelTicks: boolean,
2275
- isTickCtrl: optional(nullable(boolean)),
2276
- divisionRange: array(number),
2277
- numDivisions: optional(nullable(number)),
2278
- // NOTE(benchristel): I copied the default snapDivisions from
2279
- // number-line.tsx. See the parse-perseus-json/README.md for
2280
- // an explanation of why we want to duplicate the default here.
2281
- snapDivisions: defaulted(number, () => 2),
2282
- tickStep: optional(nullable(number)),
2283
- correctRel: optional(nullable(string)),
2284
- correctX: nullable(number),
2285
- initialX: optional(nullable(number)),
2286
- showTooltips: optional(boolean),
2287
- static: defaulted(boolean, () => false)
2288
- }));
2289
-
2290
- const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
2291
- const parseSimplify = pipeParsers(union(constant(null)).or(constant(undefined)).or(boolean).or(constant("required")).or(constant("correct")).or(constant("enforced")).or(constant("optional")).or(constant("accepted")).parser).then(convert(deprecatedSimplifyValuesToRequired)).parser;
2292
- function deprecatedSimplifyValuesToRequired(simplify) {
2293
- switch (simplify) {
2294
- case "enforced":
2295
- case "required":
2296
- case "optional":
2297
- return simplify;
2298
- // NOTE(benchristel): "accepted", "correct", true, false, undefined, and
2299
- // null are all treated the same as "required" during scoring, so we
2300
- // convert them to "required" here to preserve behavior. See the tests
2301
- // in score-numeric-input.test.ts
2302
- default:
2303
- return "required";
2304
- }
2305
- }
2306
- const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2307
- answers: array(object({
2308
- message: string,
2309
- // TODO(benchristel): value should never be null or undefined,
2310
- // but we have some content where it is anyway. If we backfill
2311
- // the data, simplify this.
2312
- value: optional(nullable(number)),
2313
- status: string,
2314
- answerForms: defaulted(array(parseMathFormat), () => undefined),
2315
- strict: boolean,
2316
- maxError: optional(nullable(number)),
2317
- // TODO(benchristel): simplify should never be a boolean, but we
2318
- // have some content where it is anyway. If we ever backfill
2319
- // the data, we should simplify `simplify`.
2320
- simplify: parseSimplify
2321
- })),
2322
- labelText: optional(string),
2323
- size: string,
2324
- coefficient: defaulted(boolean, () => false),
2325
- rightAlign: optional(boolean),
2326
- static: defaulted(boolean, () => false),
2327
- answerForms: optional(array(object({
2328
- name: parseMathFormat,
2329
- simplify: parseSimplify
2330
- })))
2331
- }));
2332
-
2333
- // There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
2334
- // This wrapper ensures that we don't refer to parsePerseusRenderer before
2335
- // it's defined.
2336
- function parseRenderer(rawValue, ctx) {
2337
- return parsePerseusRenderer(rawValue, ctx);
2338
- }
2339
- const largeToAuto = (height, ctx) => {
2340
- if (height === "large") {
2341
- return ctx.success("auto");
2342
- }
2343
- return ctx.success(height);
2344
- };
2345
- const parseOrdererWidget = parseWidget(constant("orderer"), object({
2346
- options: defaulted(array(parseRenderer), () => []),
2347
- correctOptions: array(parseRenderer),
2348
- otherOptions: array(parseRenderer),
2349
- height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
2350
- layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
2351
- }));
2352
-
2353
- const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
2354
- passageNumber: number,
2355
- referenceNumber: number,
2356
- summaryText: optional(string)
2357
- }));
2358
-
2359
- const parsePassageWidget = parseWidget(constant("passage"), object({
2360
- footnotes: defaulted(string, () => ""),
2361
- passageText: string,
2362
- passageTitle: defaulted(string, () => ""),
2363
- showLineNumbers: boolean,
2364
- static: defaulted(boolean, () => false)
2365
- }));
2366
-
2367
- const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
2368
- url: string,
2369
- description: string
2370
- }));
2371
-
2372
- const parsePlotterWidget = parseWidget(constant("plotter"), object({
2373
- labels: array(string),
2374
- categories: array(string),
2375
- type: enumeration(...plotterPlotTypes),
2376
- maxY: number,
2377
- // The default value for scaleY comes from plotter.tsx.
2378
- // See parse-perseus-json/README.md for why we want to duplicate the
2379
- // defaults here.
2380
- scaleY: defaulted(number, () => 1),
2381
- labelInterval: optional(nullable(number)),
2382
- // The default value for snapsPerLine comes from plotter.tsx.
2383
- // See parse-perseus-json/README.md for why we want to duplicate the
2384
- // defaults here.
2385
- snapsPerLine: defaulted(number, () => 2),
2386
- starting: array(number),
2387
- correct: array(number),
2388
- picUrl: optional(nullable(string)),
2389
- picSize: optional(nullable(number)),
2390
- picBoxHeight: optional(nullable(number)),
2391
- // NOTE(benchristel): I copied the default plotDimensions from
2392
- // plotter.tsx. See the parse-perseus-json/README.md for an explanation
2393
- // of why we want to duplicate the defaults here.
2394
- plotDimensions: defaulted(array(number), () => [380, 300])
2395
- }));
2396
-
2397
- const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
2398
- programID: string,
2399
- height: number
2400
- }));
2401
-
2402
- const currentVersion$3 = {
2403
- major: 2,
2404
- minor: 0
2405
- };
2406
- function deriveNumCorrect(options) {
2407
- const {
2408
- choices,
2409
- numCorrect
2410
- } = options;
2411
- return numCorrect ?? choices.filter(c => c.correct).length;
2412
- }
2413
- const widgetOptionsUpgrades$2 = {
2414
- "2": v1props => {
2415
- const upgraded = {
2416
- ...v1props,
2417
- numCorrect: deriveNumCorrect(v1props)
2418
- };
2419
- return upgraded;
2420
- },
2421
- "1": v0props => {
2422
- const {
2423
- noneOfTheAbove,
2424
- ...rest
2425
- } = v0props;
2426
- if (noneOfTheAbove) {
2427
- throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
2428
- }
2429
- return {
2430
- ...rest,
2431
- hasNoneOfTheAbove: false
2432
- };
2433
- }
2434
- };
2435
- const defaultWidgetOptions$v = {
2436
- choices: [{}, {}, {}, {}],
2437
- displayCount: null,
2438
- randomize: false,
2439
- hasNoneOfTheAbove: false,
2440
- multipleSelect: false,
2441
- countChoices: false,
2442
- deselectEnabled: false
2443
- };
2444
-
2445
- const version2 = optional(object({
2446
- major: constant(2),
2447
- minor: number
2448
- }));
2449
- const parseRadioWidgetV2 = parseWidgetWithVersion(version2, constant("radio"), object({
2450
- numCorrect: optional(number),
2451
- choices: array(object({
2452
- content: defaulted(string, () => ""),
2453
- clue: optional(string),
2454
- correct: optional(boolean),
2455
- isNoneOfTheAbove: optional(boolean),
2456
- // deprecated
2457
- // There is an import cycle between radio-widget.ts and
2458
- // widgets-map.ts. The anonymous function below ensures that we
2459
- // don't refer to parseWidgetsMap before it's defined.
2460
- widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
2461
- })),
2462
- hasNoneOfTheAbove: optional(boolean),
2463
- countChoices: optional(boolean),
2464
- randomize: optional(boolean),
2465
- multipleSelect: optional(boolean),
2466
- deselectEnabled: optional(boolean),
2467
- // deprecated
2468
- onePerLine: optional(boolean),
2469
- // deprecated
2470
- displayCount: optional(any),
2471
- // v0 props
2472
- // `noneOfTheAbove` is still in use (but only set to `false`).
2473
- noneOfTheAbove: optional(constant(false))
2474
- }));
2475
- const version1 = optional(object({
2476
- major: constant(1),
2477
- minor: number
2478
- }));
2479
- const parseRadioWidgetV1 = parseWidgetWithVersion(version1, constant("radio"), object({
2480
- choices: array(object({
2481
- content: defaulted(string, () => ""),
2482
- clue: optional(string),
2483
- correct: optional(boolean),
2484
- isNoneOfTheAbove: optional(boolean),
2485
- // deprecated
2486
- // There is an import cycle between radio-widget.ts and
2487
- // widgets-map.ts. The anonymous function below ensures that we
2488
- // don't refer to parseWidgetsMap before it's defined.
2489
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => undefined)
2490
- })),
2491
- hasNoneOfTheAbove: optional(boolean),
2492
- countChoices: optional(boolean),
2493
- randomize: optional(boolean),
2494
- multipleSelect: optional(boolean),
2495
- deselectEnabled: optional(boolean),
2496
- // deprecated
2497
- onePerLine: optional(boolean),
2498
- // deprecated
2499
- displayCount: optional(any),
2500
- // v0 props
2501
- // `noneOfTheAbove` is still in use (but only set to `false`).
2502
- noneOfTheAbove: optional(constant(false))
2503
- }));
2504
- function migrateV1ToV2(widget) {
2505
- const {
2506
- options
2507
- } = widget;
2508
- return {
2509
- ...widget,
2510
- version: {
2511
- major: 2,
2512
- minor: 0
2513
- },
2514
- options: {
2515
- ...options,
2516
- numCorrect: deriveNumCorrect(options)
2517
- }
2518
- };
2519
- }
2520
- const version0 = optional(object({
2521
- major: constant(0),
2522
- minor: number
2523
- }));
2524
- const parseRadioWidgetV0 = parseWidgetWithVersion(version0, constant("radio"), object({
2525
- choices: array(object({
2526
- content: defaulted(string, () => ""),
2527
- clue: optional(string),
2528
- correct: optional(boolean),
2529
- isNoneOfTheAbove: optional(boolean),
2530
- // deprecated
2531
- // There is an import cycle between radio-widget.ts and
2532
- // widgets-map.ts. The anonymous function below ensures that we
2533
- // don't refer to parseWidgetsMap before it's defined.
2534
- widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
2535
- })),
2536
- hasNoneOfTheAbove: optional(boolean),
2537
- countChoices: optional(boolean),
2538
- randomize: optional(boolean),
2539
- multipleSelect: optional(boolean),
2540
- deselectEnabled: optional(boolean),
2541
- // deprecated
2542
- onePerLine: optional(boolean),
2543
- // deprecated
2544
- displayCount: optional(any),
2545
- // v0 props
2546
- // `noneOfTheAbove` is still in use (but only set to `false`).
2547
- noneOfTheAbove: optional(constant(false))
2548
- }));
2549
- function migrateV0ToV1(widget) {
2550
- const {
2551
- options
2552
- } = widget;
2553
- const {
2554
- noneOfTheAbove: _,
2555
- ...rest
2556
- } = options;
2557
- return {
2558
- ...widget,
2559
- version: {
2560
- major: 1,
2561
- minor: 0
2562
- },
2563
- options: {
2564
- ...rest,
2565
- hasNoneOfTheAbove: false
2566
- }
2567
- };
2568
- }
2569
- const parseRadioWidget = versionedWidgetOptions(2, parseRadioWidgetV2).withMigrationFrom(1, parseRadioWidgetV1, migrateV1ToV2).withMigrationFrom(0, parseRadioWidgetV0, migrateV0ToV1).parser;
2570
-
2571
- const parseSorterWidget = parseWidget(constant("sorter"), object({
2572
- correct: array(string),
2573
- padding: boolean,
2574
- layout: enumeration("horizontal", "vertical")
2575
- }));
2576
-
2577
- const parseTableWidget = parseWidget(constant("table"), object({
2578
- headers: array(string),
2579
- rows: number,
2580
- columns: number,
2581
- answers: array(array(string))
2582
- }));
2583
-
2584
- const parseVideoWidget = parseWidget(constant("video"), object({
2585
- location: string,
2586
- static: optional(boolean)
2587
- }));
2588
-
2589
- const parseWidgetsMap = (rawValue, ctx) => {
2590
- if (!isObject(rawValue)) {
2591
- return ctx.failure("PerseusWidgetsMap", rawValue);
2592
- }
2593
- const widgetsMap = {};
2594
- for (const key of Object.keys(rawValue)) {
2595
- // parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
2596
- // but it's the only way I could find to make TypeScript check the key
2597
- // against the widget type.
2598
- const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
2599
- if (isFailure(entryResult)) {
2600
- return entryResult;
2601
- }
2602
- }
2603
- return ctx.success(widgetsMap);
2604
- };
2605
- const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
2606
- let [id, widget] = _ref;
2607
- const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
2608
- if (isFailure(idComponentsResult)) {
2609
- return idComponentsResult;
2610
- }
2611
- const [type, n] = idComponentsResult.value;
2612
- function parseAndAssign(key, parse) {
2613
- const widgetResult = parse(widget, ctx);
2614
- if (isFailure(widgetResult)) {
2615
- return widgetResult;
2616
- }
2617
- widgetMap[key] = widgetResult.value;
2618
- return ctx.success(undefined);
2619
- }
2620
- switch (type) {
2621
- case "categorizer":
2622
- return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
2623
- case "cs-program":
2624
- return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
2625
- case "definition":
2626
- return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
2627
- case "dropdown":
2628
- return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
2629
- case "explanation":
2630
- return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
2631
- case "expression":
2632
- return parseAndAssign(`expression ${n}`, parseExpressionWidget);
2633
- case "grapher":
2634
- return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
2635
- case "group":
2636
- return parseAndAssign(`group ${n}`, parseGroupWidget);
2637
- case "graded-group":
2638
- return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
2639
- case "graded-group-set":
2640
- return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
2641
- case "iframe":
2642
- return parseAndAssign(`iframe ${n}`, parseIframeWidget);
2643
- case "image":
2644
- return parseAndAssign(`image ${n}`, parseImageWidget);
2645
- case "input-number":
2646
- return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
2647
- case "interaction":
2648
- return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
2649
- case "interactive-graph":
2650
- return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
2651
- case "label-image":
2652
- return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
2653
- case "matcher":
2654
- return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
2655
- case "matrix":
2656
- return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
2657
- case "measurer":
2658
- return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
2659
- case "molecule-renderer":
2660
- return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
2661
- case "number-line":
2662
- return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
2663
- case "numeric-input":
2664
- return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
2665
- case "orderer":
2666
- return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
2667
- case "passage":
2668
- return parseAndAssign(`passage ${n}`, parsePassageWidget);
2669
- case "passage-ref":
2670
- return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
2671
- case "passage-ref-target":
2672
- // NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
2673
- // used in test content. See:
2674
- // https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
2675
- return parseAndAssign(`passage-ref-target ${n}`, any);
2676
- case "phet-simulation":
2677
- return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
2678
- case "plotter":
2679
- return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
2680
- case "python-program":
2681
- return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
2682
- case "radio":
2683
- return parseAndAssign(`radio ${n}`, parseRadioWidget);
2684
- case "sorter":
2685
- return parseAndAssign(`sorter ${n}`, parseSorterWidget);
2686
- case "table":
2687
- return parseAndAssign(`table ${n}`, parseTableWidget);
2688
- case "video":
2689
- return parseAndAssign(`video ${n}`, parseVideoWidget);
2690
- case "sequence":
2691
- // sequence is a deprecated widget type, and the corresponding
2692
- // widget component no longer exists.
2693
- return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
2694
- case "lights-puzzle":
2695
- return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
2696
- case "simulator":
2697
- return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
2698
- case "transformer":
2699
- return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
2700
- default:
2701
- return parseAndAssign(`${type} ${n}`, parseWidget(constant(type), any));
2702
- }
2703
- };
2704
- const parseDeprecatedWidget = parseWidget(
2705
- // Ignore the incoming widget type and hardcode "deprecated-standin"
2706
- (_, ctx) => ctx.success("deprecated-standin"),
2707
- // Allow any widget options
2708
- object({}));
2709
- const parseStringToNonNegativeInt = (rawValue, ctx) => {
2710
- // The article renderer seems to allow the numeric part of a widget ID to
2711
- // be 0, at least for image widgets. However, if widget IDs in an exercise
2712
- // contain 0, the exercise renderer will blow up. We allow 0 here for
2713
- // compatibility with articles.
2714
- if (typeof rawValue !== "string" || !/^(0|[1-9][0-9]*)$/.test(rawValue)) {
2715
- return ctx.failure("a string representing a non-negative integer", rawValue);
2716
- }
2717
- return ctx.success(+rawValue);
2718
- };
2719
- const parseWidgetIdComponents = pair(string, parseStringToNonNegativeInt);
2720
-
2721
- const parsePerseusRenderer = defaulted(object({
2722
- // TODO(benchristel): content is also defaulted to empty string in
2723
- // renderer.tsx. See if we can remove one default or the other.
2724
- content: defaulted(string, () => ""),
2725
- // This module has an import cycle with parseWidgetsMap, because the
2726
- // `group` widget can contain another renderer.
2727
- // The anonymous function below ensures that we don't try to access
2728
- // parseWidgetsMap before it's defined.
2729
- widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
2730
- images: parseImages,
2731
- // deprecated
2732
- metadata: any
2733
- }),
2734
- // Default value
2735
- () => ({
2736
- content: "",
2737
- widgets: {},
2738
- images: {}
2739
- }));
2740
-
2741
- const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
2742
-
2743
- const parseHint = object({
2744
- replace: defaulted(boolean, () => undefined),
2745
- content: string,
2746
- widgets: defaulted(parseWidgetsMap, () => ({})),
2747
- images: parseImages,
2748
- // deprecated
2749
- metadata: any
2750
- });
167
+ const parsePassageWidget=parseWidget(constant("passage"),object({footnotes:defaulted(string,()=>""),passageText:string,passageTitle:defaulted(string,()=>""),showLineNumbers:boolean,static:defaulted(boolean,()=>false)}));
2751
168
 
2752
- const parsePerseusAnswerArea = pipeParsers(defaulted(object({}), () => ({}))).then(convert(toAnswerArea)).parser;
2753
-
2754
- // Some answerAreas have extra, bogus fields, like:
2755
- //
2756
- // "answerArea": {
2757
- // "type": "multiple",
2758
- // "options": {},
2759
- // "version": null,
2760
- // "static": false,
2761
- // "graded": false,
2762
- // "alignment": "",
2763
- // }
2764
- //
2765
- // This function filters the fields of an answerArea object, keeping only the
2766
- // known ones, and converts `undefined` and `null` values to `false`.
2767
- function toAnswerArea(raw) {
2768
- return {
2769
- zTable: !!raw.zTable,
2770
- calculator: !!raw.calculator,
2771
- chi2Table: !!raw.chi2Table,
2772
- financialCalculatorMonthlyPayment: !!raw.financialCalculatorMonthlyPayment,
2773
- financialCalculatorTotalAmount: !!raw.financialCalculatorTotalAmount,
2774
- financialCalculatorTimeToPayOff: !!raw.financialCalculatorTimeToPayOff,
2775
- periodicTable: !!raw.periodicTable,
2776
- periodicTableWithKey: !!raw.periodicTableWithKey,
2777
- tTable: !!raw.tTable
2778
- };
2779
- }
169
+ const parsePhetSimulationWidget=parseWidget(constant("phet-simulation"),object({url:string,description:string}));
2780
170
 
2781
- const parsePerseusItem$1 = object({
2782
- question: parsePerseusRenderer,
2783
- hints: defaulted(array(parseHint), () => []),
2784
- answerArea: parsePerseusAnswerArea,
2785
- itemDataVersion: optional(nullable(object({
2786
- major: number,
2787
- minor: number
2788
- }))),
2789
- // Deprecated field
2790
- answer: any
2791
- });
171
+ const parsePlotterWidget=parseWidget(constant("plotter"),object({labels:array(string),categories:array(string),type:enumeration(...plotterPlotTypes),maxY:number,scaleY:defaulted(number,()=>1),labelInterval:optional(nullable(number)),snapsPerLine:defaulted(number,()=>2),starting:array(number),correct:array(number),picUrl:optional(nullable(string)),picSize:optional(nullable(number)),picBoxHeight:optional(nullable(number)),plotDimensions:defaulted(array(number),()=>[380,300])}));
2792
172
 
2793
- /**
2794
- * Helper to parse PerseusItem JSON
2795
- * Why not just use JSON.parse? We want:
2796
- * - To make sure types are correct
2797
- * - To give us a central place to validate/transform output if needed
2798
- * @deprecated - use parseAndMigratePerseusItem instead
2799
- * @param {string} json - the stringified PerseusItem JSON
2800
- * @returns {PerseusItem} the parsed PerseusItem object
2801
- */
2802
- function parsePerseusItem(json) {
2803
- // Try to block a cheating vector which relies on monkey-patching
2804
- // JSON.parse
2805
- if (isRealJSONParse(JSON.parse)) {
2806
- return JSON.parse(json);
2807
- }
2808
- throw new Error("Something went wrong.");
2809
- }
2810
- /**
2811
- * Parses a PerseusItem from a JSON string, migrates old formats to the latest
2812
- * schema, and runtime-typechecks the result. Use this to parse assessmentItem
2813
- * data.
2814
- *
2815
- * @returns a {@link Result} of the parsed PerseusItem. If the result is a
2816
- * failure, it will contain an error message describing where in the tree
2817
- * parsing failed.
2818
- * @throws SyntaxError if the argument is not well-formed JSON.
2819
- */
2820
- function parseAndMigratePerseusItem(json) {
2821
- throwErrorIfCheatingDetected();
2822
- const object = JSON.parse(json);
2823
- const result = parse(object, parsePerseusItem$1);
2824
- if (isFailure(result)) {
2825
- return failure({
2826
- message: result.detail,
2827
- invalidObject: object
2828
- });
2829
- }
2830
- return result;
2831
- }
173
+ const parsePythonProgramWidget=parseWidget(constant("python-program"),object({programID:string,height:number}));
2832
174
 
2833
- /**
2834
- * Parses a PerseusArticle from a JSON string, migrates old formats to the
2835
- * latest schema, and runtime-typechecks the result.
2836
- *
2837
- * @returns a {@link Result} of the parsed PerseusArticle. If the result is a
2838
- * failure, it will contain an error message describing where in the tree
2839
- * parsing failed.
2840
- * @throws SyntaxError if the argument is not well-formed JSON.
2841
- */
2842
- function parseAndMigratePerseusArticle(json) {
2843
- throwErrorIfCheatingDetected();
2844
- const object = JSON.parse(json);
2845
- const result = parse(object, parsePerseusArticle);
2846
- if (isFailure(result)) {
2847
- return failure({
2848
- message: result.detail,
2849
- invalidObject: object
2850
- });
2851
- }
2852
- return result;
2853
- }
175
+ const currentVersion$3={major:2,minor:0};function deriveNumCorrect(options){const{choices,numCorrect}=options;return numCorrect??choices.filter(c=>c.correct).length}const widgetOptionsUpgrades$2={"2":v1props=>{const upgraded={...v1props,numCorrect:deriveNumCorrect(v1props)};return upgraded},"1":v0props=>{const{noneOfTheAbove,...rest}=v0props;if(noneOfTheAbove){throw new Error("radio widget v0 no longer supports auto noneOfTheAbove")}return {...rest,hasNoneOfTheAbove:false}}};const defaultWidgetOptions$v={choices:[{},{},{},{}],displayCount:null,randomize:false,hasNoneOfTheAbove:false,multipleSelect:false,countChoices:false,deselectEnabled:false};
2854
176
 
2855
- /**
2856
- * Tries to block a cheating vector that relies on monkey-patching JSON.parse.
2857
- */
2858
- // TODO(LEMS-2331): delete this function once server-side scoring is done.
2859
- function throwErrorIfCheatingDetected() {
2860
- if (!isRealJSONParse(JSON.parse)) {
2861
- throw new Error("Something went wrong.");
2862
- }
2863
- }
177
+ const version2=optional(object({major:constant(2),minor:number}));const parseRadioWidgetV2=parseWidgetWithVersion(version2,constant("radio"),object({numCorrect:optional(number),choices:array(object({content:defaulted(string,()=>""),clue:optional(string),correct:optional(boolean),isNoneOfTheAbove:optional(boolean),widgets:optional((rawVal,ctx)=>parseWidgetsMap(rawVal,ctx))})),hasNoneOfTheAbove:optional(boolean),countChoices:optional(boolean),randomize:optional(boolean),multipleSelect:optional(boolean),deselectEnabled:optional(boolean),onePerLine:optional(boolean),displayCount:optional(any),noneOfTheAbove:optional(constant(false))}));const version1=optional(object({major:constant(1),minor:number}));const parseRadioWidgetV1=parseWidgetWithVersion(version1,constant("radio"),object({choices:array(object({content:defaulted(string,()=>""),clue:optional(string),correct:optional(boolean),isNoneOfTheAbove:optional(boolean),widgets:defaulted((rawVal,ctx)=>parseWidgetsMap(rawVal,ctx),()=>undefined)})),hasNoneOfTheAbove:optional(boolean),countChoices:optional(boolean),randomize:optional(boolean),multipleSelect:optional(boolean),deselectEnabled:optional(boolean),onePerLine:optional(boolean),displayCount:optional(any),noneOfTheAbove:optional(constant(false))}));function migrateV1ToV2(widget){const{options}=widget;return {...widget,version:{major:2,minor:0},options:{...options,numCorrect:deriveNumCorrect(options)}}}const version0=optional(object({major:constant(0),minor:number}));const parseRadioWidgetV0=parseWidgetWithVersion(version0,constant("radio"),object({choices:array(object({content:defaulted(string,()=>""),clue:optional(string),correct:optional(boolean),isNoneOfTheAbove:optional(boolean),widgets:optional((rawVal,ctx)=>parseWidgetsMap(rawVal,ctx))})),hasNoneOfTheAbove:optional(boolean),countChoices:optional(boolean),randomize:optional(boolean),multipleSelect:optional(boolean),deselectEnabled:optional(boolean),onePerLine:optional(boolean),displayCount:optional(any),noneOfTheAbove:optional(constant(false))}));function migrateV0ToV1(widget){const{options}=widget;const{noneOfTheAbove:_,...rest}=options;return {...widget,version:{major:1,minor:0},options:{...rest,hasNoneOfTheAbove:false}}}const parseRadioWidget=versionedWidgetOptions(2,parseRadioWidgetV2).withMigrationFrom(1,parseRadioWidgetV1,migrateV1ToV2).withMigrationFrom(0,parseRadioWidgetV0,migrateV0ToV1).parser;
2864
178
 
2865
- // This file is processed by a Rollup plugin (replace) to inject the production
2866
- // version number during the release build.
2867
- // In dev, you'll never see the version number.
2868
-
2869
- const libName = "@khanacademy/perseus-core";
2870
- const libVersion = "7.0.0";
2871
- perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
2872
-
2873
- /**
2874
- * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
2875
- */
2876
- const Errors = Object.freeze({
2877
- /**
2878
- * @property {ErrorKind} Unknown The kind of error is not known.
2879
- */
2880
- Unknown: "Unknown",
2881
- /**
2882
- * @property {ErrorKind} Internal The error is internal to the executing code.
2883
- */
2884
- Internal: "Internal",
2885
- /**
2886
- * @property {ErrorKind} InvalidInput There was a problem with the provided
2887
- * input, such as the wrong format or a null value.
2888
- */
2889
- InvalidInput: "InvalidInput",
2890
- /**
2891
- * @property {ErrorKind} NotAllowed There was a problem due to the state of
2892
- * the system not matching the requested operation or input. For example,
2893
- * trying to create a username that is valid, but is already taken by
2894
- * another user. Use {@link InvalidInput} instead when the input isn't
2895
- * valid regardless of the state of the system. Use {@link NotFound} when
2896
- * the failure is due to not being able to find a resource.
2897
- */
2898
- NotAllowed: "NotAllowed",
2899
- /**
2900
- * @property {ErrorKind} TransientService There was a problem when making a
2901
- * request to a service.
2902
- */
2903
- TransientService: "TransientService",
2904
- /**
2905
- * @property {ErrorKind} Service There was a non-transient problem when
2906
- * making a request to service.
2907
- */
2908
- Service: "Service"
2909
- });
179
+ const parseSorterWidget=parseWidget(constant("sorter"),object({correct:array(string),padding:boolean,layout:enumeration("horizontal","vertical")}));
2910
180
 
2911
- /**
2912
- * @type {ErrorKind} The kind of error being reported
2913
- */
2914
-
2915
- class PerseusError extends Error {
2916
- kind;
2917
- metadata;
2918
- constructor(message, kind, options) {
2919
- super(message);
2920
- this.kind = kind;
2921
- this.metadata = options?.metadata;
2922
- }
2923
- }
181
+ const parseTableWidget=parseWidget(constant("table"),object({headers:array(string),rows:number,columns:number,answers:array(array(string))}));
2924
182
 
2925
- /**
2926
- * _ utilities for objects
2927
- */
2928
-
2929
-
2930
- /**
2931
- * Does a pluck on keys inside objects in an object
2932
- *
2933
- * Ex:
2934
- * tools = {
2935
- * translation: {
2936
- * enabled: true
2937
- * },
2938
- * rotation: {
2939
- * enabled: false
2940
- * }
2941
- * };
2942
- * pluckObject(tools, "enabled") returns {
2943
- * translation: true
2944
- * rotation: false
2945
- * }
2946
- */
2947
- const pluck = function (table, subKey) {
2948
- return ___default.default.object(___default.default.map(table, function (value, key) {
2949
- return [key, value[subKey]];
2950
- }));
2951
- };
2952
-
2953
- /**
2954
- * Maps an object to an object
2955
- *
2956
- * > mapObject({a: '1', b: '2'}, (value, key) => {
2957
- * return value + 1;
2958
- * });
2959
- * {a: 2, b: 3}
2960
- */
2961
- const mapObject = function (obj, lambda) {
2962
- const result = {};
2963
- Object.keys(obj).forEach(key => {
2964
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'K'.
2965
- result[key] = lambda(obj[key], key);
2966
- });
2967
- return result;
2968
- };
2969
-
2970
- /**
2971
- * For details on the individual options, see the
2972
- * PerseusCategorizerWidgetOptions type
2973
- */
2974
-
2975
- /**
2976
- * Given a PerseusCategorizerWidgetOptions object, return a new object with only
2977
- * the public options that should be exposed to the client.
2978
- */
2979
- function getCategorizerPublicWidgetOptions(options) {
2980
- return {
2981
- items: options.items,
2982
- categories: options.categories,
2983
- randomizeItems: options.randomizeItems,
2984
- static: options.static
2985
- };
2986
- }
183
+ const parseVideoWidget=parseWidget(constant("video"),object({location:string,static:optional(boolean)}));
2987
184
 
2988
- const defaultWidgetOptions$u = {
2989
- items: [],
2990
- categories: [],
2991
- values: [],
2992
- randomizeItems: false
2993
- };
2994
- const categorizerWidgetLogic = {
2995
- name: "categorizer",
2996
- defaultWidgetOptions: defaultWidgetOptions$u,
2997
- getPublicWidgetOptions: getCategorizerPublicWidgetOptions
2998
- };
2999
-
3000
- function getCSProgramPublicWidgetOptions(options) {
3001
- return options;
3002
- }
185
+ const parseWidgetsMap=(rawValue,ctx)=>{if(!isObject(rawValue)){return ctx.failure("PerseusWidgetsMap",rawValue)}const widgetsMap={};for(const key of Object.keys(rawValue)){const entryResult=parseWidgetsMapEntry([key,rawValue[key]],widgetsMap,ctx.forSubtree(key));if(isFailure(entryResult)){return entryResult}}return ctx.success(widgetsMap)};const parseWidgetsMapEntry=([id,widget],widgetMap,ctx)=>{const idComponentsResult=parseWidgetIdComponents(id.split(" "),ctx.forSubtree("(widget ID)"));if(isFailure(idComponentsResult)){return idComponentsResult}const[type,n]=idComponentsResult.value;function parseAndAssign(key,parse){const widgetResult=parse(widget,ctx);if(isFailure(widgetResult)){return widgetResult}widgetMap[key]=widgetResult.value;return ctx.success(undefined)}switch(type){case "categorizer":return parseAndAssign(`categorizer ${n}`,parseCategorizerWidget);case "cs-program":return parseAndAssign(`cs-program ${n}`,parseCSProgramWidget);case "definition":return parseAndAssign(`definition ${n}`,parseDefinitionWidget);case "dropdown":return parseAndAssign(`dropdown ${n}`,parseDropdownWidget);case "explanation":return parseAndAssign(`explanation ${n}`,parseExplanationWidget);case "expression":return parseAndAssign(`expression ${n}`,parseExpressionWidget);case "grapher":return parseAndAssign(`grapher ${n}`,parseGrapherWidget);case "group":return parseAndAssign(`group ${n}`,parseGroupWidget);case "graded-group":return parseAndAssign(`graded-group ${n}`,parseGradedGroupWidget);case "graded-group-set":return parseAndAssign(`graded-group-set ${n}`,parseGradedGroupSetWidget);case "iframe":return parseAndAssign(`iframe ${n}`,parseIframeWidget);case "image":return parseAndAssign(`image ${n}`,parseImageWidget);case "input-number":return parseAndAssign(`input-number ${n}`,parseInputNumberWidget);case "interaction":return parseAndAssign(`interaction ${n}`,parseInteractionWidget);case "interactive-graph":return parseAndAssign(`interactive-graph ${n}`,parseInteractiveGraphWidget);case "label-image":return parseAndAssign(`label-image ${n}`,parseLabelImageWidget);case "matcher":return parseAndAssign(`matcher ${n}`,parseMatcherWidget);case "matrix":return parseAndAssign(`matrix ${n}`,parseMatrixWidget);case "measurer":return parseAndAssign(`measurer ${n}`,parseMeasurerWidget);case "molecule-renderer":return parseAndAssign(`molecule-renderer ${n}`,parseMoleculeRendererWidget);case "number-line":return parseAndAssign(`number-line ${n}`,parseNumberLineWidget);case "numeric-input":return parseAndAssign(`numeric-input ${n}`,parseNumericInputWidget);case "orderer":return parseAndAssign(`orderer ${n}`,parseOrdererWidget);case "passage":return parseAndAssign(`passage ${n}`,parsePassageWidget);case "passage-ref":return parseAndAssign(`passage-ref ${n}`,parsePassageRefWidget);case "passage-ref-target":return parseAndAssign(`passage-ref-target ${n}`,any);case "phet-simulation":return parseAndAssign(`phet-simulation ${n}`,parsePhetSimulationWidget);case "plotter":return parseAndAssign(`plotter ${n}`,parsePlotterWidget);case "python-program":return parseAndAssign(`python-program ${n}`,parsePythonProgramWidget);case "radio":return parseAndAssign(`radio ${n}`,parseRadioWidget);case "sorter":return parseAndAssign(`sorter ${n}`,parseSorterWidget);case "table":return parseAndAssign(`table ${n}`,parseTableWidget);case "video":return parseAndAssign(`video ${n}`,parseVideoWidget);case "sequence":return parseAndAssign(`sequence ${n}`,parseDeprecatedWidget);case "lights-puzzle":return parseAndAssign(`lights-puzzle ${n}`,parseDeprecatedWidget);case "simulator":return parseAndAssign(`simulator ${n}`,parseDeprecatedWidget);case "transformer":return parseAndAssign(`transformer ${n}`,parseDeprecatedWidget);default:return parseAndAssign(`${type} ${n}`,parseWidget(constant(type),any))}};const parseDeprecatedWidget=parseWidget((_,ctx)=>ctx.success("deprecated-standin"),object({}));const parseStringToNonNegativeInt=(rawValue,ctx)=>{if(typeof rawValue!=="string"||!/^(0|[1-9][0-9]*)$/.test(rawValue)){return ctx.failure("a string representing a non-negative integer",rawValue)}return ctx.success(+rawValue)};const parseWidgetIdComponents=pair(string,parseStringToNonNegativeInt);
3003
186
 
3004
- const DEFAULT_HEIGHT = 400;
3005
- const defaultWidgetOptions$t = {
3006
- programID: "",
3007
- programType: null,
3008
- settings: [{
3009
- name: "",
3010
- value: ""
3011
- }],
3012
- showEditor: false,
3013
- showButtons: false,
3014
- height: DEFAULT_HEIGHT
3015
- };
3016
- const csProgramWidgetLogic = {
3017
- name: "cs-program",
3018
- defaultWidgetOptions: defaultWidgetOptions$t,
3019
- supportedAlignments: ["block", "full-width"],
3020
- getPublicWidgetOptions: getCSProgramPublicWidgetOptions
3021
- };
3022
-
3023
- const defaultWidgetOptions$s = {
3024
- togglePrompt: "",
3025
- definition: ""
3026
- };
3027
- const definitionWidgetLogic = {
3028
- name: "definition",
3029
- defaultWidgetOptions: defaultWidgetOptions$s,
3030
- defaultAlignment: "inline"
3031
- };
3032
-
3033
- /**
3034
- * For details on the individual options, see the
3035
- * PerseusDropdownWidgetOptions type
3036
- */
3037
-
3038
- /**
3039
- * Given a PerseusDropdownWidgetOptions object, return a new object with only
3040
- * the public options that should be exposed to the client.
3041
- */
3042
- function getDropdownPublicWidgetOptions(options) {
3043
- return {
3044
- choices: options.choices.map(choice => ({
3045
- content: choice.content
3046
- })),
3047
- placeholder: options.placeholder,
3048
- static: options.static,
3049
- visibleLabel: options.visibleLabel,
3050
- ariaLabel: options.ariaLabel
3051
- };
3052
- }
187
+ const parsePerseusRenderer=defaulted(object({content:defaulted(string,()=>""),widgets:defaulted((rawVal,ctx)=>parseWidgetsMap(rawVal,ctx),()=>({})),images:parseImages,metadata:any}),()=>({content:"",widgets:{},images:{}}));
3053
188
 
3054
- const defaultWidgetOptions$r = {
3055
- placeholder: "",
3056
- choices: [{
3057
- content: "",
3058
- correct: false
3059
- }]
3060
- };
3061
- const dropdownWidgetLogic = {
3062
- name: "definition",
3063
- defaultWidgetOptions: defaultWidgetOptions$r,
3064
- defaultAlignment: "inline-block",
3065
- getPublicWidgetOptions: getDropdownPublicWidgetOptions
3066
- };
3067
-
3068
- const defaultWidgetOptions$q = {
3069
- showPrompt: "Explain",
3070
- hidePrompt: "Hide explanation",
3071
- explanation: "explanation goes here\n\nmore explanation",
3072
- widgets: {}
3073
- };
3074
- const explanationWidgetLogic = {
3075
- name: "explanation",
3076
- defaultWidgetOptions: defaultWidgetOptions$q,
3077
- defaultAlignment: "inline"
3078
- };
3079
-
3080
- const currentVersion$2 = {
3081
- major: 2,
3082
- minor: 0
3083
- };
3084
- const widgetOptionsUpgrades$1 = {
3085
- "2": v1options => {
3086
- return {
3087
- times: v1options.times,
3088
- buttonSets: v1options.buttonSets,
3089
- functions: v1options.functions,
3090
- buttonsVisible: v1options.buttonsVisible,
3091
- visibleLabel: v1options.visibleLabel,
3092
- ariaLabel: v1options.ariaLabel,
3093
- answerForms: v1options.answerForms,
3094
- extraKeys: v1options.extraKeys || deriveExtraKeys(v1options)
3095
- };
3096
- },
3097
- "1": v0options => {
3098
- return {
3099
- times: v0options.times,
3100
- buttonSets: v0options.buttonSets,
3101
- functions: v0options.functions,
3102
- buttonsVisible: v0options.buttonsVisible,
3103
- visibleLabel: v0options.visibleLabel,
3104
- ariaLabel: v0options.ariaLabel,
3105
- extraKeys: v0options.extraKeys,
3106
- answerForms: [{
3107
- considered: "correct",
3108
- form: v0options.form,
3109
- simplify: v0options.simplify,
3110
- value: v0options.value
3111
- }]
3112
- };
3113
- }
3114
- };
3115
- const defaultWidgetOptions$p = {
3116
- answerForms: [],
3117
- times: false,
3118
- buttonSets: ["basic"],
3119
- functions: ["f", "g", "h"]
3120
- };
3121
-
3122
- /**
3123
- * For details on the individual options, see the
3124
- * PerseusExpressionWidgetOptions type
3125
- */
3126
-
3127
- /**
3128
- * Given a PerseusExpressionWidgetOptions object, return a new object with only
3129
- * the public options that should be exposed to the client.
3130
- */
3131
- function getExpressionPublicWidgetOptions(options) {
3132
- return {
3133
- buttonSets: options.buttonSets,
3134
- functions: options.functions,
3135
- times: options.times,
3136
- visibleLabel: options.visibleLabel,
3137
- ariaLabel: options.ariaLabel,
3138
- buttonsVisible: options.buttonsVisible,
3139
- extraKeys: options.extraKeys
3140
- };
3141
- }
189
+ const parsePerseusArticle=union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
3142
190
 
3143
- const expressionWidgetLogic = {
3144
- name: "expression",
3145
- version: currentVersion$2,
3146
- widgetOptionsUpgrades: widgetOptionsUpgrades$1,
3147
- defaultWidgetOptions: defaultWidgetOptions$p,
3148
- defaultAlignment: "inline-block",
3149
- getPublicWidgetOptions: getExpressionPublicWidgetOptions
3150
- };
3151
-
3152
- const defaultWidgetOptions$o = {
3153
- title: "",
3154
- content: "",
3155
- widgets: {},
3156
- images: {},
3157
- hint: null
3158
- };
3159
- const gradedGroupWidgetLogic = {
3160
- name: "graded-group",
3161
- defaultWidgetOptions: defaultWidgetOptions$o
3162
- };
3163
-
3164
- const defaultWidgetOptions$n = {
3165
- gradedGroups: []
3166
- };
3167
- const gradedGroupSetWidgetLogic = {
3168
- name: "graded-group-set",
3169
- defaultWidgetOptions: defaultWidgetOptions$n
3170
- };
3171
-
3172
- function getGrapherPublicWidgetOptions(options) {
3173
- const {
3174
- correct: _,
3175
- ...publicOptions
3176
- } = options;
3177
- return publicOptions;
3178
- }
191
+ const parseHint=object({replace:defaulted(boolean,()=>undefined),content:string,widgets:defaulted(parseWidgetsMap,()=>({})),images:parseImages,metadata:any});
3179
192
 
3180
- const defaultWidgetOptions$m = {
3181
- graph: {
3182
- labels: ["x", "y"],
3183
- range: [[-10, 10], [-10, 10]],
3184
- step: [1, 1],
3185
- backgroundImage: {
3186
- url: null
3187
- },
3188
- markings: "graph",
3189
- rulerLabel: "",
3190
- rulerTicks: 10,
3191
- valid: true,
3192
- showTooltips: false
3193
- },
3194
- correct: {
3195
- type: "linear",
3196
- coords: null
3197
- },
3198
- availableTypes: ["linear"]
3199
- };
3200
- const grapherWidgetLogic = {
3201
- name: "grapher",
3202
- defaultWidgetOptions: defaultWidgetOptions$m,
3203
- getPublicWidgetOptions: getGrapherPublicWidgetOptions
3204
- };
3205
-
3206
- const defaultWidgetOptions$l = {
3207
- content: "",
3208
- widgets: {},
3209
- images: {}
3210
- };
3211
- const groupWidgetLogic = {
3212
- name: "group",
3213
- defaultWidgetOptions: defaultWidgetOptions$l
3214
- };
3215
-
3216
- function getIFramePublicWidgetOptions(options) {
3217
- return options;
3218
- }
193
+ const parsePerseusAnswerArea=pipeParsers(defaulted(object({}),()=>({}))).then(convert(toAnswerArea)).parser;function toAnswerArea(raw){return {zTable:!!raw.zTable,calculator:!!raw.calculator,chi2Table:!!raw.chi2Table,financialCalculatorMonthlyPayment:!!raw.financialCalculatorMonthlyPayment,financialCalculatorTotalAmount:!!raw.financialCalculatorTotalAmount,financialCalculatorTimeToPayOff:!!raw.financialCalculatorTimeToPayOff,periodicTable:!!raw.periodicTable,periodicTableWithKey:!!raw.periodicTableWithKey,tTable:!!raw.tTable}}
3219
194
 
3220
- const defaultWidgetOptions$k = {
3221
- url: "",
3222
- settings: [{
3223
- name: "",
3224
- value: ""
3225
- }],
3226
- width: "400",
3227
- height: "400",
3228
- allowFullScreen: false,
3229
- allowTopNavigation: false
3230
- };
3231
- const iframeWidgetLogic = {
3232
- name: "iframe",
3233
- defaultWidgetOptions: defaultWidgetOptions$k,
3234
- getPublicWidgetOptions: getIFramePublicWidgetOptions
3235
- };
3236
-
3237
- const defaultWidgetOptions$j = {
3238
- title: "",
3239
- range: [[0, 10], [0, 10]],
3240
- box: [400, 400],
3241
- backgroundImage: {
3242
- url: null,
3243
- width: 0,
3244
- height: 0
3245
- },
3246
- labels: [],
3247
- alt: "",
3248
- caption: ""
3249
- };
3250
- const imageWidgetLogic = {
3251
- name: "image",
3252
- defaultWidgetOptions: defaultWidgetOptions$j,
3253
- supportedAlignments: ["block", "full-width"],
3254
- defaultAlignment: "block"
3255
- };
3256
-
3257
- const defaultWidgetOptions$i = {
3258
- value: 0,
3259
- simplify: "required",
3260
- size: "normal",
3261
- inexact: false,
3262
- maxError: 0.1,
3263
- answerType: "number",
3264
- rightAlign: false
3265
- };
3266
- const inputNumberWidgetLogic = {
3267
- name: "input-number",
3268
- defaultWidgetOptions: defaultWidgetOptions$i,
3269
- defaultAlignment: "inline-block"
3270
- };
3271
-
3272
- const defaultWidgetOptions$h = {
3273
- graph: {
3274
- box: [400, 400],
3275
- labels: ["x", "y"],
3276
- range: [[-10, 10], [-10, 10]],
3277
- tickStep: [1, 1],
3278
- gridStep: [1, 1],
3279
- markings: "graph"
3280
- },
3281
- elements: []
3282
- };
3283
- const interactionWidgetLogic = {
3284
- name: "interaction",
3285
- defaultWidgetOptions: defaultWidgetOptions$h
3286
- };
3287
-
3288
- function getInteractiveGraphPublicWidgetOptions(options) {
3289
- const {
3290
- correct: _,
3291
- ...publicOptions
3292
- } = options;
3293
- return publicOptions;
3294
- }
195
+ const parsePerseusItem$1=object({question:parsePerseusRenderer,hints:defaulted(array(parseHint),()=>[]),answerArea:parsePerseusAnswerArea,itemDataVersion:optional(nullable(object({major:number,minor:number}))),answer:any});
3295
196
 
3296
- const defaultWidgetOptions$g = {
3297
- labels: ["x", "y"],
3298
- range: [[-10, 10], [-10, 10]],
3299
- step: [1, 1],
3300
- backgroundImage: {
3301
- url: null
3302
- },
3303
- markings: "graph",
3304
- showTooltips: false,
3305
- showProtractor: false,
3306
- graph: {
3307
- type: "linear"
3308
- },
3309
- correct: {
3310
- type: "linear",
3311
- coords: null
3312
- }
3313
- };
3314
- const interactiveGraphWidgetLogic = {
3315
- name: "interactive-graph",
3316
- defaultWidgetOptions: defaultWidgetOptions$g,
3317
- getPublicWidgetOptions: getInteractiveGraphPublicWidgetOptions
3318
- };
3319
-
3320
- /**
3321
- * For details on the individual options, see the
3322
- * PerseusLabelImageWidgetOptions type
3323
- */
3324
-
3325
- function getLabelImagePublicWidgetOptions(options) {
3326
- return {
3327
- ...options,
3328
- markers: options.markers.map(getLabelImageMarkerPublicData)
3329
- };
3330
- }
3331
- function getLabelImageMarkerPublicData(marker) {
3332
- const {
3333
- answers: _,
3334
- ...publicData
3335
- } = marker;
3336
- return publicData;
3337
- }
197
+ function parsePerseusItem(json){if(isRealJSONParse(JSON.parse)){return JSON.parse(json)}throw new Error("Something went wrong.")}function parseAndMigratePerseusItem(json){throwErrorIfCheatingDetected();const object=JSON.parse(json);const result=parse(object,parsePerseusItem$1);if(isFailure(result)){return failure({message:result.detail,invalidObject:object})}return result}function parseAndMigratePerseusArticle(json){throwErrorIfCheatingDetected();const object=JSON.parse(json);const result=parse(object,parsePerseusArticle);if(isFailure(result)){return failure({message:result.detail,invalidObject:object})}return result}function throwErrorIfCheatingDetected(){if(!isRealJSONParse(JSON.parse)){throw new Error("Something went wrong.")}}
3338
198
 
3339
- const defaultWidgetOptions$f = {
3340
- choices: [],
3341
- imageAlt: "",
3342
- imageUrl: "",
3343
- imageWidth: 0,
3344
- imageHeight: 0,
3345
- markers: [],
3346
- multipleAnswers: false,
3347
- hideChoicesFromInstructions: false
3348
- };
3349
- const labelImageWidgetLogic = {
3350
- name: "label-image",
3351
- defaultWidgetOptions: defaultWidgetOptions$f,
3352
- getPublicWidgetOptions: getLabelImagePublicWidgetOptions
3353
- };
3354
-
3355
- /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
3356
- May be useful to bring other perseus package utilities here. Contains utility functions
3357
- and types used across multiple widgets for randomization and shuffling. */
3358
-
3359
- const seededRNG = function (seed) {
3360
- let randomSeed = seed;
3361
- return function () {
3362
- // Robert Jenkins' 32 bit integer hash function.
3363
- let seed = randomSeed;
3364
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
3365
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
3366
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
3367
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
3368
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
3369
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
3370
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
3371
- };
3372
- };
3373
-
3374
- // Shuffle an array using a given random seed or function.
3375
- // If `ensurePermuted` is true, the input and output are guaranteed to be
3376
- // distinct permutations.
3377
- function shuffle(array, randomSeed) {
3378
- let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
3379
- // Always return a copy of the input array
3380
- const shuffled = ___default.default.clone(array);
3381
-
3382
- // Handle edge cases (input array is empty or uniform)
3383
- if (
3384
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3385
- !shuffled.length || ___default.default.all(shuffled, function (value) {
3386
- return ___default.default.isEqual(value, shuffled[0]);
3387
- })) {
3388
- return shuffled;
3389
- }
3390
- let random;
3391
- if (typeof randomSeed === "function") {
3392
- random = randomSeed;
3393
- } else {
3394
- random = seededRNG(randomSeed);
3395
- }
3396
- do {
3397
- // Fischer-Yates shuffle
3398
- for (let top = shuffled.length; top > 0; top--) {
3399
- const newEnd = Math.floor(random() * top);
3400
- const temp = shuffled[newEnd];
3401
-
3402
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3403
- shuffled[newEnd] = shuffled[top - 1];
3404
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3405
- shuffled[top - 1] = temp;
3406
- }
3407
- } while (ensurePermuted && ___default.default.isEqual(array, shuffled));
3408
- return shuffled;
3409
- }
3410
- const random = seededRNG(new Date().getTime() & 0xffffffff);
3411
-
3412
- // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3413
-
3414
- // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3415
- const shuffleMatcher = props => {
3416
- // Use the same random() function to shuffle both columns sequentially
3417
- const rng = seededRNG(props.problemNum);
3418
- let left;
3419
- if (!props.orderMatters) {
3420
- // If the order doesn't matter, don't shuffle the left column
3421
- left = props.left;
3422
- } else {
3423
- left = shuffle(props.left, rng, /* ensurePermuted */true);
3424
- }
3425
- const right = shuffle(props.right, rng, /* ensurePermuted */true);
3426
- return {
3427
- left,
3428
- right
3429
- };
3430
- };
3431
-
3432
- // TODO(LEMS-2841): Can shorten to shuffleMatcher after above function removed
3433
- function shuffleMatcherWithRandom(data) {
3434
- // Use the same random() function to shuffle both columns sequentially
3435
- let left;
3436
- if (!data.orderMatters) {
3437
- // If the order doesn't matter, don't shuffle the left column
3438
- left = data.left;
3439
- } else {
3440
- left = shuffle(data.left, Math.random, /* ensurePermuted */true);
3441
- }
3442
- const right = shuffle(data.right, Math.random, /* ensurePermuted */true);
3443
- return {
3444
- left,
3445
- right
3446
- };
3447
- }
199
+ const libName="@khanacademy/perseus-core";const libVersion="7.0.2";perseusUtils.addLibraryVersionToPerseusDebug(libName,libVersion);
3448
200
 
3449
- /**
3450
- * For details on the individual options, see the
3451
- * PerseusMatcherWidgetOptions type
3452
- */
3453
-
3454
- /**
3455
- * Given a PerseusMatcherWidgetOptions object, return a new object with only
3456
- * the public options that should be exposed to the client.
3457
- */
3458
- function getMatcherPublicWidgetOptions(options) {
3459
- const {
3460
- left,
3461
- right
3462
- } = shuffleMatcherWithRandom(options);
3463
- return {
3464
- ...options,
3465
- left: left,
3466
- right: right
3467
- };
3468
- }
201
+ const Errors=Object.freeze({Unknown:"Unknown",Internal:"Internal",InvalidInput:"InvalidInput",NotAllowed:"NotAllowed",TransientService:"TransientService",Service:"Service"});
3469
202
 
3470
- const defaultWidgetOptions$e = {
3471
- left: ["$x$", "$y$", "$z$"],
3472
- right: ["$1$", "$2$", "$3$"],
3473
- labels: ["test", "label"],
3474
- orderMatters: false,
3475
- padding: true
3476
- };
3477
- const matcherWidgetLogic = {
3478
- name: "matcher",
3479
- defaultWidgetOptions: defaultWidgetOptions$e,
3480
- getPublicWidgetOptions: getMatcherPublicWidgetOptions
3481
- };
3482
-
3483
- function getMatrixPublicWidgetOptions(options) {
3484
- const {
3485
- answers: _,
3486
- ...publicOptions
3487
- } = options;
3488
- return publicOptions;
3489
- }
203
+ class PerseusError extends Error{constructor(message,kind,options){super(message);this.kind=kind;this.metadata=options?.metadata;}}
3490
204
 
3491
- const defaultWidgetOptions$d = {
3492
- matrixBoardSize: [3, 3],
3493
- answers: [[]],
3494
- prefix: "",
3495
- suffix: "",
3496
- cursorPosition: [0, 0]
3497
- };
3498
- const matrixWidgetLogic = {
3499
- name: "matrix",
3500
- defaultWidgetOptions: defaultWidgetOptions$d,
3501
- getPublicWidgetOptions: getMatrixPublicWidgetOptions
3502
- };
3503
-
3504
- const currentVersion$1 = {
3505
- major: 1,
3506
- minor: 0
3507
- };
3508
- const widgetOptionsUpgrades = {
3509
- "1": v0options => {
3510
- const {
3511
- imageUrl,
3512
- imageTop,
3513
- imageLeft,
3514
- ...rest
3515
- } = v0options;
3516
- return {
3517
- ...rest,
3518
- image: {
3519
- url: imageUrl,
3520
- top: imageTop,
3521
- left: imageLeft
3522
- }
3523
- };
3524
- }
3525
- };
3526
- const defaultWidgetOptions$c = {
3527
- box: [480, 480],
3528
- image: {},
3529
- showProtractor: true,
3530
- showRuler: false,
3531
- rulerLabel: "",
3532
- rulerTicks: 10,
3533
- rulerPixels: 40,
3534
- rulerLength: 10
3535
- };
3536
-
3537
- const measurerWidgetLogic = {
3538
- name: "measurer",
3539
- version: currentVersion$1,
3540
- widgetOptionsUpgrades: widgetOptionsUpgrades,
3541
- defaultWidgetOptions: defaultWidgetOptions$c
3542
- };
3543
-
3544
- function getNumberLinePublicWidgetOptions(options) {
3545
- const {
3546
- correctX: _,
3547
- correctRel: __,
3548
- ...publicOptions
3549
- } = options;
3550
- return publicOptions;
3551
- }
205
+ const pluck=function(table,subKey){return ___default.default.object(___default.default.map(table,function(value,key){return [key,value[subKey]]}))};const mapObject=function(obj,lambda){const result={};Object.keys(obj).forEach(key=>{result[key]=lambda(obj[key],key);});return result};
3552
206
 
3553
- const defaultWidgetOptions$b = {
3554
- range: [0, 10],
3555
- labelRange: [null, null],
3556
- labelStyle: "decimal",
3557
- labelTicks: true,
3558
- divisionRange: [1, 12],
3559
- numDivisions: 5,
3560
- snapDivisions: 2,
3561
- tickStep: null,
3562
- correctRel: "eq",
3563
- correctX: null,
3564
- initialX: null,
3565
- showTooltips: false
3566
- };
3567
- const numberLineWidgetLogic = {
3568
- name: "number-line",
3569
- defaultWidgetOptions: defaultWidgetOptions$b,
3570
- getPublicWidgetOptions: getNumberLinePublicWidgetOptions
3571
- };
3572
-
3573
- /**
3574
- * For details on the individual options, see the
3575
- * PerseusNumericInputWidgetOptions type
3576
- */
3577
-
3578
- /**
3579
- * This data from `answers` is used pre-scoring to give hints
3580
- * to the learner regarding the format of accepted answers
3581
- */
3582
- function getNumericInputAnswerPublicData(answer) {
3583
- const {
3584
- answerForms,
3585
- simplify,
3586
- status
3587
- } = answer;
3588
- return {
3589
- answerForms,
3590
- simplify,
3591
- status
3592
- };
3593
- }
207
+ function getCategorizerPublicWidgetOptions(options){return {items:options.items,categories:options.categories,randomizeItems:options.randomizeItems,static:options.static}}
3594
208
 
3595
- /**
3596
- * Given a PerseusNumericInputWidgetOptions object, return a new object with only
3597
- * the public options that should be exposed to the client.
3598
- */
3599
- function getNumericInputPublicWidgetOptions(options) {
3600
- const {
3601
- answers,
3602
- ...publicWidgetOptions
3603
- } = options;
3604
- return {
3605
- ...publicWidgetOptions,
3606
- answers: answers.map(getNumericInputAnswerPublicData)
3607
- };
3608
- }
209
+ const defaultWidgetOptions$u={items:[],categories:[],values:[],randomizeItems:false};const categorizerWidgetLogic={name:"categorizer",defaultWidgetOptions: defaultWidgetOptions$u,getPublicWidgetOptions:getCategorizerPublicWidgetOptions};
3609
210
 
3610
- const defaultWidgetOptions$a = {
3611
- answers: [{
3612
- value: null,
3613
- status: "correct",
3614
- message: "",
3615
- simplify: "required",
3616
- answerForms: [],
3617
- strict: false,
3618
- maxError: null
3619
- }],
3620
- size: "normal",
3621
- coefficient: false,
3622
- labelText: "",
3623
- rightAlign: false
3624
- };
3625
- const numericInputWidgetLogic = {
3626
- name: "numeric-input",
3627
- defaultWidgetOptions: defaultWidgetOptions$a,
3628
- defaultAlignment: "inline-block",
3629
- getPublicWidgetOptions: getNumericInputPublicWidgetOptions
3630
- };
3631
-
3632
- /**
3633
- * For details on the individual options, see the
3634
- * PerseusOrdererWidgetOptions type
3635
- */
3636
-
3637
- /**
3638
- * Given a PerseusOrdererWidgetOptions object, return a new object with only
3639
- * the public options that should be exposed to the client.
3640
- */
3641
- function getOrdererPublicWidgetOptions(options) {
3642
- return {
3643
- options: options.options,
3644
- height: options.height,
3645
- layout: options.layout
3646
- };
3647
- }
211
+ function getCSProgramPublicWidgetOptions(options){return options}
3648
212
 
3649
- const defaultWidgetOptions$9 = {
3650
- correctOptions: [{
3651
- content: "$x$"
3652
- }],
3653
- otherOptions: [{
3654
- content: "$y$"
3655
- }],
3656
- height: "normal",
3657
- layout: "horizontal"
3658
- };
3659
- const ordererWidgetLogic = {
3660
- name: "orderer",
3661
- defaultWidgetOptions: defaultWidgetOptions$9,
3662
- getPublicWidgetOptions: getOrdererPublicWidgetOptions
3663
- };
3664
-
3665
- const defaultWidgetOptions$8 = {
3666
- passageTitle: "",
3667
- passageText: "",
3668
- footnotes: "",
3669
- showLineNumbers: true
3670
- };
3671
- const passageWidgetLogic = {
3672
- name: "passage",
3673
- defaultWidgetOptions: defaultWidgetOptions$8
3674
- };
3675
-
3676
- const currentVersion = {
3677
- major: 0,
3678
- minor: 1
3679
- };
3680
- const defaultWidgetOptions$7 = {
3681
- passageNumber: 1,
3682
- referenceNumber: 1,
3683
- summaryText: ""
3684
- };
3685
-
3686
- const passageRefWidgetLogic = {
3687
- name: "passageRef",
3688
- version: currentVersion,
3689
- defaultWidgetOptions: defaultWidgetOptions$7,
3690
- defaultAlignment: "inline"
3691
- };
3692
-
3693
- const defaultWidgetOptions$6 = {
3694
- content: ""
3695
- };
3696
- const passageRefTargetWidgetLogic = {
3697
- name: "passageRefTarget",
3698
- defaultWidgetOptions: defaultWidgetOptions$6,
3699
- defaultAlignment: "inline"
3700
- };
3701
-
3702
- const defaultWidgetOptions$5 = {
3703
- url: "",
3704
- description: ""
3705
- };
3706
- const phetSimulationWidgetLogic = {
3707
- name: "phet-simulation",
3708
- defaultWidgetOptions: defaultWidgetOptions$5
3709
- };
3710
-
3711
- /**
3712
- * For details on the individual options, see the
3713
- * PerseusPlotterWidgetOptions type
3714
- */
3715
-
3716
- /**
3717
- * Given a PerseusPlotterWidgetOptions object, return a new object with only
3718
- * the public options that should be exposed to the client.
3719
- */
3720
- function getPlotterPublicWidgetOptions(options) {
3721
- const {
3722
- correct: _,
3723
- ...publicOptions
3724
- } = options;
3725
- return publicOptions;
3726
- }
213
+ const DEFAULT_HEIGHT=400;const defaultWidgetOptions$t={programID:"",programType:null,settings:[{name:"",value:""}],showEditor:false,showButtons:false,height:DEFAULT_HEIGHT};const csProgramWidgetLogic={name:"cs-program",defaultWidgetOptions: defaultWidgetOptions$t,supportedAlignments:["block","full-width"],getPublicWidgetOptions:getCSProgramPublicWidgetOptions};
3727
214
 
3728
- const defaultWidgetOptions$4 = {
3729
- scaleY: 1,
3730
- maxY: 10,
3731
- snapsPerLine: 2,
3732
- correct: [1],
3733
- starting: [1],
3734
- type: "bar",
3735
- labels: ["", ""],
3736
- categories: [""],
3737
- picSize: 30,
3738
- picBoxHeight: 36,
3739
- plotDimensions: [275, 200],
3740
- labelInterval: 1,
3741
- picUrl: null
3742
- };
3743
- const plotterWidgetLogic = {
3744
- name: "plotter",
3745
- defaultWidgetOptions: defaultWidgetOptions$4,
3746
- getPublicWidgetOptions: getPlotterPublicWidgetOptions
3747
- };
3748
-
3749
- const defaultWidgetOptions$3 = {
3750
- programID: "",
3751
- height: 400
3752
- };
3753
- const pythonProgramWidgetLogic = {
3754
- name: "python-program",
3755
- defaultWidgetOptions: defaultWidgetOptions$3
3756
- };
3757
-
3758
- /**
3759
- * For details on the individual options, see the
3760
- * PerseusRadioWidgetOptions type
3761
- */
3762
-
3763
- /**
3764
- * Only the options from each Radio choice that should be exposed to the client.
3765
- */
3766
-
3767
- /**
3768
- * Given a PerseusRadioChoice object, return a new object with only the public
3769
- * data that should be included in the Radio public widget options.
3770
- */
3771
- function getRadioChoicePublicData(choice) {
3772
- const {
3773
- content,
3774
- isNoneOfTheAbove,
3775
- widgets
3776
- } = choice;
3777
- return {
3778
- content,
3779
- isNoneOfTheAbove,
3780
- widgets
3781
- };
3782
- }
215
+ const defaultWidgetOptions$s={togglePrompt:"",definition:""};const definitionWidgetLogic={name:"definition",defaultWidgetOptions: defaultWidgetOptions$s,defaultAlignment:"inline"};
3783
216
 
3784
- /**
3785
- * Shared functionality to determine if numCorrect is used, because:
3786
- *
3787
- * 1. numCorrect is conditionally used for rendering pre-scoring
3788
- * 2. numCorrect also exposes information about answers
3789
- *
3790
- * So only include/use numCorrect when we know it's useful.
3791
- */
3792
- function usesNumCorrect(multipleSelect, countChoices, numCorrect) {
3793
- return multipleSelect && countChoices && numCorrect;
3794
- }
217
+ function getDropdownPublicWidgetOptions(options){return {choices:options.choices.map(choice=>({content:choice.content})),placeholder:options.placeholder,static:options.static,visibleLabel:options.visibleLabel,ariaLabel:options.ariaLabel}}
3795
218
 
3796
- /**
3797
- * Given a PerseusRadioWidgetOptions object, return a new object with only
3798
- * the public options that should be exposed to the client.
3799
- */
3800
- function getRadioPublicWidgetOptions(options) {
3801
- const {
3802
- numCorrect,
3803
- choices,
3804
- multipleSelect,
3805
- countChoices
3806
- } = options;
3807
- return {
3808
- ...options,
3809
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
3810
- numCorrect: usesNumCorrect(multipleSelect, countChoices, numCorrect) ? numCorrect : undefined,
3811
- choices: choices.map(getRadioChoicePublicData)
3812
- };
3813
- }
219
+ const defaultWidgetOptions$r={placeholder:"",choices:[{content:"",correct:false}]};const dropdownWidgetLogic={name:"definition",defaultWidgetOptions: defaultWidgetOptions$r,defaultAlignment:"inline-block",getPublicWidgetOptions:getDropdownPublicWidgetOptions};
3814
220
 
3815
- const radioWidgetLogic = {
3816
- name: "radio",
3817
- version: currentVersion$3,
3818
- widgetOptionsUpgrades: widgetOptionsUpgrades$2,
3819
- defaultWidgetOptions: defaultWidgetOptions$v,
3820
- getPublicWidgetOptions: getRadioPublicWidgetOptions
3821
- };
3822
-
3823
- /**
3824
- * For details on the individual options, see the
3825
- * PerseusSorterWidgetOptions type
3826
- */
3827
-
3828
- /**
3829
- * Given a PerseusSorterWidgetOptions object, return a new object with only
3830
- * the public options that should be exposed to the client.
3831
- */
3832
- function getSorterPublicWidgetOptions(options) {
3833
- const shuffledCorrect = shuffle(options.correct, Math.random, /* ensurePermuted */true);
3834
- return {
3835
- ...options,
3836
- // Note(Tamara): This does not provide correct answer information any longer.
3837
- // To maintain compatibility with the original widget options, we are
3838
- // keeping the key the same. Represents initial state of the cards here.
3839
- correct: shuffledCorrect,
3840
- // Note(Tamara): This new key is only added here with "true". There isn't
3841
- // a place where it is set to false. It indicates that the correct field
3842
- // has been shuffled and no longer contains correct answer info.
3843
- isCorrectShuffled: true
3844
- };
3845
- }
221
+ const defaultWidgetOptions$q={showPrompt:"Explain",hidePrompt:"Hide explanation",explanation:"explanation goes here\n\nmore explanation",widgets:{}};const explanationWidgetLogic={name:"explanation",defaultWidgetOptions: defaultWidgetOptions$q,defaultAlignment:"inline"};
3846
222
 
3847
- const defaultWidgetOptions$2 = {
3848
- correct: ["$x$", "$y$", "$z$"],
3849
- layout: "horizontal",
3850
- padding: true
3851
- };
3852
- const sorterWidgetLogic = {
3853
- name: "sorter",
3854
- defaultWidgetOptions: defaultWidgetOptions$2,
3855
- getPublicWidgetOptions: getSorterPublicWidgetOptions
3856
- };
3857
-
3858
- function getTablePublicWidgetOptions(options) {
3859
- const {
3860
- answers: _,
3861
- ...publicOptions
3862
- } = options;
3863
- return publicOptions;
3864
- }
223
+ const currentVersion$2={major:2,minor:0};const widgetOptionsUpgrades$1={"2":v1options=>{return {times:v1options.times,buttonSets:v1options.buttonSets,functions:v1options.functions,buttonsVisible:v1options.buttonsVisible,visibleLabel:v1options.visibleLabel,ariaLabel:v1options.ariaLabel,answerForms:v1options.answerForms,extraKeys:v1options.extraKeys||deriveExtraKeys(v1options)}},"1":v0options=>{return {times:v0options.times,buttonSets:v0options.buttonSets,functions:v0options.functions,buttonsVisible:v0options.buttonsVisible,visibleLabel:v0options.visibleLabel,ariaLabel:v0options.ariaLabel,extraKeys:v0options.extraKeys,answerForms:[{considered:"correct",form:v0options.form,simplify:v0options.simplify,value:v0options.value}]}}};const defaultWidgetOptions$p={answerForms:[],times:false,buttonSets:["basic"],functions:["f","g","h"]};
3865
224
 
3866
- const defaultRows = 4;
3867
- const defaultColumns = 1;
3868
-
3869
- // initialize a 2D array
3870
- // (defaultRows x defaultColumns) of empty strings
3871
- const answers = new Array(defaultRows).fill(0).map(() => new Array(defaultColumns).fill(""));
3872
- const defaultWidgetOptions$1 = {
3873
- headers: [""],
3874
- rows: defaultRows,
3875
- columns: defaultColumns,
3876
- answers: answers
3877
- };
3878
- const tableWidgetLogic = {
3879
- name: "table",
3880
- defaultWidgetOptions: defaultWidgetOptions$1,
3881
- getPublicWidgetOptions: getTablePublicWidgetOptions
3882
- };
3883
-
3884
- const defaultWidgetOptions = {
3885
- location: ""
3886
- };
3887
- const videoWidgetLogic = {
3888
- name: "video",
3889
- defaultWidgetOptions,
3890
- supportedAlignments: ["block", "float-left", "float-right", "full-width"],
3891
- defaultAlignment: "block"
3892
- };
3893
-
3894
- const widgets = {};
3895
- function registerWidget(type, logic) {
3896
- widgets[type] = logic;
3897
- }
3898
- function isWidgetRegistered(type) {
3899
- const widgetLogic = widgets[type];
3900
- return !!widgetLogic;
3901
- }
3902
- function getCurrentVersion(type) {
3903
- const widgetLogic = widgets[type];
3904
- return widgetLogic?.version || {
3905
- major: 0,
3906
- minor: 0
3907
- };
3908
- }
225
+ function getExpressionPublicWidgetOptions(options){return {buttonSets:options.buttonSets,functions:options.functions,times:options.times,visibleLabel:options.visibleLabel,ariaLabel:options.ariaLabel,buttonsVisible:options.buttonsVisible,extraKeys:options.extraKeys}}
3909
226
 
3910
- // TODO(LEMS-2870): getPublicWidgetOptionsFunction/PublicWidgetOptionsFunction
3911
- // need better types
3912
- const getPublicWidgetOptionsFunction = name => {
3913
- return widgets[name]?.getPublicWidgetOptions ?? (i => i);
3914
- };
3915
- function getWidgetOptionsUpgrades(type) {
3916
- const widgetLogic = widgets[type];
3917
- return widgetLogic?.widgetOptionsUpgrades || {};
3918
- }
3919
- function getDefaultWidgetOptions(type) {
3920
- const widgetLogic = widgets[type];
3921
- return widgetLogic?.defaultWidgetOptions || {};
3922
- }
227
+ const expressionWidgetLogic={name:"expression",version:currentVersion$2,widgetOptionsUpgrades:widgetOptionsUpgrades$1,defaultWidgetOptions:defaultWidgetOptions$p,defaultAlignment:"inline-block",getPublicWidgetOptions:getExpressionPublicWidgetOptions};
228
+
229
+ const defaultWidgetOptions$o={title:"",content:"",widgets:{},images:{},hint:null};const gradedGroupWidgetLogic={name:"graded-group",defaultWidgetOptions: defaultWidgetOptions$o};
230
+
231
+ const defaultWidgetOptions$n={gradedGroups:[]};const gradedGroupSetWidgetLogic={name:"graded-group-set",defaultWidgetOptions: defaultWidgetOptions$n};
232
+
233
+ function getGrapherPublicWidgetOptions(options){const{correct:_,...publicOptions}=options;return publicOptions}
234
+
235
+ const defaultWidgetOptions$m={graph:{labels:["x","y"],range:[[-10,10],[-10,10]],step:[1,1],backgroundImage:{url:null},markings:"graph",rulerLabel:"",rulerTicks:10,valid:true,showTooltips:false},correct:{type:"linear",coords:null},availableTypes:["linear"]};const grapherWidgetLogic={name:"grapher",defaultWidgetOptions: defaultWidgetOptions$m,getPublicWidgetOptions:getGrapherPublicWidgetOptions};
236
+
237
+ const defaultWidgetOptions$l={content:"",widgets:{},images:{}};const groupWidgetLogic={name:"group",defaultWidgetOptions: defaultWidgetOptions$l};
238
+
239
+ function getIFramePublicWidgetOptions(options){return options}
240
+
241
+ const defaultWidgetOptions$k={url:"",settings:[{name:"",value:""}],width:"400",height:"400",allowFullScreen:false,allowTopNavigation:false};const iframeWidgetLogic={name:"iframe",defaultWidgetOptions: defaultWidgetOptions$k,getPublicWidgetOptions:getIFramePublicWidgetOptions};
242
+
243
+ const defaultWidgetOptions$j={title:"",range:[[0,10],[0,10]],box:[400,400],backgroundImage:{url:null,width:0,height:0},labels:[],alt:"",caption:""};const imageWidgetLogic={name:"image",defaultWidgetOptions: defaultWidgetOptions$j,supportedAlignments:["block","full-width"],defaultAlignment:"block"};
244
+
245
+ const defaultWidgetOptions$i={value:0,simplify:"required",size:"normal",inexact:false,maxError:.1,answerType:"number",rightAlign:false};const inputNumberWidgetLogic={name:"input-number",defaultWidgetOptions: defaultWidgetOptions$i,defaultAlignment:"inline-block"};
246
+
247
+ const defaultWidgetOptions$h={graph:{box:[400,400],labels:["x","y"],range:[[-10,10],[-10,10]],tickStep:[1,1],gridStep:[1,1],markings:"graph"},elements:[]};const interactionWidgetLogic={name:"interaction",defaultWidgetOptions: defaultWidgetOptions$h};
248
+
249
+ function getInteractiveGraphPublicWidgetOptions(options){const{correct:_,...publicOptions}=options;return publicOptions}
250
+
251
+ const defaultWidgetOptions$g={labels:["x","y"],range:[[-10,10],[-10,10]],step:[1,1],backgroundImage:{url:null},markings:"graph",showTooltips:false,showProtractor:false,graph:{type:"linear"},correct:{type:"linear",coords:null}};const interactiveGraphWidgetLogic={name:"interactive-graph",defaultWidgetOptions: defaultWidgetOptions$g,getPublicWidgetOptions:getInteractiveGraphPublicWidgetOptions};
252
+
253
+ function getLabelImagePublicWidgetOptions(options){return {...options,markers:options.markers.map(getLabelImageMarkerPublicData)}}function getLabelImageMarkerPublicData(marker){const{answers:_,...publicData}=marker;return publicData}
254
+
255
+ const defaultWidgetOptions$f={choices:[],imageAlt:"",imageUrl:"",imageWidth:0,imageHeight:0,markers:[],multipleAnswers:false,hideChoicesFromInstructions:false};const labelImageWidgetLogic={name:"label-image",defaultWidgetOptions: defaultWidgetOptions$f,getPublicWidgetOptions:getLabelImagePublicWidgetOptions};
256
+
257
+ const seededRNG=function(seed){let randomSeed=seed;return function(){let seed=randomSeed;seed=seed+0x7ed55d16+(seed<<12)&0xffffffff;seed=(seed^0xc761c23c^seed>>>19)&0xffffffff;seed=seed+0x165667b1+(seed<<5)&0xffffffff;seed=(seed+0xd3a2646c^seed<<9)&0xffffffff;seed=seed+0xfd7046c5+(seed<<3)&0xffffffff;seed=(seed^0xb55a4f09^seed>>>16)&0xffffffff;return (randomSeed=seed&0xfffffff)/0x10000000}};function shuffle(array,randomSeed,ensurePermuted=false){const shuffled=___default.default.clone(array);if(!shuffled.length||___default.default.all(shuffled,function(value){return ___default.default.isEqual(value,shuffled[0])})){return shuffled}let random;if(typeof randomSeed==="function"){random=randomSeed;}else {random=seededRNG(randomSeed);}do{for(let top=shuffled.length;top>0;top--){const newEnd=Math.floor(random()*top);const temp=shuffled[newEnd];shuffled[newEnd]=shuffled[top-1];shuffled[top-1]=temp;}}while(ensurePermuted&&___default.default.isEqual(array,shuffled))return shuffled}const random=seededRNG(new Date().getTime()&0xffffffff);
258
+
259
+ const shuffleMatcher=props=>{const rng=seededRNG(props.problemNum);let left;if(!props.orderMatters){left=props.left;}else {left=shuffle(props.left,rng,true);}const right=shuffle(props.right,rng,true);return {left,right}};function shuffleMatcherWithRandom(data){let left;if(!data.orderMatters){left=data.left;}else {left=shuffle(data.left,Math.random,true);}const right=shuffle(data.right,Math.random,true);return {left,right}}function getMatcherPublicWidgetOptions(options){const{left,right}=shuffleMatcherWithRandom(options);return {...options,left:left,right:right}}
260
+
261
+ const defaultWidgetOptions$e={left:["$x$","$y$","$z$"],right:["$1$","$2$","$3$"],labels:["test","label"],orderMatters:false,padding:true};const matcherWidgetLogic={name:"matcher",defaultWidgetOptions: defaultWidgetOptions$e,getPublicWidgetOptions:getMatcherPublicWidgetOptions};
262
+
263
+ function getMatrixPublicWidgetOptions(options){const{answers:_,...publicOptions}=options;return publicOptions}
3923
264
 
3924
- /**
3925
- * Handling for the optional alignments for widgets
3926
- * See widget-container.jsx for details on how alignments are implemented.
3927
- */
3928
-
3929
- /**
3930
- * Returns the list of supported alignments for the given (string) widget
3931
- * type. This is used primarily at editing time to display the choices
3932
- * for the user.
3933
- *
3934
- * Supported alignments are given as an array of strings in the exports of
3935
- * a widget's module.
3936
- */
3937
- const getSupportedAlignments = type => {
3938
- const widgetLogic = widgets[type];
3939
- if (!widgetLogic?.supportedAlignments?.[0]) {
3940
- // default alignments
3941
- return ["default"];
3942
- }
3943
- return widgetLogic?.supportedAlignments;
3944
- };
3945
-
3946
- /**
3947
- * For the given (string) widget type, determine the default alignment for
3948
- * the widget. This is used at rendering time to go from "default" alignment
3949
- * to the actual alignment displayed on the screen.
3950
- *
3951
- * The default alignment is given either as a string (called
3952
- * `defaultAlignment`) or a function (called `getDefaultAlignment`) on
3953
- * the exports of a widget's module.
3954
- */
3955
- const getDefaultAlignment = type => {
3956
- const widgetLogic = widgets[type];
3957
- if (!widgetLogic?.defaultAlignment) {
3958
- return "block";
3959
- }
3960
- return widgetLogic.defaultAlignment;
3961
- };
3962
- registerWidget("categorizer", categorizerWidgetLogic);
3963
- registerWidget("cs-program", csProgramWidgetLogic);
3964
- registerWidget("definition", definitionWidgetLogic);
3965
- registerWidget("dropdown", dropdownWidgetLogic);
3966
- registerWidget("explanation", explanationWidgetLogic);
3967
- registerWidget("expression", expressionWidgetLogic);
3968
- registerWidget("graded-group", gradedGroupWidgetLogic);
3969
- registerWidget("graded-group-set", gradedGroupSetWidgetLogic);
3970
- registerWidget("grapher", grapherWidgetLogic);
3971
- registerWidget("group", groupWidgetLogic);
3972
- registerWidget("iframe", iframeWidgetLogic);
3973
- registerWidget("image", imageWidgetLogic);
3974
- registerWidget("input-number", inputNumberWidgetLogic);
3975
- registerWidget("interaction", interactionWidgetLogic);
3976
- registerWidget("interactive-graph", interactiveGraphWidgetLogic);
3977
- registerWidget("label-image", labelImageWidgetLogic);
3978
- registerWidget("matcher", matcherWidgetLogic);
3979
- registerWidget("matrix", matrixWidgetLogic);
3980
- registerWidget("measurer", measurerWidgetLogic);
3981
- registerWidget("number-line", numberLineWidgetLogic);
3982
- registerWidget("numeric-input", numericInputWidgetLogic);
3983
- registerWidget("orderer", ordererWidgetLogic);
3984
- registerWidget("passage", passageWidgetLogic);
3985
- registerWidget("passage-ref", passageRefWidgetLogic);
3986
- registerWidget("passage-ref-target", passageRefTargetWidgetLogic);
3987
- registerWidget("phet-simulation", phetSimulationWidgetLogic);
3988
- registerWidget("plotter", plotterWidgetLogic);
3989
- registerWidget("python-program", pythonProgramWidgetLogic);
3990
- registerWidget("radio", radioWidgetLogic);
3991
- registerWidget("sorter", sorterWidgetLogic);
3992
- registerWidget("table", tableWidgetLogic);
3993
- registerWidget("video", videoWidgetLogic);
265
+ const defaultWidgetOptions$d={matrixBoardSize:[3,3],answers:[[]],prefix:"",suffix:"",cursorPosition:[0,0]};const matrixWidgetLogic={name:"matrix",defaultWidgetOptions: defaultWidgetOptions$d,getPublicWidgetOptions:getMatrixPublicWidgetOptions};
266
+
267
+ const currentVersion$1={major:1,minor:0};const widgetOptionsUpgrades={"1":v0options=>{const{imageUrl,imageTop,imageLeft,...rest}=v0options;return {...rest,image:{url:imageUrl,top:imageTop,left:imageLeft}}}};const defaultWidgetOptions$c={box:[480,480],image:{},showProtractor:true,showRuler:false,rulerLabel:"",rulerTicks:10,rulerPixels:40,rulerLength:10};
268
+
269
+ const measurerWidgetLogic={name:"measurer",version:currentVersion$1,widgetOptionsUpgrades:widgetOptionsUpgrades,defaultWidgetOptions:defaultWidgetOptions$c};
270
+
271
+ function getNumberLinePublicWidgetOptions(options){const{correctX:_,correctRel:__,...publicOptions}=options;return publicOptions}
272
+
273
+ const defaultWidgetOptions$b={range:[0,10],labelRange:[null,null],labelStyle:"decimal",labelTicks:true,divisionRange:[1,12],numDivisions:5,snapDivisions:2,tickStep:null,correctRel:"eq",correctX:null,initialX:null,showTooltips:false};const numberLineWidgetLogic={name:"number-line",defaultWidgetOptions: defaultWidgetOptions$b,getPublicWidgetOptions:getNumberLinePublicWidgetOptions};
274
+
275
+ function getNumericInputAnswerPublicData(answer){const{answerForms,simplify,status}=answer;return {answerForms,simplify,status}}function getNumericInputPublicWidgetOptions(options){const{answers,...publicWidgetOptions}=options;return {...publicWidgetOptions,answers:answers.map(getNumericInputAnswerPublicData)}}
276
+
277
+ const defaultWidgetOptions$a={answers:[{value:null,status:"correct",message:"",simplify:"required",answerForms:[],strict:false,maxError:null}],size:"normal",coefficient:false,labelText:"",rightAlign:false};const numericInputWidgetLogic={name:"numeric-input",defaultWidgetOptions: defaultWidgetOptions$a,defaultAlignment:"inline-block",getPublicWidgetOptions:getNumericInputPublicWidgetOptions};
278
+
279
+ function getOrdererPublicWidgetOptions(options){return {options:options.options,height:options.height,layout:options.layout}}
280
+
281
+ const defaultWidgetOptions$9={correctOptions:[{content:"$x$"}],otherOptions:[{content:"$y$"}],height:"normal",layout:"horizontal"};const ordererWidgetLogic={name:"orderer",defaultWidgetOptions: defaultWidgetOptions$9,getPublicWidgetOptions:getOrdererPublicWidgetOptions};
282
+
283
+ const defaultWidgetOptions$8={passageTitle:"",passageText:"",footnotes:"",showLineNumbers:true};const passageWidgetLogic={name:"passage",defaultWidgetOptions: defaultWidgetOptions$8};
284
+
285
+ const currentVersion={major:0,minor:1};const defaultWidgetOptions$7={passageNumber:1,referenceNumber:1,summaryText:""};
286
+
287
+ const passageRefWidgetLogic={name:"passageRef",version:currentVersion,defaultWidgetOptions:defaultWidgetOptions$7,defaultAlignment:"inline"};
288
+
289
+ const defaultWidgetOptions$6={content:""};const passageRefTargetWidgetLogic={name:"passageRefTarget",defaultWidgetOptions: defaultWidgetOptions$6,defaultAlignment:"inline"};
290
+
291
+ const defaultWidgetOptions$5={url:"",description:""};const phetSimulationWidgetLogic={name:"phet-simulation",defaultWidgetOptions: defaultWidgetOptions$5};
292
+
293
+ function getPlotterPublicWidgetOptions(options){const{correct:_,...publicOptions}=options;return publicOptions}
294
+
295
+ const defaultWidgetOptions$4={scaleY:1,maxY:10,snapsPerLine:2,correct:[1],starting:[1],type:"bar",labels:["",""],categories:[""],picSize:30,picBoxHeight:36,plotDimensions:[275,200],labelInterval:1,picUrl:null};const plotterWidgetLogic={name:"plotter",defaultWidgetOptions: defaultWidgetOptions$4,getPublicWidgetOptions:getPlotterPublicWidgetOptions};
296
+
297
+ const defaultWidgetOptions$3={programID:"",height:400};const pythonProgramWidgetLogic={name:"python-program",defaultWidgetOptions: defaultWidgetOptions$3};
298
+
299
+ function getRadioChoicePublicData(choice){const{content,isNoneOfTheAbove,widgets}=choice;return {content,isNoneOfTheAbove,widgets}}function usesNumCorrect(multipleSelect,countChoices,numCorrect){return multipleSelect&&countChoices&&numCorrect}function getRadioPublicWidgetOptions(options){const{numCorrect,choices,multipleSelect,countChoices}=options;return {...options,numCorrect:usesNumCorrect(multipleSelect,countChoices,numCorrect)?numCorrect:undefined,choices:choices.map(getRadioChoicePublicData)}}
300
+
301
+ const radioWidgetLogic={name:"radio",version:currentVersion$3,widgetOptionsUpgrades:widgetOptionsUpgrades$2,defaultWidgetOptions:defaultWidgetOptions$v,getPublicWidgetOptions:getRadioPublicWidgetOptions};
302
+
303
+ function getSorterPublicWidgetOptions(options){const shuffledCorrect=shuffle(options.correct,Math.random,true);return {...options,correct:shuffledCorrect,isCorrectShuffled:true}}
304
+
305
+ const defaultWidgetOptions$2={correct:["$x$","$y$","$z$"],layout:"horizontal",padding:true};const sorterWidgetLogic={name:"sorter",defaultWidgetOptions: defaultWidgetOptions$2,getPublicWidgetOptions:getSorterPublicWidgetOptions};
306
+
307
+ function getTablePublicWidgetOptions(options){const{answers:_,...publicOptions}=options;return publicOptions}
308
+
309
+ const defaultRows=4;const defaultColumns=1;const answers=new Array(defaultRows).fill(0).map(()=>new Array(defaultColumns).fill(""));const defaultWidgetOptions$1={headers:[""],rows:defaultRows,columns:defaultColumns,answers:answers};const tableWidgetLogic={name:"table",defaultWidgetOptions: defaultWidgetOptions$1,getPublicWidgetOptions:getTablePublicWidgetOptions};
310
+
311
+ const defaultWidgetOptions={location:""};const videoWidgetLogic={name:"video",defaultWidgetOptions,supportedAlignments:["block","float-left","float-right","full-width"],defaultAlignment:"block"};
312
+
313
+ const widgets={};function registerWidget(type,logic){widgets[type]=logic;}function isWidgetRegistered(type){const widgetLogic=widgets[type];return !!widgetLogic}function getCurrentVersion(type){const widgetLogic=widgets[type];return widgetLogic?.version||{major:0,minor:0}}const getPublicWidgetOptionsFunction=name=>{return widgets[name]?.getPublicWidgetOptions??(i=>i)};function getWidgetOptionsUpgrades(type){const widgetLogic=widgets[type];return widgetLogic?.widgetOptionsUpgrades||{}}function getDefaultWidgetOptions(type){const widgetLogic=widgets[type];return widgetLogic?.defaultWidgetOptions||{}}const getSupportedAlignments=type=>{const widgetLogic=widgets[type];if(!widgetLogic?.supportedAlignments?.[0]){return ["default"]}return widgetLogic?.supportedAlignments};const getDefaultAlignment=type=>{const widgetLogic=widgets[type];if(!widgetLogic?.defaultAlignment){return "block"}return widgetLogic.defaultAlignment};registerWidget("categorizer",categorizerWidgetLogic);registerWidget("cs-program",csProgramWidgetLogic);registerWidget("definition",definitionWidgetLogic);registerWidget("dropdown",dropdownWidgetLogic);registerWidget("explanation",explanationWidgetLogic);registerWidget("expression",expressionWidgetLogic);registerWidget("graded-group",gradedGroupWidgetLogic);registerWidget("graded-group-set",gradedGroupSetWidgetLogic);registerWidget("grapher",grapherWidgetLogic);registerWidget("group",groupWidgetLogic);registerWidget("iframe",iframeWidgetLogic);registerWidget("image",imageWidgetLogic);registerWidget("input-number",inputNumberWidgetLogic);registerWidget("interaction",interactionWidgetLogic);registerWidget("interactive-graph",interactiveGraphWidgetLogic);registerWidget("label-image",labelImageWidgetLogic);registerWidget("matcher",matcherWidgetLogic);registerWidget("matrix",matrixWidgetLogic);registerWidget("measurer",measurerWidgetLogic);registerWidget("number-line",numberLineWidgetLogic);registerWidget("numeric-input",numericInputWidgetLogic);registerWidget("orderer",ordererWidgetLogic);registerWidget("passage",passageWidgetLogic);registerWidget("passage-ref",passageRefWidgetLogic);registerWidget("passage-ref-target",passageRefTargetWidgetLogic);registerWidget("phet-simulation",phetSimulationWidgetLogic);registerWidget("plotter",plotterWidgetLogic);registerWidget("python-program",pythonProgramWidgetLogic);registerWidget("radio",radioWidgetLogic);registerWidget("sorter",sorterWidgetLogic);registerWidget("table",tableWidgetLogic);registerWidget("video",videoWidgetLogic);
3994
314
 
3995
315
  var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
3996
316
  __proto__: null,
@@ -4003,155 +323,9 @@ var coreWidgetRegistry = /*#__PURE__*/Object.freeze({
4003
323
  isWidgetRegistered: isWidgetRegistered
4004
324
  });
4005
325
 
4006
- const DEFAULT_STATIC = false;
4007
- const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
4008
- const type = oldWidgetInfo.type;
4009
- // NOTE(jeremy): This looks like it could be replaced by fixing types so
4010
- // that `type` is non-optional. But we're seeing this in Sentry today so I
4011
- // suspect we have legacy data (potentially unpublished) and we should
4012
- // figure that out before depending solely on types.
4013
- if (!___default.default.isString(type)) {
4014
- throw new PerseusError("widget type must be a string, but was: " + type, Errors.Internal);
4015
- }
4016
- if (!isWidgetRegistered(type)) {
4017
- // If we have a widget that isn't registered, we can't upgrade it
4018
- // TODO(aria): Figure out what the best thing to do here would be
4019
- return oldWidgetInfo;
4020
- }
4021
-
4022
- // Unversioned widgets (pre-July 2014) are all implicitly 0.0
4023
- const initialVersion = oldWidgetInfo.version || {
4024
- major: 0,
4025
- minor: 0
4026
- };
4027
- const latestVersion = getCurrentVersion(type);
4028
-
4029
- // If the widget version is later than what we understand (major
4030
- // version is higher than latest, or major versions are equal and minor
4031
- // version is higher than latest), don't perform any upgrades.
4032
- if (initialVersion.major > latestVersion.major || initialVersion.major === latestVersion.major && initialVersion.minor > latestVersion.minor) {
4033
- return oldWidgetInfo;
4034
- }
4035
-
4036
- // We do a clone here so that it's safe to mutate the input parameter
4037
- // in propUpgrades functions (which I will probably accidentally do at
4038
- // some point, and we would like to not break when that happens).
4039
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4040
- let newEditorOptions = ___default.default.clone(oldWidgetInfo.options) || {};
4041
- const upgradePropsMap = getWidgetOptionsUpgrades(type);
4042
-
4043
- // Empty props usually mean a newly created widget by the editor,
4044
- // and are always considerered up-to-date.
4045
- // Mostly, we'd rather not run upgrade functions on props that are
4046
- // not complete.
4047
- if (___default.default.keys(newEditorOptions).length !== 0) {
4048
- // We loop through all the versions after the current version of
4049
- // the loaded widget, up to and including the latest version of the
4050
- // loaded widget, and run the upgrade function to bring our loaded
4051
- // widget's props up to that version.
4052
- // There is a little subtlety here in that we call
4053
- // upgradePropsMap[1] to upgrade *to* version 1,
4054
- // (not from version 1).
4055
- for (let nextVersion = initialVersion.major + 1; nextVersion <= latestVersion.major; nextVersion++) {
4056
- if (upgradePropsMap[String(nextVersion)]) {
4057
- newEditorOptions = upgradePropsMap[String(nextVersion)](newEditorOptions);
4058
- } else {
4059
- throw new PerseusError("No upgrade found for widget. Cannot render.", Errors.Internal, {
4060
- metadata: {
4061
- type,
4062
- fromMajorVersion: nextVersion - 1,
4063
- toMajorVersion: nextVersion,
4064
- oldWidgetInfo: JSON.stringify(oldWidgetInfo)
4065
- }
4066
- });
4067
- }
4068
- }
4069
- }
4070
-
4071
- // Minor version upgrades (eg. new optional props) don't have
4072
- // transform functions. Instead, we fill in the new props with their
4073
- // defaults.
4074
- const defaultOptions = getDefaultWidgetOptions(type);
4075
- newEditorOptions = {
4076
- ...defaultOptions,
4077
- ...newEditorOptions
4078
- };
4079
- let alignment = oldWidgetInfo.alignment;
4080
-
4081
- // Widgets that support multiple alignments will "lock in" the
4082
- // alignment to the alignment that would be listed first in the
4083
- // select box. If the widget only supports one alignment, the
4084
- // alignment value will likely just end up as "default".
4085
- if (alignment == null || alignment === "default") {
4086
- alignment = getSupportedAlignments(type)?.[0];
4087
- if (!alignment) {
4088
- throw new PerseusError("No default alignment found when upgrading widget", Errors.Internal, {
4089
- metadata: {
4090
- widgetType: type
4091
- }
4092
- });
4093
- }
4094
- }
4095
- let widgetStatic = oldWidgetInfo.static;
4096
- if (widgetStatic == null) {
4097
- widgetStatic = DEFAULT_STATIC;
4098
- }
4099
- return {
4100
- ...oldWidgetInfo,
4101
- // maintain other info, like type
4102
- // After upgrading we guarantee that the version is up-to-date
4103
- version: latestVersion,
4104
- // Default graded to true (so null/undefined becomes true):
4105
- graded: oldWidgetInfo.graded != null ? oldWidgetInfo.graded : true,
4106
- alignment: alignment,
4107
- static: widgetStatic,
4108
- options: newEditorOptions
4109
- };
4110
- };
4111
- function getUpgradedWidgetOptions(oldWidgetOptions) {
4112
- return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
4113
- if (!widgetInfo.type || !widgetInfo.alignment) {
4114
- const newValues = {};
4115
- if (!widgetInfo.type) {
4116
- // TODO: why does widget have no type?
4117
- // We don't want to derive type from widget ID
4118
- // see: LEMS-1845
4119
- newValues.type = widgetId.split(" ")[0];
4120
- }
4121
- if (!widgetInfo.alignment) {
4122
- newValues.alignment = "default";
4123
- }
4124
- widgetInfo = {
4125
- ...widgetInfo,
4126
- ...newValues
4127
- };
4128
- }
4129
- return upgradeWidgetInfoToLatestVersion(widgetInfo);
4130
- });
4131
- }
326
+ const DEFAULT_STATIC=false;const upgradeWidgetInfoToLatestVersion=oldWidgetInfo=>{const type=oldWidgetInfo.type;if(!___default.default.isString(type)){throw new PerseusError("widget type must be a string, but was: "+type,Errors.Internal)}if(!isWidgetRegistered(type)){return oldWidgetInfo}const initialVersion=oldWidgetInfo.version||{major:0,minor:0};const latestVersion=getCurrentVersion(type);if(initialVersion.major>latestVersion.major||initialVersion.major===latestVersion.major&&initialVersion.minor>latestVersion.minor){return oldWidgetInfo}let newEditorOptions=___default.default.clone(oldWidgetInfo.options)||{};const upgradePropsMap=getWidgetOptionsUpgrades(type);if(___default.default.keys(newEditorOptions).length!==0){for(let nextVersion=initialVersion.major+1;nextVersion<=latestVersion.major;nextVersion++){if(upgradePropsMap[String(nextVersion)]){newEditorOptions=upgradePropsMap[String(nextVersion)](newEditorOptions);}else {throw new PerseusError("No upgrade found for widget. Cannot render.",Errors.Internal,{metadata:{type,fromMajorVersion:nextVersion-1,toMajorVersion:nextVersion,oldWidgetInfo:JSON.stringify(oldWidgetInfo)}})}}}const defaultOptions=getDefaultWidgetOptions(type);newEditorOptions={...defaultOptions,...newEditorOptions};let alignment=oldWidgetInfo.alignment;if(alignment==null||alignment==="default"){alignment=getSupportedAlignments(type)?.[0];if(!alignment){throw new PerseusError("No default alignment found when upgrading widget",Errors.Internal,{metadata:{widgetType:type}})}}let widgetStatic=oldWidgetInfo.static;if(widgetStatic==null){widgetStatic=DEFAULT_STATIC;}return {...oldWidgetInfo,version:latestVersion,graded:oldWidgetInfo.graded!=null?oldWidgetInfo.graded:true,alignment:alignment,static:widgetStatic,options:newEditorOptions}};function getUpgradedWidgetOptions(oldWidgetOptions){return mapObject(oldWidgetOptions,(widgetInfo,widgetId)=>{if(!widgetInfo.type||!widgetInfo.alignment){const newValues={};if(!widgetInfo.type){newValues.type=widgetId.split(" ")[0];}if(!widgetInfo.alignment){newValues.alignment="default";}widgetInfo={...widgetInfo,...newValues};}return upgradeWidgetInfoToLatestVersion(widgetInfo)})}
4132
327
 
4133
- /**
4134
- * Return a copy of a Perseus item with rubric data removed (ie answers)
4135
- *
4136
- * @param originalItem - the original, full Perseus item (which includes the rubric - aka answer data)
4137
- */
4138
- function splitPerseusItem(originalItem) {
4139
- const item = ___default.default.clone(originalItem);
4140
- const originalWidgets = item.widgets ?? {};
4141
- const upgradedWidgets = getUpgradedWidgetOptions(originalWidgets);
4142
- const splitWidgets = {};
4143
- for (const [id, widget] of Object.entries(upgradedWidgets)) {
4144
- const publicWidgetOptionsFun = getPublicWidgetOptionsFunction(widget.type);
4145
- splitWidgets[id] = {
4146
- ...widget,
4147
- options: publicWidgetOptionsFun(widget.options)
4148
- };
4149
- }
4150
- return {
4151
- ...item,
4152
- widgets: splitWidgets
4153
- };
4154
- }
328
+ function splitPerseusItem(originalItem){const item=___default.default.clone(originalItem);const originalWidgets=item.widgets??{};const upgradedWidgets=getUpgradedWidgetOptions(originalWidgets);const splitWidgets={};for(const[id,widget]of Object.entries(upgradedWidgets)){const publicWidgetOptionsFun=getPublicWidgetOptionsFunction(widget.type);splitWidgets[id]={...widget,options:publicWidgetOptionsFun(widget.options)};}return {...item,widgets:splitWidgets}}
4155
329
 
4156
330
  exports.CoreWidgetRegistry = coreWidgetRegistry;
4157
331
  exports.Errors = Errors;