@khanacademy/perseus 51.0.1 → 52.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +681 -1996
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +102 -2
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +36 -6
- package/dist/index.js +736 -2004
- package/dist/index.js.map +1 -1
- package/dist/item-version.d.ts +1 -1
- package/dist/renderer-util.d.ts +7 -2
- package/dist/strings.d.ts +100 -0
- package/dist/strings.js +158 -13
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +13 -12
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
- package/dist/widget-type-utils.d.ts +1 -22
- package/dist/widgets/categorizer/categorizer.d.ts +5 -5
- package/dist/widgets/cs-program/cs-program.d.ts +3 -2
- package/dist/widgets/dropdown/dropdown.d.ts +2 -1
- package/dist/widgets/expression/expression.d.ts +8 -10
- package/dist/widgets/grapher/grapher.d.ts +2 -1
- package/dist/widgets/group/group.d.ts +4 -2
- package/dist/widgets/group/score-group.d.ts +2 -2
- package/dist/widgets/group/validate-group.d.ts +4 -0
- package/dist/widgets/iframe/iframe.d.ts +3 -2
- package/dist/widgets/input-number/input-number.d.ts +2 -1
- package/dist/widgets/interactive-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/linear.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +12 -1
- package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +6 -0
- package/dist/widgets/label-image/label-image.d.ts +5 -4
- package/dist/widgets/matcher/matcher.d.ts +2 -1
- package/dist/widgets/matrix/matrix.d.ts +4 -2
- package/dist/widgets/measurer/measurer.d.ts +3 -8
- package/dist/widgets/mock-widgets/mock-widget-types.d.ts +17 -0
- package/dist/widgets/mock-widgets/mock-widget.d.ts +5 -4
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +2 -1
- package/dist/widgets/mock-widgets/validate-mock-widget.d.ts +4 -0
- package/dist/widgets/number-line/number-line.d.ts +3 -1
- package/dist/widgets/numeric-input/numeric-input.d.ts +2 -1
- package/dist/widgets/orderer/orderer.d.ts +6 -2
- package/dist/widgets/passage-ref/passage-ref.d.ts +2 -5
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +0 -4
- package/dist/widgets/plotter/plotter.d.ts +4 -2
- package/dist/widgets/radio/radio-component.d.ts +1 -1
- package/dist/widgets/radio/radio.d.ts +5 -9
- package/dist/widgets/sorter/sorter.d.ts +3 -1
- package/dist/widgets/table/table.d.ts +3 -1
- package/dist/widgets.d.ts +3 -2
- package/package.json +14 -14
- package/dist/util/is-real-json-parse.d.ts +0 -1
- package/dist/util/parse-perseus-json/error-tracking-parse-context.d.ts +0 -9
- package/dist/util/parse-perseus-json/exhaustive-test-tool/index.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/any.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/array.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/boolean.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/constant.d.ts +0 -4
- package/dist/util/parse-perseus-json/general-purpose-parsers/convert.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/defaulted.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.d.ts +0 -21
- package/dist/util/parse-perseus-json/general-purpose-parsers/discriminated-union.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/enumeration.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/index.d.ts +0 -17
- package/dist/util/parse-perseus-json/general-purpose-parsers/is-object.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/nullable.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/number.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/object.d.ts +0 -6
- package/dist/util/parse-perseus-json/general-purpose-parsers/optional.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/pair.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.d.ts +0 -7
- package/dist/util/parse-perseus-json/general-purpose-parsers/pipe-parsers.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/record.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/string-to-number.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/string.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/test-helpers.d.ts +0 -8
- package/dist/util/parse-perseus-json/general-purpose-parsers/trio.d.ts +0 -2
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.d.ts +0 -7
- package/dist/util/parse-perseus-json/general-purpose-parsers/union.typetest.d.ts +0 -1
- package/dist/util/parse-perseus-json/general-purpose-parsers/unknown.d.ts +0 -2
- package/dist/util/parse-perseus-json/index.d.ts +0 -45
- package/dist/util/parse-perseus-json/object-path.d.ts +0 -2
- package/dist/util/parse-perseus-json/parse-failure-detail.d.ts +0 -2
- package/dist/util/parse-perseus-json/parse.d.ts +0 -3
- package/dist/util/parse-perseus-json/parser-types.d.ts +0 -30
- package/dist/util/parse-perseus-json/perseus-parsers/categorizer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/cs-program-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/definition-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/dropdown-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/explanation-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/expression-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-set-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/graded-group-widget.d.ts +0 -16
- package/dist/util/parse-perseus-json/perseus-parsers/grapher-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/group-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/hint.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/iframe-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/image-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/images-map.d.ts +0 -5
- package/dist/util/parse-perseus-json/perseus-parsers/input-number-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/interaction-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/interactive-graph-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/label-image-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/matcher-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/matrix-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/measurer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/molecule-renderer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/number-line-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/numeric-input-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/orderer-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/passage-ref-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/passage-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-article.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-image-background.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-item.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/perseus-renderer.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/phet-simulation-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/plotter-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/python-program-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/radio-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/sorter-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/table-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/versioned-widget-options.d.ts +0 -36
- package/dist/util/parse-perseus-json/perseus-parsers/video-widget.d.ts +0 -3
- package/dist/util/parse-perseus-json/perseus-parsers/widget.d.ts +0 -7
- package/dist/util/parse-perseus-json/perseus-parsers/widgets-map.d.ts +0 -3
- package/dist/util/parse-perseus-json/result.d.ts +0 -16
- package/dist/util/snowman-utils.d.ts +0 -17
- package/dist/widgets/categorizer/categorizer.util.d.ts +0 -17
package/dist/es/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
-
import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize,
|
|
3
|
+
import { PerseusError, Errors, expressionLogic, getExpressionPublicWidgetOptions, getDecimalSeparator, approximateDeepEqual, pluck, getWidgetIdsFromContent, mapObject, passageRefLogic, radioLogic, getCategorizerPublicWidgetOptions, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, measurerLogic, getOrdererPublicWidgetOptions, addLibraryVersionToPerseusDebug, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
|
|
4
|
+
export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
|
|
4
5
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
5
6
|
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
6
|
-
import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
|
|
7
|
+
import { scoreExpression, validateExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, validateRadio, scoreCategorizer, validateCategorizer, scoreCSProgram, scoreDropdown, validateDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, validateMatrix, scoreNumberLine, validateNumberLine, scoreOrderer, validateOrderer, scorePlotter, validatePlotter, scoreSorter, validateSorter, scoreTable, validateTable } from '@khanacademy/perseus-score';
|
|
7
8
|
import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
8
9
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
9
10
|
import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -22,7 +23,7 @@ import { v4 } from 'uuid';
|
|
|
22
23
|
import PropTypes from 'prop-types';
|
|
23
24
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
25
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import { point, KhanMath, number
|
|
26
|
+
import { point, KhanMath, number, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
|
|
26
27
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
27
28
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
28
29
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -232,6 +233,14 @@ const strings = {
|
|
|
232
233
|
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
234
|
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
235
|
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
|
|
236
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
237
|
+
srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s line segments on a coordinate plane.",
|
|
238
|
+
srMultipleSegmentIndividualLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
|
|
239
|
+
srSingleSegmentLabel: "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
|
|
240
|
+
srSegmentLength: "Segment length %(length)s units.",
|
|
241
|
+
srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.",
|
|
242
|
+
srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.",
|
|
243
|
+
srSegmentGrabHandle: "Segment from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
235
244
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
236
245
|
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
237
246
|
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
@@ -239,7 +248,21 @@ const strings = {
|
|
|
239
248
|
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
240
249
|
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
241
250
|
srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
|
|
242
|
-
srRayTerminalPoint: "Through point at %(x)s comma %(y)s."
|
|
251
|
+
srRayTerminalPoint: "Through point at %(x)s comma %(y)s.",
|
|
252
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
253
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
254
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
255
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
256
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
257
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
258
|
+
srQuadraticGraphVertexQuadrant: "Vertex is in quadrant %(quadrant)s.",
|
|
259
|
+
srQuadraticTwoXIntercepts: "The X-intercepts are at %(intercept1)s comma 0 and %(intercept2)s comma 0.",
|
|
260
|
+
srQuadraticOneXIntercept: "The X-intercept is at %(intercept)s comma 0.",
|
|
261
|
+
srQuadraticYIntercept: "The Y-intercept is at 0 comma %(intercept)s.",
|
|
262
|
+
srQuadraticPointOrigin: "Point %(pointNumber)s on parabola at the origin.",
|
|
263
|
+
srQuadraticPointAxis: "Point %(pointNumber)s on parabola at %(x)s comma %(y)s.",
|
|
264
|
+
srQuadraticPointQuadrant: "Point %(pointNumber)s on parabola in quadrant %(quadrant)s at %(x)s comma %(y)s.",
|
|
265
|
+
srQuadraticInteractiveElements: "Parabola with points at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, and %(point3X)s comma %(point3Y)s."
|
|
243
266
|
// The above strings are used for interactive graph SR descriptions.
|
|
244
267
|
};
|
|
245
268
|
|
|
@@ -509,6 +532,43 @@ const mockStrings = {
|
|
|
509
532
|
endingSideX,
|
|
510
533
|
endingSideY
|
|
511
534
|
}) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
|
|
535
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
536
|
+
srMultipleSegmentGraphAriaLabel: ({
|
|
537
|
+
countOfSegments
|
|
538
|
+
}) => `${countOfSegments} segments on a coordinate plane.`,
|
|
539
|
+
srMultipleSegmentIndividualLabel: ({
|
|
540
|
+
point1X,
|
|
541
|
+
point1Y,
|
|
542
|
+
point2X,
|
|
543
|
+
point2Y,
|
|
544
|
+
indexOfSegment
|
|
545
|
+
}) => `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
|
|
546
|
+
srSingleSegmentLabel: ({
|
|
547
|
+
point1X,
|
|
548
|
+
point1Y,
|
|
549
|
+
point2X,
|
|
550
|
+
point2Y
|
|
551
|
+
}) => `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,
|
|
552
|
+
srSegmentLength: ({
|
|
553
|
+
length
|
|
554
|
+
}) => `Segment length ${length} units.`,
|
|
555
|
+
srSingleSegmentGraphEndpointAriaLabel: ({
|
|
556
|
+
endpointNumber,
|
|
557
|
+
x,
|
|
558
|
+
y
|
|
559
|
+
}) => `Endpoint ${endpointNumber} at ${x} comma ${y}.`,
|
|
560
|
+
srMultipleSegmentGraphEndpointAriaLabel: ({
|
|
561
|
+
endpointNumber,
|
|
562
|
+
x,
|
|
563
|
+
y,
|
|
564
|
+
indexOfSegment
|
|
565
|
+
}) => `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`,
|
|
566
|
+
srSegmentGrabHandle: ({
|
|
567
|
+
point1X,
|
|
568
|
+
point1Y,
|
|
569
|
+
point2X,
|
|
570
|
+
point2Y
|
|
571
|
+
}) => `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
|
|
512
572
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
513
573
|
srLinearSystemPoints: ({
|
|
514
574
|
lineNumber,
|
|
@@ -543,7 +603,48 @@ const mockStrings = {
|
|
|
543
603
|
srRayTerminalPoint: ({
|
|
544
604
|
x,
|
|
545
605
|
y
|
|
546
|
-
}) => `Through point at ${x} comma ${y}
|
|
606
|
+
}) => `Through point at ${x} comma ${y}.`,
|
|
607
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
608
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
609
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
610
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
611
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
612
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
613
|
+
srQuadraticGraphVertexQuadrant: ({
|
|
614
|
+
quadrant
|
|
615
|
+
}) => `Vertex is in quadrant ${quadrant}.`,
|
|
616
|
+
srQuadraticTwoXIntercepts: ({
|
|
617
|
+
intercept1,
|
|
618
|
+
intercept2
|
|
619
|
+
}) => `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`,
|
|
620
|
+
srQuadraticOneXIntercept: ({
|
|
621
|
+
intercept
|
|
622
|
+
}) => `The X-intercept is at ${intercept} comma 0.`,
|
|
623
|
+
srQuadraticYIntercept: ({
|
|
624
|
+
intercept
|
|
625
|
+
}) => `The Y-intercept is at 0 comma ${intercept}.`,
|
|
626
|
+
srQuadraticPointOrigin: ({
|
|
627
|
+
pointNumber
|
|
628
|
+
}) => `Point ${pointNumber} on parabola at the origin.`,
|
|
629
|
+
srQuadraticPointAxis: ({
|
|
630
|
+
pointNumber,
|
|
631
|
+
x,
|
|
632
|
+
y
|
|
633
|
+
}) => `Point ${pointNumber} on parabola at ${x} comma ${y}.`,
|
|
634
|
+
srQuadraticPointQuadrant: ({
|
|
635
|
+
pointNumber,
|
|
636
|
+
x,
|
|
637
|
+
y,
|
|
638
|
+
quadrant
|
|
639
|
+
}) => `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`,
|
|
640
|
+
srQuadraticInteractiveElements: ({
|
|
641
|
+
point1X,
|
|
642
|
+
point1Y,
|
|
643
|
+
point2X,
|
|
644
|
+
point2Y,
|
|
645
|
+
point3X,
|
|
646
|
+
point3Y
|
|
647
|
+
}) => `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`
|
|
547
648
|
// The above strings are used for interactive graph SR descriptions.
|
|
548
649
|
};
|
|
549
650
|
|
|
@@ -1830,23 +1931,6 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
1830
1931
|
times: widgetOptions.times
|
|
1831
1932
|
};
|
|
1832
1933
|
};
|
|
1833
|
-
const propUpgrades$2 = {
|
|
1834
|
-
/* c8 ignore next */
|
|
1835
|
-
"1": v0props => ({
|
|
1836
|
-
times: v0props.times,
|
|
1837
|
-
buttonSets: v0props.buttonSets,
|
|
1838
|
-
functions: v0props.functions,
|
|
1839
|
-
buttonsVisible: v0props.buttonsVisible,
|
|
1840
|
-
visibleLabel: v0props.visibleLabel,
|
|
1841
|
-
ariaLabel: v0props.ariaLabel,
|
|
1842
|
-
answerForms: [{
|
|
1843
|
-
considered: "correct",
|
|
1844
|
-
form: v0props.form,
|
|
1845
|
-
simplify: v0props.simplify,
|
|
1846
|
-
value: v0props.value
|
|
1847
|
-
}]
|
|
1848
|
-
})
|
|
1849
|
-
};
|
|
1850
1934
|
const ExpressionWithDependencies = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
1851
1935
|
const deps = useDependencies();
|
|
1852
1936
|
return /*#__PURE__*/React.createElement(Expression, _extends({
|
|
@@ -1886,17 +1970,18 @@ var Expression$1 = {
|
|
|
1886
1970
|
ariaLabel
|
|
1887
1971
|
};
|
|
1888
1972
|
},
|
|
1889
|
-
version:
|
|
1890
|
-
|
|
1891
|
-
minor: 0
|
|
1892
|
-
},
|
|
1893
|
-
propUpgrades: propUpgrades$2,
|
|
1973
|
+
version: expressionLogic.version,
|
|
1974
|
+
propUpgrades: expressionLogic.widgetOptionsUpgrades,
|
|
1894
1975
|
// For use by the editor
|
|
1895
1976
|
isLintable: true,
|
|
1896
1977
|
// TODO(LEMS-2656): remove TS suppression
|
|
1897
1978
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1898
1979
|
scorer: scoreExpression,
|
|
1899
1980
|
// TODO(LEMS-2656): remove TS suppression
|
|
1981
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
1982
|
+
validator: validateExpression,
|
|
1983
|
+
getPublicWidgetOptions: getExpressionPublicWidgetOptions,
|
|
1984
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
1900
1985
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
1901
1986
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
1902
1987
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
@@ -8580,7 +8665,7 @@ class Graphie$1 {
|
|
|
8580
8665
|
// We round the coordinates to make testing easier, because trig
|
|
8581
8666
|
// operations return unround numbers.
|
|
8582
8667
|
// TODO(benchristel): move rounding to scalePoint?
|
|
8583
|
-
const round = x => number
|
|
8668
|
+
const round = x => number.round(x, 6);
|
|
8584
8669
|
const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
|
|
8585
8670
|
const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
|
|
8586
8671
|
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
@@ -17336,7 +17421,7 @@ const inlineNodeTypes = {
|
|
|
17336
17421
|
|
|
17337
17422
|
// @ts-expect-error - TS2345 - Argument of type '{ readonly columns: { readonly react: (node: any, output: any, state: any) => Element; readonly order: -2; readonly match: any; readonly parse: (capture: any, parse: any, state: any) => any; }; ... 35 more ...; readonly text: TextInOutRule; }' is not assignable to parameter of type 'ParserRules'.
|
|
17338
17423
|
const builtParser$1 = SimpleMarkdown.parserFor(rules$1);
|
|
17339
|
-
const parse$
|
|
17424
|
+
const parse$3 = (source, state) => {
|
|
17340
17425
|
const paragraphedSource = source + "\n\n";
|
|
17341
17426
|
return builtParser$1(paragraphedSource, _extends({}, state, {
|
|
17342
17427
|
inline: false
|
|
@@ -17419,14 +17504,14 @@ const getContent = ast => {
|
|
|
17419
17504
|
*/
|
|
17420
17505
|
const characterCount = source => {
|
|
17421
17506
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
17422
|
-
const ast = parse$
|
|
17507
|
+
const ast = parse$3(source);
|
|
17423
17508
|
const content = getContent(ast).join("");
|
|
17424
17509
|
return content.length;
|
|
17425
17510
|
};
|
|
17426
17511
|
var PerseusMarkdown = {
|
|
17427
17512
|
characterCount: characterCount,
|
|
17428
17513
|
traverseContent: traverseContent,
|
|
17429
|
-
parse: parse$
|
|
17514
|
+
parse: parse$3,
|
|
17430
17515
|
parseInline: inlineParser,
|
|
17431
17516
|
reactFor: SimpleMarkdown.reactFor,
|
|
17432
17517
|
ruleOutput: SimpleMarkdown.ruleOutput(rules$1, "react"),
|
|
@@ -17568,138 +17653,6 @@ guess, state) {
|
|
|
17568
17653
|
});
|
|
17569
17654
|
}
|
|
17570
17655
|
|
|
17571
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
17572
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
17573
|
-
|
|
17574
|
-
/**
|
|
17575
|
-
* Regex for widget placeholders in a string.
|
|
17576
|
-
*
|
|
17577
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
17578
|
-
* Second capture group is the widget type (ex. "radio)
|
|
17579
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
17580
|
-
*/
|
|
17581
|
-
function getWidgetRegex() {
|
|
17582
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
17583
|
-
}
|
|
17584
|
-
|
|
17585
|
-
/**
|
|
17586
|
-
* Add a widget placeholder using the widget ID.
|
|
17587
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
17588
|
-
*
|
|
17589
|
-
* @param {string} id
|
|
17590
|
-
* @returns {string}
|
|
17591
|
-
*/
|
|
17592
|
-
function addWidget(id) {
|
|
17593
|
-
return `[[☃ ${id}]]`;
|
|
17594
|
-
}
|
|
17595
|
-
|
|
17596
|
-
/**
|
|
17597
|
-
* Get a widget type by a widget's ID
|
|
17598
|
-
* (please don't derive type from ID)
|
|
17599
|
-
*
|
|
17600
|
-
* @param {string} widgetId the ID of the widget
|
|
17601
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17602
|
-
* @returns {string} the widget type (ie "radio")
|
|
17603
|
-
*/
|
|
17604
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
17605
|
-
var _widget$type;
|
|
17606
|
-
const widget = widgetMap[WidgetId];
|
|
17607
|
-
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
17608
|
-
}
|
|
17609
|
-
|
|
17610
|
-
/**
|
|
17611
|
-
* Does the content have a specific type of widget?
|
|
17612
|
-
*
|
|
17613
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
17614
|
-
* @param {string} content the string to search through
|
|
17615
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17616
|
-
* @returns {boolean} if the content includes the widget type
|
|
17617
|
-
*/
|
|
17618
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
17619
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
17620
|
-
}
|
|
17621
|
-
|
|
17622
|
-
/**
|
|
17623
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
17624
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
17625
|
-
*
|
|
17626
|
-
* Content should contain Perseus widget placeholders,
|
|
17627
|
-
* which look like: '[[☃ radio 1]]'.
|
|
17628
|
-
*
|
|
17629
|
-
* @param {string} content
|
|
17630
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
17631
|
-
*/
|
|
17632
|
-
function getWidgetIdsFromContent(content) {
|
|
17633
|
-
const widgets = [];
|
|
17634
|
-
const localWidgetRegex = getWidgetRegex();
|
|
17635
|
-
let match = localWidgetRegex.exec(content);
|
|
17636
|
-
while (match !== null) {
|
|
17637
|
-
widgets.push(match[1]);
|
|
17638
|
-
match = localWidgetRegex.exec(content);
|
|
17639
|
-
}
|
|
17640
|
-
return widgets;
|
|
17641
|
-
}
|
|
17642
|
-
|
|
17643
|
-
/**
|
|
17644
|
-
* Get a list of widget IDs from content,
|
|
17645
|
-
* but only for specific widget types
|
|
17646
|
-
*
|
|
17647
|
-
* @param {string} type the type of widget (ie "radio")
|
|
17648
|
-
* @param {string} content the string to parse
|
|
17649
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17650
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
17651
|
-
*/
|
|
17652
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
17653
|
-
const rv = [];
|
|
17654
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
17655
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
17656
|
-
const widget = widgetMap[widgetId];
|
|
17657
|
-
if ((widget == null ? void 0 : widget.type) === type) {
|
|
17658
|
-
rv.push(widgetId);
|
|
17659
|
-
}
|
|
17660
|
-
});
|
|
17661
|
-
return rv;
|
|
17662
|
-
}
|
|
17663
|
-
|
|
17664
|
-
/**
|
|
17665
|
-
* Pull the widget map out of ItemData
|
|
17666
|
-
*
|
|
17667
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
17668
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
17669
|
-
*/
|
|
17670
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
17671
|
-
return itemData.question.widgets;
|
|
17672
|
-
}
|
|
17673
|
-
|
|
17674
|
-
/**
|
|
17675
|
-
* Get the widget information from a WidgetMap
|
|
17676
|
-
*
|
|
17677
|
-
* @param {string} widgetId the ID of the widget
|
|
17678
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
17679
|
-
*/
|
|
17680
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
17681
|
-
var _widgetMap$widgetId;
|
|
17682
|
-
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
17683
|
-
}
|
|
17684
|
-
|
|
17685
|
-
/**
|
|
17686
|
-
* Get select widgets from a widget map.
|
|
17687
|
-
*
|
|
17688
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
17689
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
17690
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
17691
|
-
*/
|
|
17692
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
17693
|
-
const widgets = {};
|
|
17694
|
-
widgetIds.forEach(widgetId => {
|
|
17695
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
17696
|
-
if (widget) {
|
|
17697
|
-
widgets[widgetId] = widget;
|
|
17698
|
-
}
|
|
17699
|
-
});
|
|
17700
|
-
return widgets;
|
|
17701
|
-
}
|
|
17702
|
-
|
|
17703
17656
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17704
17657
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17705
17658
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -17804,13 +17757,17 @@ const getWidgetExport = name => {
|
|
|
17804
17757
|
var _widgets$name3;
|
|
17805
17758
|
return (_widgets$name3 = widgets[name]) != null ? _widgets$name3 : null;
|
|
17806
17759
|
};
|
|
17760
|
+
const getWidgetValidator = name => {
|
|
17761
|
+
var _widgets$name$validat, _widgets$name4;
|
|
17762
|
+
return (_widgets$name$validat = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.validator) != null ? _widgets$name$validat : null;
|
|
17763
|
+
};
|
|
17807
17764
|
const getWidgetScorer = name => {
|
|
17808
|
-
var _widgets$name$scorer, _widgets$
|
|
17809
|
-
return (_widgets$name$scorer = (_widgets$
|
|
17765
|
+
var _widgets$name$scorer, _widgets$name5;
|
|
17766
|
+
return (_widgets$name$scorer = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.scorer) != null ? _widgets$name$scorer : null;
|
|
17810
17767
|
};
|
|
17811
17768
|
const getPublicWidgetOptionsFunction = name => {
|
|
17812
|
-
var _widgets$name$getPubl, _widgets$
|
|
17813
|
-
return (_widgets$name$getPubl = (_widgets$
|
|
17769
|
+
var _widgets$name$getPubl, _widgets$name6;
|
|
17770
|
+
return (_widgets$name$getPubl = (_widgets$name6 = widgets[name]) == null ? void 0 : _widgets$name6.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
17814
17771
|
};
|
|
17815
17772
|
const getEditor = name => {
|
|
17816
17773
|
return _.has(editors, name) ? editors[name] : null;
|
|
@@ -18115,6 +18072,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18115
18072
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
18116
18073
|
getWidget: getWidget,
|
|
18117
18074
|
getWidgetExport: getWidgetExport,
|
|
18075
|
+
getWidgetValidator: getWidgetValidator,
|
|
18118
18076
|
getWidgetScorer: getWidgetScorer,
|
|
18119
18077
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18120
18078
|
getEditor: getEditor,
|
|
@@ -18137,7 +18095,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18137
18095
|
});
|
|
18138
18096
|
|
|
18139
18097
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
18140
|
-
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
18141
18098
|
return mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18142
18099
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
18143
18100
|
const newValues = {};
|
|
@@ -18152,22 +18109,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
|
18152
18109
|
}
|
|
18153
18110
|
widgetInfo = _extends({}, widgetInfo, newValues);
|
|
18154
18111
|
}
|
|
18112
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
18155
18113
|
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
18156
18114
|
});
|
|
18157
18115
|
}
|
|
18116
|
+
|
|
18117
|
+
/**
|
|
18118
|
+
* Checks the given user input to see if any answerable widgets have not been
|
|
18119
|
+
* "filled in" (ie. if they're empty). Another way to think about this
|
|
18120
|
+
* function is that its a check to see if we can score the provided input.
|
|
18121
|
+
*/
|
|
18158
18122
|
function emptyWidgetsFunctional(widgets,
|
|
18159
18123
|
// This is a port of old code, I'm not sure why
|
|
18160
18124
|
// we need widgetIds vs the keys of the widgets object
|
|
18161
18125
|
widgetIds, userInputMap, strings, locale) {
|
|
18162
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
18163
18126
|
return widgetIds.filter(id => {
|
|
18164
|
-
const widget =
|
|
18165
|
-
if (!widget || widget.static) {
|
|
18127
|
+
const widget = widgets[id];
|
|
18128
|
+
if (!widget || widget.static === true) {
|
|
18166
18129
|
// Static widgets shouldn't count as empty
|
|
18167
18130
|
return false;
|
|
18168
18131
|
}
|
|
18169
|
-
const
|
|
18170
|
-
const
|
|
18132
|
+
const validator = getWidgetValidator(widget.type);
|
|
18133
|
+
const userInput = userInputMap[id];
|
|
18134
|
+
const validationData = widget.options;
|
|
18135
|
+
const score = validator == null ? void 0 : validator(userInput, validationData, strings, locale);
|
|
18171
18136
|
if (score) {
|
|
18172
18137
|
return scoreIsEmpty(score);
|
|
18173
18138
|
}
|
|
@@ -18201,13 +18166,18 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
18201
18166
|
});
|
|
18202
18167
|
const widgetScores = {};
|
|
18203
18168
|
gradedWidgetIds.forEach(id => {
|
|
18169
|
+
var _validator;
|
|
18204
18170
|
const widget = upgradedWidgets[id];
|
|
18205
18171
|
if (!widget) {
|
|
18206
18172
|
return;
|
|
18207
18173
|
}
|
|
18208
18174
|
const userInput = userInputMap[id];
|
|
18175
|
+
const validator = getWidgetValidator(widget.type);
|
|
18209
18176
|
const scorer = getWidgetScorer(widget.type);
|
|
18210
|
-
|
|
18177
|
+
|
|
18178
|
+
// We do validation (empty checks) first and then scoring. If
|
|
18179
|
+
// validation fails, it's result is itself a PerseusScore.
|
|
18180
|
+
const score = (_validator = validator == null ? void 0 : validator(userInput, widget.options, strings, locale)) != null ? _validator : scorer == null ? void 0 : scorer(userInput, widget.options, strings, locale);
|
|
18211
18181
|
if (score != null) {
|
|
18212
18182
|
widgetScores[id] = score;
|
|
18213
18183
|
}
|
|
@@ -19607,7 +19577,7 @@ class Renderer extends React.Component {
|
|
|
19607
19577
|
const apiOptions = this.getApiOptions();
|
|
19608
19578
|
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19609
19579
|
|
|
19610
|
-
// The widget needs access to its "
|
|
19580
|
+
// The widget needs access to its "scoring data" at all times when in review
|
|
19611
19581
|
// mode (which is really just part of its widget info).
|
|
19612
19582
|
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19613
19583
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
@@ -19938,7 +19908,7 @@ function _objectWithoutPropertiesLoose(r, e) {
|
|
|
19938
19908
|
return t;
|
|
19939
19909
|
}
|
|
19940
19910
|
|
|
19941
|
-
const _excluded$
|
|
19911
|
+
const _excluded$4 = ["position", "visibility", "left", "top"];
|
|
19942
19912
|
const zIndex = 10;
|
|
19943
19913
|
let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
|
|
19944
19914
|
HorizontalDirection["Left"] = "left";
|
|
@@ -19989,7 +19959,7 @@ const TooltipArrow = _ref => {
|
|
|
19989
19959
|
left = 100,
|
|
19990
19960
|
top = 0
|
|
19991
19961
|
} = _ref,
|
|
19992
|
-
props = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
19962
|
+
props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
|
|
19993
19963
|
// TODO(aria): Think about adding a box-shadow to the triangle here
|
|
19994
19964
|
// See http://css-tricks.com/triangle-with-shadow/
|
|
19995
19965
|
|
|
@@ -20363,7 +20333,7 @@ InputWithExamples.defaultProps = {
|
|
|
20363
20333
|
className: ""
|
|
20364
20334
|
};
|
|
20365
20335
|
|
|
20366
|
-
const _excluded$
|
|
20336
|
+
const _excluded$3 = ["keypadElement", "onFocus", "value"];
|
|
20367
20337
|
class SimpleKeypadInput extends React.Component {
|
|
20368
20338
|
constructor(...args) {
|
|
20369
20339
|
super(...args);
|
|
@@ -20408,7 +20378,7 @@ class SimpleKeypadInput extends React.Component {
|
|
|
20408
20378
|
onFocus,
|
|
20409
20379
|
value
|
|
20410
20380
|
} = _this$props,
|
|
20411
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
20381
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
|
|
20412
20382
|
return (
|
|
20413
20383
|
/*#__PURE__*/
|
|
20414
20384
|
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
@@ -22745,7 +22715,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
22745
22715
|
// TODO(LEMS-2656): remove TS suppression
|
|
22746
22716
|
// @ts-expect-error: State is not assignable to ParseState
|
|
22747
22717
|
const builtParser = SimpleMarkdown.parserFor(rules);
|
|
22748
|
-
const parse$
|
|
22718
|
+
const parse$2 = (source, state) => {
|
|
22749
22719
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
22750
22720
|
state = state || {};
|
|
22751
22721
|
const paragraphedSource = source + "\n\n";
|
|
@@ -22755,7 +22725,7 @@ const parse$3 = (source, state) => {
|
|
|
22755
22725
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
22756
22726
|
const output = SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules, "react"));
|
|
22757
22727
|
var PassageMarkdown = {
|
|
22758
|
-
parse: parse$
|
|
22728
|
+
parse: parse$2,
|
|
22759
22729
|
output,
|
|
22760
22730
|
START_REF_PREFIX,
|
|
22761
22731
|
END_REF_PREFIX,
|
|
@@ -23312,10 +23282,7 @@ var PassageRef$1 = {
|
|
|
23312
23282
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23313
23283
|
summaryText: widgetOptions.summaryText
|
|
23314
23284
|
}),
|
|
23315
|
-
version:
|
|
23316
|
-
major: 0,
|
|
23317
|
-
minor: 1
|
|
23318
|
-
},
|
|
23285
|
+
version: passageRefLogic.version,
|
|
23319
23286
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23320
23287
|
scorer: () => scoreNoop()
|
|
23321
23288
|
};
|
|
@@ -24297,14 +24264,14 @@ var Choice$1 = /*#__PURE__*/React.forwardRef((props, ref) => /*#__PURE__*/React.
|
|
|
24297
24264
|
forwardedRef: ref
|
|
24298
24265
|
})));
|
|
24299
24266
|
|
|
24300
|
-
const _excluded$
|
|
24267
|
+
const _excluded$2 = ["content", "forwardedRef", "showContent"];
|
|
24301
24268
|
const ChoiceNoneAbove = function ChoiceNoneAbove(_ref) {
|
|
24302
24269
|
let {
|
|
24303
24270
|
content,
|
|
24304
24271
|
forwardedRef,
|
|
24305
24272
|
showContent = true
|
|
24306
24273
|
} = _ref,
|
|
24307
|
-
rest = _objectWithoutPropertiesLoose(_ref, _excluded$
|
|
24274
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$2);
|
|
24308
24275
|
const {
|
|
24309
24276
|
strings
|
|
24310
24277
|
} = usePerseusI18n();
|
|
@@ -24968,7 +24935,7 @@ class Radio$1 extends React.Component {
|
|
|
24968
24935
|
// Current versions of the radio widget always pass in the
|
|
24969
24936
|
// "correct" value through the choices. Old serialized state
|
|
24970
24937
|
// for radio widgets doesn't have this though, so we have to
|
|
24971
|
-
// pull the correctness out of the review mode
|
|
24938
|
+
// pull the correctness out of the review mode scoring data. This
|
|
24972
24939
|
// only works because all of the places we use
|
|
24973
24940
|
// `restoreSerializedState()` also turn on reviewMode, but is
|
|
24974
24941
|
// fine for now.
|
|
@@ -25014,7 +24981,6 @@ Radio$1.defaultProps = {
|
|
|
25014
24981
|
showSolutions: "none"
|
|
25015
24982
|
};
|
|
25016
24983
|
|
|
25017
|
-
const _excluded$4 = ["noneOfTheAbove"];
|
|
25018
24984
|
const {
|
|
25019
24985
|
shuffle: shuffle$2,
|
|
25020
24986
|
random
|
|
@@ -25101,20 +25067,6 @@ const transform = (widgetOptions, strings, problemNum) => {
|
|
|
25101
25067
|
selectedChoices: _.pluck(choices, "correct")
|
|
25102
25068
|
};
|
|
25103
25069
|
};
|
|
25104
|
-
const propUpgrades$1 = {
|
|
25105
|
-
"1": v0props => {
|
|
25106
|
-
const {
|
|
25107
|
-
noneOfTheAbove
|
|
25108
|
-
} = v0props,
|
|
25109
|
-
rest = _objectWithoutPropertiesLoose(v0props, _excluded$4);
|
|
25110
|
-
if (noneOfTheAbove) {
|
|
25111
|
-
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
25112
|
-
}
|
|
25113
|
-
return _extends({}, rest, {
|
|
25114
|
-
hasNoneOfTheAbove: false
|
|
25115
|
-
});
|
|
25116
|
-
}
|
|
25117
|
-
};
|
|
25118
25070
|
var Radio = {
|
|
25119
25071
|
name: "radio",
|
|
25120
25072
|
displayName: "Radio / Multiple choice",
|
|
@@ -25122,15 +25074,15 @@ var Radio = {
|
|
|
25122
25074
|
widget: Radio$1,
|
|
25123
25075
|
transform: transform,
|
|
25124
25076
|
staticTransform: transform,
|
|
25125
|
-
version:
|
|
25126
|
-
|
|
25127
|
-
minor: 0
|
|
25128
|
-
},
|
|
25129
|
-
propUpgrades: propUpgrades$1,
|
|
25077
|
+
version: radioLogic.version,
|
|
25078
|
+
propUpgrades: radioLogic.widgetOptionsUpgrades,
|
|
25130
25079
|
isLintable: true,
|
|
25131
25080
|
// TODO(LEMS-2656): remove TS suppression
|
|
25132
25081
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25133
|
-
scorer: scoreRadio
|
|
25082
|
+
scorer: scoreRadio,
|
|
25083
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25084
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25085
|
+
validator: validateRadio
|
|
25134
25086
|
};
|
|
25135
25087
|
|
|
25136
25088
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -25149,24 +25101,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
25149
25101
|
};
|
|
25150
25102
|
};
|
|
25151
25103
|
|
|
25152
|
-
/**
|
|
25153
|
-
* For details on the individual options, see the
|
|
25154
|
-
* PerseusCategorizerWidgetOptions type
|
|
25155
|
-
*/
|
|
25156
|
-
|
|
25157
|
-
/**
|
|
25158
|
-
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
25159
|
-
* the public options that should be exposed to the client.
|
|
25160
|
-
*/
|
|
25161
|
-
function getCategorizerPublicWidgetOptions(options) {
|
|
25162
|
-
return {
|
|
25163
|
-
items: options.items,
|
|
25164
|
-
categories: options.categories,
|
|
25165
|
-
randomizeItems: options.randomizeItems,
|
|
25166
|
-
static: options.static
|
|
25167
|
-
};
|
|
25168
|
-
}
|
|
25169
|
-
|
|
25170
25104
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
25171
25105
|
class Categorizer extends React.Component {
|
|
25172
25106
|
constructor(..._args) {
|
|
@@ -25346,6 +25280,9 @@ var Categorizer$1 = {
|
|
|
25346
25280
|
// TODO(LEMS-2656): remove TS suppression
|
|
25347
25281
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25348
25282
|
scorer: scoreCategorizer,
|
|
25283
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25284
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25285
|
+
validator: validateCategorizer,
|
|
25349
25286
|
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
25350
25287
|
};
|
|
25351
25288
|
|
|
@@ -25783,7 +25720,10 @@ var Dropdown$1 = {
|
|
|
25783
25720
|
transform: optionsTransform,
|
|
25784
25721
|
// TODO(LEMS-2656): remove TS suppression
|
|
25785
25722
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25786
|
-
scorer: scoreDropdown
|
|
25723
|
+
scorer: scoreDropdown,
|
|
25724
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25725
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
25726
|
+
validator: validateDropdown
|
|
25787
25727
|
};
|
|
25788
25728
|
|
|
25789
25729
|
const getPromptJSON$j = renderProps => {
|
|
@@ -26974,7 +26914,7 @@ class FunctionGrapher extends React.Component {
|
|
|
26974
26914
|
constraints: [Interactive2.MovablePoint.constraints.bound(), Interactive2.MovablePoint.constraints.snap(), coord => {
|
|
26975
26915
|
// Always enforce that this is a function
|
|
26976
26916
|
const isFunction = _.all(this._coords(), (otherCoord, j) => {
|
|
26977
|
-
return i === j || !otherCoord || !number
|
|
26917
|
+
return i === j || !otherCoord || !number.equal(coord[0], otherCoord[0]);
|
|
26978
26918
|
});
|
|
26979
26919
|
|
|
26980
26920
|
// Evaluate this criteria before per-point
|
|
@@ -27282,11 +27222,22 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27282
27222
|
|
|
27283
27223
|
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27284
27224
|
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27285
|
-
function scoreGroup(userInput,
|
|
27286
|
-
const scores = scoreWidgetsFunctional(
|
|
27225
|
+
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27226
|
+
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27287
27227
|
return flattenScores(scores);
|
|
27288
27228
|
}
|
|
27289
27229
|
|
|
27230
|
+
function validateGroup(userInput, validationData, strings, locale) {
|
|
27231
|
+
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27232
|
+
if (emptyWidgets.length === 0) {
|
|
27233
|
+
return null;
|
|
27234
|
+
}
|
|
27235
|
+
return {
|
|
27236
|
+
type: "invalid",
|
|
27237
|
+
message: null
|
|
27238
|
+
};
|
|
27239
|
+
}
|
|
27240
|
+
|
|
27290
27241
|
// exports has no 'transform'
|
|
27291
27242
|
|
|
27292
27243
|
class Group extends React.Component {
|
|
@@ -27442,8 +27393,11 @@ var Group$1 = {
|
|
|
27442
27393
|
widget: Group,
|
|
27443
27394
|
traverseChildWidgets: traverseChildWidgets,
|
|
27444
27395
|
// TODO(LEMS-2656): remove TS suppression
|
|
27445
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type '
|
|
27396
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27446
27397
|
scorer: scoreGroup,
|
|
27398
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27399
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27400
|
+
validator: validateGroup,
|
|
27447
27401
|
hidden: true,
|
|
27448
27402
|
isLintable: true
|
|
27449
27403
|
};
|
|
@@ -29779,7 +29733,7 @@ const styles$d = StyleSheet.create({
|
|
|
29779
29733
|
}
|
|
29780
29734
|
});
|
|
29781
29735
|
|
|
29782
|
-
const _excluded$
|
|
29736
|
+
const _excluded$1 = ["onFormatChange", "checkValidity", "useArrowKeys"];
|
|
29783
29737
|
const {
|
|
29784
29738
|
firstNumericalParse,
|
|
29785
29739
|
captureScratchpadTouchStart
|
|
@@ -29917,7 +29871,7 @@ class NumberInput extends React.Component {
|
|
|
29917
29871
|
};
|
|
29918
29872
|
}
|
|
29919
29873
|
componentDidUpdate(prevProps) {
|
|
29920
|
-
if (!number
|
|
29874
|
+
if (!number.equal(this.getValue(), this.props.value)) {
|
|
29921
29875
|
this._setValue(this.props.value, this.state.format);
|
|
29922
29876
|
}
|
|
29923
29877
|
}
|
|
@@ -29933,7 +29887,7 @@ class NumberInput extends React.Component {
|
|
|
29933
29887
|
classes = classes + " " + this.props.className;
|
|
29934
29888
|
}
|
|
29935
29889
|
const _this$props = this.props,
|
|
29936
|
-
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$
|
|
29890
|
+
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded$1);
|
|
29937
29891
|
return /*#__PURE__*/React.createElement("input", _extends({}, restProps, {
|
|
29938
29892
|
className: classes,
|
|
29939
29893
|
type: "text",
|
|
@@ -30331,6 +30285,96 @@ function getInterceptStringForLine(line, strings, locale) {
|
|
|
30331
30285
|
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30332
30286
|
});
|
|
30333
30287
|
}
|
|
30288
|
+
function getCoordQuadrant(coord) {
|
|
30289
|
+
const [unroundedX, unroundedY] = coord;
|
|
30290
|
+
const x = Number(unroundedX.toFixed(3));
|
|
30291
|
+
const y = Number(unroundedY.toFixed(3));
|
|
30292
|
+
if (x === 0 && y === 0) {
|
|
30293
|
+
return "origin";
|
|
30294
|
+
}
|
|
30295
|
+
if (y === 0) {
|
|
30296
|
+
return "x-axis";
|
|
30297
|
+
}
|
|
30298
|
+
if (x === 0) {
|
|
30299
|
+
return "y-axis";
|
|
30300
|
+
}
|
|
30301
|
+
if (x > 0 && y > 0) {
|
|
30302
|
+
return 1;
|
|
30303
|
+
}
|
|
30304
|
+
if (x < 0 && y > 0) {
|
|
30305
|
+
return 2;
|
|
30306
|
+
}
|
|
30307
|
+
if (x < 0 && y < 0) {
|
|
30308
|
+
return 3;
|
|
30309
|
+
}
|
|
30310
|
+
return 4;
|
|
30311
|
+
}
|
|
30312
|
+
function getQuadraticVertexString(vertex, strings) {
|
|
30313
|
+
const location = getCoordQuadrant(vertex);
|
|
30314
|
+
switch (location) {
|
|
30315
|
+
case "origin":
|
|
30316
|
+
return strings.srQuadraticGraphVertexOrigin;
|
|
30317
|
+
case "x-axis":
|
|
30318
|
+
return strings.srQuadraticGraphVertexXAxis;
|
|
30319
|
+
case "y-axis":
|
|
30320
|
+
return strings.srQuadraticGraphVertexYAxis;
|
|
30321
|
+
default:
|
|
30322
|
+
return strings.srQuadraticGraphVertexQuadrant({
|
|
30323
|
+
quadrant: location
|
|
30324
|
+
});
|
|
30325
|
+
}
|
|
30326
|
+
}
|
|
30327
|
+
function getQuadraticPointString(pointNumber, coord, strings, locale) {
|
|
30328
|
+
const location = getCoordQuadrant(coord);
|
|
30329
|
+
const [x, y] = coord;
|
|
30330
|
+
switch (location) {
|
|
30331
|
+
case "origin":
|
|
30332
|
+
return strings.srQuadraticPointOrigin({
|
|
30333
|
+
pointNumber: pointNumber
|
|
30334
|
+
});
|
|
30335
|
+
case "x-axis":
|
|
30336
|
+
case "y-axis":
|
|
30337
|
+
return strings.srQuadraticPointAxis({
|
|
30338
|
+
pointNumber: pointNumber,
|
|
30339
|
+
x: srFormatNumber(x, locale),
|
|
30340
|
+
y: srFormatNumber(y, locale)
|
|
30341
|
+
});
|
|
30342
|
+
default:
|
|
30343
|
+
// return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
|
|
30344
|
+
return strings.srQuadraticPointQuadrant({
|
|
30345
|
+
pointNumber: pointNumber,
|
|
30346
|
+
quadrant: location,
|
|
30347
|
+
x: srFormatNumber(x, locale),
|
|
30348
|
+
y: srFormatNumber(y, locale)
|
|
30349
|
+
});
|
|
30350
|
+
}
|
|
30351
|
+
}
|
|
30352
|
+
function getQuadraticXIntercepts(
|
|
30353
|
+
// Coefficients of the quadratic equation
|
|
30354
|
+
a, b, c) {
|
|
30355
|
+
// If a is 0, the equation is a line and has maybe only one x-intercept.
|
|
30356
|
+
if (a === 0) {
|
|
30357
|
+
// If b is 0, the equation a horizontal line and has no x-intercepts.
|
|
30358
|
+
if (b === 0) {
|
|
30359
|
+
return [];
|
|
30360
|
+
}
|
|
30361
|
+
return [-c / b];
|
|
30362
|
+
}
|
|
30363
|
+
|
|
30364
|
+
// Find the x-intercepts by plugging in y = 0 into the quadratic equation.
|
|
30365
|
+
// 0 = ax^2 + bx + c
|
|
30366
|
+
// x = (-b ± sqrt(b^2 - 4ac)) / 2a
|
|
30367
|
+
const discriminant = b * b - 4 * a * c;
|
|
30368
|
+
if (discriminant < 0) {
|
|
30369
|
+
return [];
|
|
30370
|
+
}
|
|
30371
|
+
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
|
|
30372
|
+
const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
|
|
30373
|
+
if (x1 === x2) {
|
|
30374
|
+
return [x1];
|
|
30375
|
+
}
|
|
30376
|
+
return [x1, x2];
|
|
30377
|
+
}
|
|
30334
30378
|
|
|
30335
30379
|
const {
|
|
30336
30380
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
@@ -30748,12 +30792,12 @@ const MafsCssTransformWrapper = ({
|
|
|
30748
30792
|
}
|
|
30749
30793
|
}, children);
|
|
30750
30794
|
|
|
30751
|
-
const _excluded
|
|
30795
|
+
const _excluded = ["children"];
|
|
30752
30796
|
const TextLabel = _ref => {
|
|
30753
30797
|
let {
|
|
30754
30798
|
children
|
|
30755
30799
|
} = _ref,
|
|
30756
|
-
rest = _objectWithoutPropertiesLoose(_ref, _excluded
|
|
30800
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
|
|
30757
30801
|
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
30758
30802
|
size: 16,
|
|
30759
30803
|
svgTextProps: {
|
|
@@ -32135,7 +32179,9 @@ function renderLinearGraph(state, dispatch) {
|
|
|
32135
32179
|
graphState: state,
|
|
32136
32180
|
dispatch: dispatch
|
|
32137
32181
|
}),
|
|
32138
|
-
interactiveElementsDescription:
|
|
32182
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearGraphDescription, {
|
|
32183
|
+
state: state
|
|
32184
|
+
})
|
|
32139
32185
|
};
|
|
32140
32186
|
}
|
|
32141
32187
|
const LinearGraph = (props, key) => {
|
|
@@ -32154,33 +32200,29 @@ const LinearGraph = (props, key) => {
|
|
|
32154
32200
|
const interceptDescriptionId = id + "-intercept";
|
|
32155
32201
|
const slopeDescriptionId = id + "-slope";
|
|
32156
32202
|
|
|
32157
|
-
// Aria
|
|
32158
|
-
const
|
|
32159
|
-
|
|
32160
|
-
|
|
32161
|
-
|
|
32162
|
-
|
|
32163
|
-
|
|
32164
|
-
|
|
32165
|
-
|
|
32166
|
-
|
|
32167
|
-
point2X: srFormatNumber(line[1][0], locale),
|
|
32168
|
-
point2Y: srFormatNumber(line[1][1], locale)
|
|
32203
|
+
// Aria strings
|
|
32204
|
+
const {
|
|
32205
|
+
srLinearGraph,
|
|
32206
|
+
srLinearGraphPoints,
|
|
32207
|
+
srLinearGrabHandle,
|
|
32208
|
+
slopeString,
|
|
32209
|
+
interceptString
|
|
32210
|
+
} = describeLinearGraph(props.graphState, {
|
|
32211
|
+
strings,
|
|
32212
|
+
locale
|
|
32169
32213
|
});
|
|
32170
|
-
const slopeString = getSlopeStringForLine(line, strings);
|
|
32171
|
-
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32172
32214
|
|
|
32173
32215
|
// Linear graphs only have one line
|
|
32174
32216
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
32175
32217
|
// and another action for moving multiple lines
|
|
32176
32218
|
return /*#__PURE__*/React.createElement("g", {
|
|
32177
32219
|
// Outer line minimal description
|
|
32178
|
-
"aria-label":
|
|
32220
|
+
"aria-label": srLinearGraph,
|
|
32179
32221
|
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
32180
32222
|
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
32181
32223
|
key: 0,
|
|
32182
32224
|
ariaLabels: {
|
|
32183
|
-
grabHandleAriaLabel:
|
|
32225
|
+
grabHandleAriaLabel: srLinearGrabHandle
|
|
32184
32226
|
},
|
|
32185
32227
|
ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
|
|
32186
32228
|
points: line,
|
|
@@ -32196,7 +32238,7 @@ const LinearGraph = (props, key) => {
|
|
|
32196
32238
|
}), /*#__PURE__*/React.createElement("g", {
|
|
32197
32239
|
id: pointsDescriptionId,
|
|
32198
32240
|
style: a11y.srOnly
|
|
32199
|
-
},
|
|
32241
|
+
}, srLinearGraphPoints), /*#__PURE__*/React.createElement("g", {
|
|
32200
32242
|
id: interceptDescriptionId,
|
|
32201
32243
|
style: a11y.srOnly
|
|
32202
32244
|
}, interceptString), /*#__PURE__*/React.createElement("g", {
|
|
@@ -32204,6 +32246,55 @@ const LinearGraph = (props, key) => {
|
|
|
32204
32246
|
style: a11y.srOnly
|
|
32205
32247
|
}, slopeString));
|
|
32206
32248
|
};
|
|
32249
|
+
function LinearGraphDescription({
|
|
32250
|
+
state
|
|
32251
|
+
}) {
|
|
32252
|
+
// The reason that LinearGraphDescription is a component (rather than a
|
|
32253
|
+
// function that returns a string) is because it needs to use a
|
|
32254
|
+
// hook: `usePerseusI18n`.
|
|
32255
|
+
const i18n = usePerseusI18n();
|
|
32256
|
+
const strings = describeLinearGraph(state, i18n);
|
|
32257
|
+
return strings.srLinearInteractiveElement;
|
|
32258
|
+
}
|
|
32259
|
+
|
|
32260
|
+
// Exported for testing
|
|
32261
|
+
function describeLinearGraph(state, i18n) {
|
|
32262
|
+
const {
|
|
32263
|
+
coords: line
|
|
32264
|
+
} = state;
|
|
32265
|
+
const {
|
|
32266
|
+
strings,
|
|
32267
|
+
locale
|
|
32268
|
+
} = i18n;
|
|
32269
|
+
|
|
32270
|
+
// Aria label strings
|
|
32271
|
+
const srLinearGraph = strings.srLinearGraph;
|
|
32272
|
+
const srLinearGraphPoints = strings.srLinearGraphPoints({
|
|
32273
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32274
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32275
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32276
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32277
|
+
});
|
|
32278
|
+
const srLinearGrabHandle = strings.srLinearGrabHandle({
|
|
32279
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32280
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32281
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32282
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32283
|
+
});
|
|
32284
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32285
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32286
|
+
const srLinearInteractiveElement = strings.srInteractiveElements({
|
|
32287
|
+
elements: [srLinearGraph, srLinearGraphPoints].join(" ")
|
|
32288
|
+
});
|
|
32289
|
+
return {
|
|
32290
|
+
srLinearGraph,
|
|
32291
|
+
srLinearGraphPoints,
|
|
32292
|
+
srLinearGrabHandle,
|
|
32293
|
+
slopeString,
|
|
32294
|
+
interceptString,
|
|
32295
|
+
srLinearInteractiveElement
|
|
32296
|
+
};
|
|
32297
|
+
}
|
|
32207
32298
|
|
|
32208
32299
|
function renderLinearSystemGraph(state, dispatch) {
|
|
32209
32300
|
return {
|
|
@@ -32752,13 +32843,13 @@ const hasFocusVisible = element => {
|
|
|
32752
32843
|
}
|
|
32753
32844
|
};
|
|
32754
32845
|
|
|
32755
|
-
function renderQuadraticGraph(state, dispatch) {
|
|
32846
|
+
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
32756
32847
|
return {
|
|
32757
32848
|
graph: /*#__PURE__*/React.createElement(QuadraticGraph, {
|
|
32758
32849
|
graphState: state,
|
|
32759
32850
|
dispatch: dispatch
|
|
32760
32851
|
}),
|
|
32761
|
-
interactiveElementsDescription:
|
|
32852
|
+
interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
|
|
32762
32853
|
};
|
|
32763
32854
|
}
|
|
32764
32855
|
function QuadraticGraph(props) {
|
|
@@ -32769,6 +32860,14 @@ function QuadraticGraph(props) {
|
|
|
32769
32860
|
const {
|
|
32770
32861
|
coords
|
|
32771
32862
|
} = graphState;
|
|
32863
|
+
const {
|
|
32864
|
+
strings,
|
|
32865
|
+
locale
|
|
32866
|
+
} = usePerseusI18n();
|
|
32867
|
+
const id = React.useId();
|
|
32868
|
+
const quadraticDirectionId = id + "-direction";
|
|
32869
|
+
const quadraticVertexId = id + "-vertex";
|
|
32870
|
+
const quadraticInterceptsId = id + "-intercepts";
|
|
32772
32871
|
|
|
32773
32872
|
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
32774
32873
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
@@ -32784,15 +32883,46 @@ function QuadraticGraph(props) {
|
|
|
32784
32883
|
|
|
32785
32884
|
// Calculate the y value based on the current x value
|
|
32786
32885
|
const y = x => (a * x + b) * x + c;
|
|
32787
|
-
|
|
32886
|
+
|
|
32887
|
+
// Aria strings
|
|
32888
|
+
const {
|
|
32889
|
+
srQuadraticGraph,
|
|
32890
|
+
srQuadraticDirection,
|
|
32891
|
+
srQuadraticVertex,
|
|
32892
|
+
srQuadraticXIntercepts,
|
|
32893
|
+
srQuadraticYIntercept
|
|
32894
|
+
} = describeQuadraticGraph(graphState, {
|
|
32895
|
+
strings,
|
|
32896
|
+
locale
|
|
32897
|
+
});
|
|
32898
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32899
|
+
// Outer graph minimal description
|
|
32900
|
+
"aria-label": srQuadraticGraph,
|
|
32901
|
+
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
32902
|
+
}, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
32788
32903
|
y: y,
|
|
32789
32904
|
color: color.blue
|
|
32790
|
-
}), coords.map((coord, i) =>
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32905
|
+
}), coords.map((coord, i) => {
|
|
32906
|
+
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
32907
|
+
// Vertex might not exist if the quadratic graph is a line
|
|
32908
|
+
const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
|
|
32909
|
+
return /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
32910
|
+
key: "point-" + i,
|
|
32911
|
+
ariaLabel: `${srQuadraticPoint}${srVertex}`,
|
|
32912
|
+
point: coord,
|
|
32913
|
+
sequenceNumber: i + 1,
|
|
32914
|
+
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
32915
|
+
});
|
|
32916
|
+
}), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
|
|
32917
|
+
id: quadraticDirectionId,
|
|
32918
|
+
style: a11y.srOnly
|
|
32919
|
+
}, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React.createElement("g", {
|
|
32920
|
+
id: quadraticVertexId,
|
|
32921
|
+
style: a11y.srOnly
|
|
32922
|
+
}, srQuadraticVertex), /*#__PURE__*/React.createElement("g", {
|
|
32923
|
+
id: quadraticInterceptsId,
|
|
32924
|
+
style: a11y.srOnly
|
|
32925
|
+
}, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
|
|
32796
32926
|
}
|
|
32797
32927
|
|
|
32798
32928
|
// Get the quadratic coefficients from the 3 control points
|
|
@@ -32815,6 +32945,60 @@ const getQuadraticCoefficients$2 = coords => {
|
|
|
32815
32945
|
const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
|
|
32816
32946
|
return [a, b, c];
|
|
32817
32947
|
};
|
|
32948
|
+
function getQuadraticGraphDescription(state, i18n) {
|
|
32949
|
+
const strings = describeQuadraticGraph(state, i18n);
|
|
32950
|
+
return strings.srQuadraticInteractiveElements;
|
|
32951
|
+
}
|
|
32952
|
+
// Exported for testing
|
|
32953
|
+
function describeQuadraticGraph(state, i18n) {
|
|
32954
|
+
const {
|
|
32955
|
+
strings,
|
|
32956
|
+
locale
|
|
32957
|
+
} = i18n;
|
|
32958
|
+
const coeffs = getQuadraticCoefficients$2(state.coords);
|
|
32959
|
+
const [a, b, c] = coeffs != null ? coeffs : [0, 0, 0];
|
|
32960
|
+
const vertex = [-b / (2 * a), c - b * b / (4 * a)];
|
|
32961
|
+
const xIntercepts = getQuadraticXIntercepts(a, b, c);
|
|
32962
|
+
|
|
32963
|
+
// Aria label strings
|
|
32964
|
+
const srQuadraticGraph = strings.srQuadraticGraph;
|
|
32965
|
+
const srQuadraticFaceUp = strings.srQuadraticFaceUp;
|
|
32966
|
+
const srQuadraticFaceDown = strings.srQuadraticFaceDown;
|
|
32967
|
+
// The graph only has a direction if it is not a line.
|
|
32968
|
+
const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
|
|
32969
|
+
// Only describe vertex if the quadratic graph is not a line.
|
|
32970
|
+
// (Undefined means the quadratic graph is a line and has no vertex.)
|
|
32971
|
+
const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
|
|
32972
|
+
// Undefined means the quadratic graph has no x-intercepts,
|
|
32973
|
+
// such as when the graph is a horizontal line.
|
|
32974
|
+
const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
|
|
32975
|
+
intercept1: srFormatNumber(xIntercepts[0], locale),
|
|
32976
|
+
intercept2: srFormatNumber(xIntercepts[1], locale)
|
|
32977
|
+
}) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
|
|
32978
|
+
intercept: srFormatNumber(xIntercepts[0], locale)
|
|
32979
|
+
}) : undefined;
|
|
32980
|
+
const srQuadraticYIntercept = strings.srQuadraticYIntercept({
|
|
32981
|
+
intercept: srFormatNumber(c, locale)
|
|
32982
|
+
});
|
|
32983
|
+
const srQuadraticInteractiveElements = strings.srInteractiveElements({
|
|
32984
|
+
elements: strings.srQuadraticInteractiveElements({
|
|
32985
|
+
point1X: srFormatNumber(state.coords[0][0], locale),
|
|
32986
|
+
point1Y: srFormatNumber(state.coords[0][1], locale),
|
|
32987
|
+
point2X: srFormatNumber(state.coords[1][0], locale),
|
|
32988
|
+
point2Y: srFormatNumber(state.coords[1][1], locale),
|
|
32989
|
+
point3X: srFormatNumber(state.coords[2][0], locale),
|
|
32990
|
+
point3Y: srFormatNumber(state.coords[2][1], locale)
|
|
32991
|
+
})
|
|
32992
|
+
});
|
|
32993
|
+
return {
|
|
32994
|
+
srQuadraticGraph,
|
|
32995
|
+
srQuadraticDirection,
|
|
32996
|
+
srQuadraticVertex,
|
|
32997
|
+
srQuadraticXIntercepts,
|
|
32998
|
+
srQuadraticYIntercept,
|
|
32999
|
+
srQuadraticInteractiveElements
|
|
33000
|
+
};
|
|
33001
|
+
}
|
|
32818
33002
|
|
|
32819
33003
|
function renderRayGraph(state, dispatch) {
|
|
32820
33004
|
return {
|
|
@@ -32940,17 +33124,69 @@ function renderSegmentGraph(state, dispatch) {
|
|
|
32940
33124
|
graphState: state,
|
|
32941
33125
|
dispatch: dispatch
|
|
32942
33126
|
}),
|
|
32943
|
-
interactiveElementsDescription:
|
|
33127
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(SegmentGraphDescription, {
|
|
33128
|
+
state: state
|
|
33129
|
+
})
|
|
32944
33130
|
};
|
|
32945
33131
|
}
|
|
32946
|
-
const SegmentGraph =
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
33132
|
+
const SegmentGraph = ({
|
|
33133
|
+
dispatch,
|
|
33134
|
+
graphState
|
|
33135
|
+
}) => {
|
|
32950
33136
|
const {
|
|
32951
33137
|
coords: segments
|
|
32952
|
-
} =
|
|
32953
|
-
|
|
33138
|
+
} = graphState;
|
|
33139
|
+
const {
|
|
33140
|
+
strings,
|
|
33141
|
+
locale
|
|
33142
|
+
} = usePerseusI18n();
|
|
33143
|
+
const segmentUniqueId = React.useId();
|
|
33144
|
+
const lengthDescriptionId = segmentUniqueId + "-length";
|
|
33145
|
+
const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
|
|
33146
|
+
function getWholeSegmentGraphAriaLabel() {
|
|
33147
|
+
return (segments == null ? void 0 : segments.length) > 1 ? strings.srMultipleSegmentGraphAriaLabel({
|
|
33148
|
+
countOfSegments: segments.length
|
|
33149
|
+
}) : strings.srSingleSegmentGraphAriaLabel;
|
|
33150
|
+
}
|
|
33151
|
+
const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
|
|
33152
|
+
function getIndividualSegmentAriaLabel(segment, index) {
|
|
33153
|
+
if (segments.length === 1) {
|
|
33154
|
+
return strings.srSingleSegmentLabel({
|
|
33155
|
+
point1X: srFormatNumber(segments[0][0][X], locale),
|
|
33156
|
+
point1Y: srFormatNumber(segments[0][0][Y], locale),
|
|
33157
|
+
point2X: srFormatNumber(segments[0][1][X], locale),
|
|
33158
|
+
point2Y: srFormatNumber(segments[0][1][Y], locale)
|
|
33159
|
+
});
|
|
33160
|
+
}
|
|
33161
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
33162
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33163
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33164
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33165
|
+
point2Y: srFormatNumber(segment[1][Y], locale),
|
|
33166
|
+
indexOfSegment: index + 1
|
|
33167
|
+
});
|
|
33168
|
+
}
|
|
33169
|
+
function getWholeSegmentGraphAriaDescription() {
|
|
33170
|
+
return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
|
|
33171
|
+
}
|
|
33172
|
+
function formatSegment(endpointNumber, x, y, index) {
|
|
33173
|
+
const segObj = {
|
|
33174
|
+
endpointNumber: endpointNumber,
|
|
33175
|
+
x: srFormatNumber(x, locale),
|
|
33176
|
+
y: srFormatNumber(y, locale)
|
|
33177
|
+
};
|
|
33178
|
+
return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel(_extends({}, segObj, {
|
|
33179
|
+
indexOfSegment: index
|
|
33180
|
+
})) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
|
|
33181
|
+
}
|
|
33182
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
33183
|
+
"aria-label": wholeSegmentGraphAriaLabel,
|
|
33184
|
+
"aria-describedby": wholeGraphDescriptionId
|
|
33185
|
+
}, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement("g", {
|
|
33186
|
+
"aria-label": getIndividualSegmentAriaLabel(segment, i),
|
|
33187
|
+
"aria-describedby": lengthDescriptionId,
|
|
33188
|
+
key: `${segmentUniqueId}-${i}`
|
|
33189
|
+
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
32954
33190
|
key: i,
|
|
32955
33191
|
points: segment,
|
|
32956
33192
|
onMoveLine: delta => {
|
|
@@ -32958,9 +33194,57 @@ const SegmentGraph = props => {
|
|
|
32958
33194
|
},
|
|
32959
33195
|
onMovePoint: (endpointIndex, destination) => {
|
|
32960
33196
|
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
33197
|
+
},
|
|
33198
|
+
ariaLabels: {
|
|
33199
|
+
point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
|
|
33200
|
+
point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
|
|
33201
|
+
grabHandleAriaLabel: strings.srSegmentGrabHandle({
|
|
33202
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33203
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33204
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33205
|
+
point2Y: srFormatNumber(segment[1][Y], locale)
|
|
33206
|
+
})
|
|
32961
33207
|
}
|
|
32962
|
-
})
|
|
33208
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
33209
|
+
id: lengthDescriptionId,
|
|
33210
|
+
style: a11y.srOnly
|
|
33211
|
+
}, strings.srSegmentLength({
|
|
33212
|
+
length: srFormatNumber(getLengthOfSegment(segment), locale)
|
|
33213
|
+
})))), /*#__PURE__*/React.createElement("g", {
|
|
33214
|
+
style: a11y.srOnly,
|
|
33215
|
+
id: wholeGraphDescriptionId
|
|
33216
|
+
}, getWholeSegmentGraphAriaDescription()));
|
|
32963
33217
|
};
|
|
33218
|
+
function getLengthOfSegment(segment) {
|
|
33219
|
+
return point.distanceToPoint(...segment);
|
|
33220
|
+
}
|
|
33221
|
+
function SegmentGraphDescription({
|
|
33222
|
+
state
|
|
33223
|
+
}) {
|
|
33224
|
+
// The reason that SegmentGraphDescription is a component (rather than a
|
|
33225
|
+
// function that returns a string) is because it needs to use a
|
|
33226
|
+
// hook: `usePerseusI18n`.
|
|
33227
|
+
const i18n = usePerseusI18n();
|
|
33228
|
+
return describeSegmentGraph(state, i18n);
|
|
33229
|
+
}
|
|
33230
|
+
|
|
33231
|
+
// Exported for testing
|
|
33232
|
+
function describeSegmentGraph(state, i18n) {
|
|
33233
|
+
const {
|
|
33234
|
+
strings,
|
|
33235
|
+
locale
|
|
33236
|
+
} = i18n;
|
|
33237
|
+
const segmentDescriptions = state.coords.map(([point1, point2], index) => strings.srMultipleSegmentIndividualLabel({
|
|
33238
|
+
point1X: srFormatNumber(point1[X], locale),
|
|
33239
|
+
point1Y: srFormatNumber(point1[Y], locale),
|
|
33240
|
+
point2X: srFormatNumber(point2[X], locale),
|
|
33241
|
+
point2Y: srFormatNumber(point2[Y], locale),
|
|
33242
|
+
indexOfSegment: index + 1
|
|
33243
|
+
}));
|
|
33244
|
+
return strings.srInteractiveElements({
|
|
33245
|
+
elements: segmentDescriptions.join(" ")
|
|
33246
|
+
});
|
|
33247
|
+
}
|
|
32964
33248
|
|
|
32965
33249
|
function renderSinusoidGraph(state, dispatch) {
|
|
32966
33250
|
return {
|
|
@@ -33236,9 +33520,10 @@ const MafsGraph = props => {
|
|
|
33236
33520
|
x: viewboxX,
|
|
33237
33521
|
y: viewboxY
|
|
33238
33522
|
};
|
|
33523
|
+
const i18n = usePerseusI18n();
|
|
33239
33524
|
const {
|
|
33240
33525
|
strings
|
|
33241
|
-
} =
|
|
33526
|
+
} = i18n;
|
|
33242
33527
|
const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
|
|
33243
33528
|
useOnMountEffect(() => {
|
|
33244
33529
|
analytics.onAnalyticsEvent({
|
|
@@ -33255,7 +33540,8 @@ const MafsGraph = props => {
|
|
|
33255
33540
|
interactiveElementsDescription
|
|
33256
33541
|
} = renderGraphElements({
|
|
33257
33542
|
state,
|
|
33258
|
-
dispatch
|
|
33543
|
+
dispatch,
|
|
33544
|
+
i18n
|
|
33259
33545
|
});
|
|
33260
33546
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
33261
33547
|
value: {
|
|
@@ -33633,7 +33919,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
33633
33919
|
const renderGraphElements = props => {
|
|
33634
33920
|
const {
|
|
33635
33921
|
state,
|
|
33636
|
-
dispatch
|
|
33922
|
+
dispatch,
|
|
33923
|
+
i18n
|
|
33637
33924
|
} = props;
|
|
33638
33925
|
const {
|
|
33639
33926
|
type
|
|
@@ -33656,7 +33943,7 @@ const renderGraphElements = props => {
|
|
|
33656
33943
|
case "circle":
|
|
33657
33944
|
return renderCircleGraph(state, dispatch);
|
|
33658
33945
|
case "quadratic":
|
|
33659
|
-
return renderQuadraticGraph(state, dispatch);
|
|
33946
|
+
return renderQuadraticGraph(state, dispatch, i18n);
|
|
33660
33947
|
case "sinusoid":
|
|
33661
33948
|
return renderSinusoidGraph(state, dispatch);
|
|
33662
33949
|
case "none":
|
|
@@ -35188,7 +35475,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
35188
35475
|
};
|
|
35189
35476
|
this.setTrashCanVisibility = opacity => {
|
|
35190
35477
|
const graphie = this.graphie;
|
|
35191
|
-
if (number
|
|
35478
|
+
if (number.equal(opacity, 0)) {
|
|
35192
35479
|
if (this.trashCan) {
|
|
35193
35480
|
this.trashCan.remove();
|
|
35194
35481
|
this.trashCan = null;
|
|
@@ -37238,19 +37525,21 @@ class LabelImage extends React.Component {
|
|
|
37238
37525
|
this._mounted = false;
|
|
37239
37526
|
}
|
|
37240
37527
|
getUserInput() {
|
|
37241
|
-
const {
|
|
37242
|
-
markers
|
|
37243
|
-
} = this.props;
|
|
37244
37528
|
return {
|
|
37245
|
-
markers
|
|
37529
|
+
markers: this.props.markers.map(marker => ({
|
|
37530
|
+
selected: marker.selected,
|
|
37531
|
+
label: marker.label
|
|
37532
|
+
}))
|
|
37246
37533
|
};
|
|
37247
37534
|
}
|
|
37248
37535
|
getPromptJSON() {
|
|
37249
37536
|
return getPromptJSON$a(this.props, this.getUserInput());
|
|
37250
37537
|
}
|
|
37251
37538
|
|
|
37252
|
-
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37253
|
-
|
|
37539
|
+
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37540
|
+
// Also consider how scoreMarker is being called as it seems to require the marker.answers property.
|
|
37541
|
+
// Removed rubric parameter, but it gets a full widget options object from the renderer
|
|
37542
|
+
showRationalesForCurrentlySelectedChoices() {
|
|
37254
37543
|
const {
|
|
37255
37544
|
markers
|
|
37256
37545
|
} = this.props;
|
|
@@ -37258,7 +37547,7 @@ class LabelImage extends React.Component {
|
|
|
37258
37547
|
onChange
|
|
37259
37548
|
} = this.props;
|
|
37260
37549
|
const updatedMarkers = markers.map(marker => {
|
|
37261
|
-
const score = scoreLabelImageMarker(marker);
|
|
37550
|
+
const score = scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37262
37551
|
return _extends({}, marker, {
|
|
37263
37552
|
// Reveal correctness state for markers with answers.
|
|
37264
37553
|
showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
|
|
@@ -37364,6 +37653,7 @@ class LabelImage extends React.Component {
|
|
|
37364
37653
|
selected: selected.length ? selected : undefined
|
|
37365
37654
|
}));
|
|
37366
37655
|
}
|
|
37656
|
+
// TODO(LEMS-2723): Investigate if possible to change this to not require answers
|
|
37367
37657
|
renderMarkers() {
|
|
37368
37658
|
const {
|
|
37369
37659
|
markers,
|
|
@@ -37405,7 +37695,7 @@ class LabelImage extends React.Component {
|
|
|
37405
37695
|
bottom: "top"
|
|
37406
37696
|
}[markerPosition];
|
|
37407
37697
|
}
|
|
37408
|
-
const score = scoreLabelImageMarker(marker);
|
|
37698
|
+
const score = scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37409
37699
|
// Once the question is answered, show markers
|
|
37410
37700
|
// with correct answers, otherwise passthrough
|
|
37411
37701
|
// the correctness state.
|
|
@@ -38950,14 +39240,16 @@ var Matrix$1 = {
|
|
|
38950
39240
|
isLintable: true,
|
|
38951
39241
|
// TODO(LEMS-2656): remove TS suppression
|
|
38952
39242
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
38953
|
-
scorer: scoreMatrix
|
|
39243
|
+
scorer: scoreMatrix,
|
|
39244
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39245
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39246
|
+
validator: validateMatrix
|
|
38954
39247
|
};
|
|
38955
39248
|
|
|
38956
39249
|
const getPromptJSON$7 = () => {
|
|
38957
39250
|
return getUnsupportedPromptJSON("measurer");
|
|
38958
39251
|
};
|
|
38959
39252
|
|
|
38960
|
-
const _excluded$1 = ["imageUrl", "imageTop", "imageLeft"];
|
|
38961
39253
|
const defaultImage = {
|
|
38962
39254
|
url: null,
|
|
38963
39255
|
top: 0,
|
|
@@ -39062,33 +39354,13 @@ Measurer.defaultProps = {
|
|
|
39062
39354
|
rulerPixels: 40,
|
|
39063
39355
|
rulerLength: 10
|
|
39064
39356
|
};
|
|
39065
|
-
const propUpgrades = {
|
|
39066
|
-
"1": v0props => {
|
|
39067
|
-
const {
|
|
39068
|
-
imageUrl,
|
|
39069
|
-
imageTop,
|
|
39070
|
-
imageLeft
|
|
39071
|
-
} = v0props,
|
|
39072
|
-
rest = _objectWithoutPropertiesLoose(v0props, _excluded$1);
|
|
39073
|
-
return _extends({}, rest, {
|
|
39074
|
-
image: {
|
|
39075
|
-
url: imageUrl,
|
|
39076
|
-
top: imageTop,
|
|
39077
|
-
left: imageLeft
|
|
39078
|
-
}
|
|
39079
|
-
});
|
|
39080
|
-
}
|
|
39081
|
-
};
|
|
39082
39357
|
var Measurer$1 = {
|
|
39083
39358
|
name: "measurer",
|
|
39084
39359
|
displayName: "Measurer",
|
|
39085
39360
|
hidden: true,
|
|
39086
39361
|
widget: Measurer,
|
|
39087
|
-
version:
|
|
39088
|
-
|
|
39089
|
-
minor: 0
|
|
39090
|
-
},
|
|
39091
|
-
propUpgrades: propUpgrades,
|
|
39362
|
+
version: measurerLogic.version,
|
|
39363
|
+
propUpgrades: measurerLogic.widgetOptionsUpgrades,
|
|
39092
39364
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39093
39365
|
scorer: () => scoreNoop(1)
|
|
39094
39366
|
};
|
|
@@ -39661,10 +39933,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
39661
39933
|
const firstChar = smiles[0];
|
|
39662
39934
|
const rest = smiles.slice(1);
|
|
39663
39935
|
if (firstChar === "=") {
|
|
39664
|
-
return parse$
|
|
39936
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
39665
39937
|
}
|
|
39666
39938
|
if (firstChar === "#") {
|
|
39667
|
-
return parse$
|
|
39939
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
39668
39940
|
}
|
|
39669
39941
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
39670
39942
|
}
|
|
@@ -39723,8 +39995,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
39723
39995
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
39724
39996
|
parens: newCtx.parens.concat("(")
|
|
39725
39997
|
});
|
|
39726
|
-
const parenExpr = parse$
|
|
39727
|
-
const remainder = parse$
|
|
39998
|
+
const parenExpr = parse$1(rest, parenCtx);
|
|
39999
|
+
const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
39728
40000
|
return [parenExpr].concat(remainder);
|
|
39729
40001
|
}
|
|
39730
40002
|
if (firstChar === ")") {
|
|
@@ -39788,7 +40060,7 @@ function parseAtom(smiles, ctx) {
|
|
|
39788
40060
|
|
|
39789
40061
|
// increment the atom counter and reset the branch counter
|
|
39790
40062
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
39791
|
-
let restOfMolecule = parse$
|
|
40063
|
+
let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
39792
40064
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
39793
40065
|
//TODO(colin): fix this awkwardness.
|
|
39794
40066
|
restOfMolecule = [restOfMolecule];
|
|
@@ -39827,7 +40099,7 @@ function isModifierChar(s) {
|
|
|
39827
40099
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
39828
40100
|
* yet implemented.
|
|
39829
40101
|
*/
|
|
39830
|
-
function parse$
|
|
40102
|
+
function parse$1(smiles, ctx) {
|
|
39831
40103
|
if (!validate(smiles)) {
|
|
39832
40104
|
throw new ParseError$1("Invalid input.");
|
|
39833
40105
|
}
|
|
@@ -39852,7 +40124,7 @@ function parse$2(smiles, ctx) {
|
|
|
39852
40124
|
return parseParenthesizedExpression(smiles, ctx);
|
|
39853
40125
|
}
|
|
39854
40126
|
var SmilesParser = {
|
|
39855
|
-
parse: parse$
|
|
40127
|
+
parse: parse$1,
|
|
39856
40128
|
ParseError: ParseError$1
|
|
39857
40129
|
};
|
|
39858
40130
|
|
|
@@ -39860,7 +40132,7 @@ var SmilesParser = {
|
|
|
39860
40132
|
const {
|
|
39861
40133
|
layout
|
|
39862
40134
|
} = MoleculeLayout;
|
|
39863
|
-
const parse
|
|
40135
|
+
const parse = SmilesParser.parse;
|
|
39864
40136
|
const ParseError = SmilesParser.ParseError;
|
|
39865
40137
|
const borderSize = 30;
|
|
39866
40138
|
class Molecule extends React.Component {
|
|
@@ -39874,7 +40146,7 @@ class Molecule extends React.Component {
|
|
|
39874
40146
|
try {
|
|
39875
40147
|
this.setState({
|
|
39876
40148
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
39877
|
-
parsedSmiles: parse
|
|
40149
|
+
parsedSmiles: parse(smiles),
|
|
39878
40150
|
error: null
|
|
39879
40151
|
});
|
|
39880
40152
|
} catch (e) {
|
|
@@ -40093,7 +40365,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
40093
40365
|
const x = range[0] + i * tickStep;
|
|
40094
40366
|
fractions.push(x);
|
|
40095
40367
|
}
|
|
40096
|
-
const getDenom = x => number
|
|
40368
|
+
const getDenom = x => number.toFraction(x)[1];
|
|
40097
40369
|
const denoms = _.map(fractions, getDenom);
|
|
40098
40370
|
base = _.reduce(denoms, (x, y) => KhanMath.getLCM(x, y));
|
|
40099
40371
|
} else {
|
|
@@ -40156,7 +40428,7 @@ class NumberLine extends React.Component {
|
|
|
40156
40428
|
let initialX = this.props.numLinePosition;
|
|
40157
40429
|
const divisionRange = this.props.divisionRange;
|
|
40158
40430
|
initialX = initialX == null ? range[0] : initialX;
|
|
40159
|
-
return range[0] < range[1] && number
|
|
40431
|
+
return range[0] < range[1] && number.sign(initialX - range[0]) >= 0 && number.sign(initialX - range[1]) <= 0 && divisionRange[0] < divisionRange[1] && 0 < this.props.numDivisions && 0 < this.props.snapDivisions;
|
|
40160
40432
|
};
|
|
40161
40433
|
this.onNumDivisionsChange = (numDivisions, cb) => {
|
|
40162
40434
|
const divRange = this.props.divisionRange.slice();
|
|
@@ -40259,7 +40531,7 @@ class NumberLine extends React.Component {
|
|
|
40259
40531
|
const right = props.range[1];
|
|
40260
40532
|
const snapX = props.tickStep / props.snapDivisions;
|
|
40261
40533
|
let x = bound(numLinePosition, left, right);
|
|
40262
|
-
x = left + number
|
|
40534
|
+
x = left + number.roundTo(x - left, snapX);
|
|
40263
40535
|
assert$1(_.isFinite(x));
|
|
40264
40536
|
return x;
|
|
40265
40537
|
};
|
|
@@ -40550,7 +40822,10 @@ var NumberLine$1 = {
|
|
|
40550
40822
|
staticTransform: staticTransform$1,
|
|
40551
40823
|
// TODO(LEMS-2656): remove TS suppression
|
|
40552
40824
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40553
|
-
scorer: scoreNumberLine
|
|
40825
|
+
scorer: scoreNumberLine,
|
|
40826
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
40827
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40828
|
+
validator: validateNumberLine
|
|
40554
40829
|
};
|
|
40555
40830
|
|
|
40556
40831
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -41113,7 +41388,11 @@ var Orderer$1 = {
|
|
|
41113
41388
|
isLintable: true,
|
|
41114
41389
|
// TODO(LEMS-2656): remove TS suppression
|
|
41115
41390
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41116
|
-
scorer: scoreOrderer
|
|
41391
|
+
scorer: scoreOrderer,
|
|
41392
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41393
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41394
|
+
validator: validateOrderer,
|
|
41395
|
+
getPublicWidgetOptions: getOrdererPublicWidgetOptions
|
|
41117
41396
|
};
|
|
41118
41397
|
|
|
41119
41398
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -41157,10 +41436,6 @@ var PassageRefTarget$1 = {
|
|
|
41157
41436
|
transform: editorProps => {
|
|
41158
41437
|
return _.pick(editorProps, "content");
|
|
41159
41438
|
},
|
|
41160
|
-
version: {
|
|
41161
|
-
major: 0,
|
|
41162
|
-
minor: 0
|
|
41163
|
-
},
|
|
41164
41439
|
isLintable: true,
|
|
41165
41440
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41166
41441
|
scorer: () => scoreNoop()
|
|
@@ -42183,7 +42458,10 @@ var Plotter$1 = {
|
|
|
42183
42458
|
staticTransform: staticTransform,
|
|
42184
42459
|
// TODO(LEMS-2656): remove TS suppression
|
|
42185
42460
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42186
|
-
scorer: scorePlotter
|
|
42461
|
+
scorer: scorePlotter,
|
|
42462
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42463
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42464
|
+
validator: validatePlotter
|
|
42187
42465
|
};
|
|
42188
42466
|
|
|
42189
42467
|
const getPromptJSON$2 = () => {
|
|
@@ -42348,7 +42626,10 @@ var Sorter$1 = {
|
|
|
42348
42626
|
isLintable: true,
|
|
42349
42627
|
// TODO(LEMS-2656): remove TS suppression
|
|
42350
42628
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42351
|
-
scorer: scoreSorter
|
|
42629
|
+
scorer: scoreSorter,
|
|
42630
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42631
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42632
|
+
validator: validateSorter
|
|
42352
42633
|
};
|
|
42353
42634
|
|
|
42354
42635
|
const {
|
|
@@ -42570,7 +42851,10 @@ var Table$1 = {
|
|
|
42570
42851
|
isLintable: true,
|
|
42571
42852
|
// TODO(LEMS-2656): remove TS suppression
|
|
42572
42853
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42573
|
-
scorer: scoreTable
|
|
42854
|
+
scorer: scoreTable,
|
|
42855
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42856
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
42857
|
+
validator: validateTable
|
|
42574
42858
|
};
|
|
42575
42859
|
|
|
42576
42860
|
const getPromptJSON = () => {
|
|
@@ -42753,7 +43037,7 @@ const init = function init() {
|
|
|
42753
43037
|
|
|
42754
43038
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
42755
43039
|
const libName = "@khanacademy/perseus";
|
|
42756
|
-
const libVersion = "
|
|
43040
|
+
const libVersion = "52.0.0";
|
|
42757
43041
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
42758
43042
|
|
|
42759
43043
|
const apiVersion = {
|
|
@@ -44191,6 +44475,71 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44191
44475
|
replaceDeprecatedWidgets();
|
|
44192
44476
|
};
|
|
44193
44477
|
|
|
44478
|
+
/**
|
|
44479
|
+
* Get a widget type by a widget's ID
|
|
44480
|
+
* (please don't derive type from ID)
|
|
44481
|
+
*
|
|
44482
|
+
* @param {string} widgetId the ID of the widget
|
|
44483
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44484
|
+
* @returns {string} the widget type (ie "radio")
|
|
44485
|
+
*/
|
|
44486
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44487
|
+
var _widget$type;
|
|
44488
|
+
const widget = widgetMap[WidgetId];
|
|
44489
|
+
return (_widget$type = widget == null ? void 0 : widget.type) != null ? _widget$type : null;
|
|
44490
|
+
}
|
|
44491
|
+
|
|
44492
|
+
/**
|
|
44493
|
+
* Does the content have a specific type of widget?
|
|
44494
|
+
*
|
|
44495
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44496
|
+
* @param {string} content the string to search through
|
|
44497
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44498
|
+
* @returns {boolean} if the content includes the widget type
|
|
44499
|
+
*/
|
|
44500
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44501
|
+
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44502
|
+
}
|
|
44503
|
+
|
|
44504
|
+
/**
|
|
44505
|
+
* Pull the widget map out of ItemData
|
|
44506
|
+
*
|
|
44507
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44508
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44509
|
+
*/
|
|
44510
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
44511
|
+
return itemData.question.widgets;
|
|
44512
|
+
}
|
|
44513
|
+
|
|
44514
|
+
/**
|
|
44515
|
+
* Get the widget information from a WidgetMap
|
|
44516
|
+
*
|
|
44517
|
+
* @param {string} widgetId the ID of the widget
|
|
44518
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
44519
|
+
*/
|
|
44520
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
44521
|
+
var _widgetMap$widgetId;
|
|
44522
|
+
return (_widgetMap$widgetId = widgetMap[widgetId]) != null ? _widgetMap$widgetId : null;
|
|
44523
|
+
}
|
|
44524
|
+
|
|
44525
|
+
/**
|
|
44526
|
+
* Get select widgets from a widget map.
|
|
44527
|
+
*
|
|
44528
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
44529
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
44530
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
44531
|
+
*/
|
|
44532
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
44533
|
+
const widgets = {};
|
|
44534
|
+
widgetIds.forEach(widgetId => {
|
|
44535
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
44536
|
+
if (widget) {
|
|
44537
|
+
widgets[widgetId] = widget;
|
|
44538
|
+
}
|
|
44539
|
+
});
|
|
44540
|
+
return widgets;
|
|
44541
|
+
}
|
|
44542
|
+
|
|
44194
44543
|
/**
|
|
44195
44544
|
* This function extracts the answers from the widgets.
|
|
44196
44545
|
*
|
|
@@ -44714,1670 +45063,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
44714
45063
|
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
44715
45064
|
};
|
|
44716
45065
|
|
|
44717
|
-
function isRealJSONParse(jsonParse) {
|
|
44718
|
-
const randomPhrase = buildRandomPhrase();
|
|
44719
|
-
const randomHintPhrase = buildRandomPhrase();
|
|
44720
|
-
const randomString = buildRandomString();
|
|
44721
|
-
const testingObject = JSON.stringify({
|
|
44722
|
-
answerArea: {
|
|
44723
|
-
calculator: false,
|
|
44724
|
-
chi2Table: false,
|
|
44725
|
-
financialCalculatorMonthlyPayment: false,
|
|
44726
|
-
financialCalculatorTimeToPayOff: false,
|
|
44727
|
-
financialCalculatorTotalAmount: false,
|
|
44728
|
-
periodicTable: false,
|
|
44729
|
-
periodicTableWithKey: false,
|
|
44730
|
-
tTable: false,
|
|
44731
|
-
zTable: false
|
|
44732
|
-
},
|
|
44733
|
-
hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
|
|
44734
|
-
itemDataVersion: {
|
|
44735
|
-
major: 0,
|
|
44736
|
-
minor: 1
|
|
44737
|
-
},
|
|
44738
|
-
question: {
|
|
44739
|
-
content: `${randomPhrase}`,
|
|
44740
|
-
images: {},
|
|
44741
|
-
widgets: {
|
|
44742
|
-
expression1: {
|
|
44743
|
-
alignment: "default",
|
|
44744
|
-
graded: false,
|
|
44745
|
-
options: {
|
|
44746
|
-
answerForms: [{
|
|
44747
|
-
considered: "wrong",
|
|
44748
|
-
form: false,
|
|
44749
|
-
key: 0,
|
|
44750
|
-
simplify: false,
|
|
44751
|
-
value: `${randomString}`
|
|
44752
|
-
}],
|
|
44753
|
-
ariaLabel: "Answer",
|
|
44754
|
-
buttonSets: ["basic"],
|
|
44755
|
-
functions: ["f", "g", "h"],
|
|
44756
|
-
static: true,
|
|
44757
|
-
times: false,
|
|
44758
|
-
visibleLabel: "Answer"
|
|
44759
|
-
},
|
|
44760
|
-
static: true,
|
|
44761
|
-
type: "expression",
|
|
44762
|
-
version: {
|
|
44763
|
-
major: 1,
|
|
44764
|
-
minor: 0
|
|
44765
|
-
}
|
|
44766
|
-
}
|
|
44767
|
-
}
|
|
44768
|
-
}
|
|
44769
|
-
});
|
|
44770
|
-
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
44771
|
-
const parsedTestJSON = jsonParse(testJSON);
|
|
44772
|
-
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
44773
|
-
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
44774
|
-
}
|
|
44775
|
-
function buildRandomString(capitalize = false) {
|
|
44776
|
-
let randomString = "";
|
|
44777
|
-
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
44778
|
-
for (let i = 0; i < randomLength; i++) {
|
|
44779
|
-
const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
44780
|
-
randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
|
|
44781
|
-
}
|
|
44782
|
-
return randomString;
|
|
44783
|
-
}
|
|
44784
|
-
function buildRandomPhrase() {
|
|
44785
|
-
const phrases = [];
|
|
44786
|
-
const randomLength = Math.floor(Math.random() * 10) + 5;
|
|
44787
|
-
for (let i = 0; i < randomLength; i++) {
|
|
44788
|
-
phrases.push(buildRandomString(i === 0));
|
|
44789
|
-
}
|
|
44790
|
-
const modifierStart = ["**", "$"];
|
|
44791
|
-
const modifierEnd = ["**", "$"];
|
|
44792
|
-
const modifierIndex = Math.floor(Math.random() * modifierStart.length);
|
|
44793
|
-
return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
|
|
44794
|
-
}
|
|
44795
|
-
function buildTestData(testObject) {
|
|
44796
|
-
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
44797
|
-
}
|
|
44798
|
-
|
|
44799
|
-
function success(value) {
|
|
44800
|
-
return {
|
|
44801
|
-
type: "success",
|
|
44802
|
-
value
|
|
44803
|
-
};
|
|
44804
|
-
}
|
|
44805
|
-
function failure(detail) {
|
|
44806
|
-
return {
|
|
44807
|
-
type: "failure",
|
|
44808
|
-
detail
|
|
44809
|
-
};
|
|
44810
|
-
}
|
|
44811
|
-
function isFailure(result) {
|
|
44812
|
-
return result.type === "failure";
|
|
44813
|
-
}
|
|
44814
|
-
function isSuccess(result) {
|
|
44815
|
-
return result.type === "success";
|
|
44816
|
-
}
|
|
44817
|
-
|
|
44818
|
-
// Result's `all` function is similar to Promise.all: given an array of
|
|
44819
|
-
// results, it returns success if all succeeded, and failure if any failed.
|
|
44820
|
-
function all(results, combineFailureDetails = a => a) {
|
|
44821
|
-
const values = [];
|
|
44822
|
-
const failureDetails = [];
|
|
44823
|
-
for (const result of results) {
|
|
44824
|
-
if (result.type === "success") {
|
|
44825
|
-
values.push(result.value);
|
|
44826
|
-
} else {
|
|
44827
|
-
failureDetails.push(result.detail);
|
|
44828
|
-
}
|
|
44829
|
-
}
|
|
44830
|
-
if (failureDetails.length > 0) {
|
|
44831
|
-
return failure(failureDetails.reduce(combineFailureDetails));
|
|
44832
|
-
}
|
|
44833
|
-
return success(values);
|
|
44834
|
-
}
|
|
44835
|
-
|
|
44836
|
-
class ErrorTrackingParseContext {
|
|
44837
|
-
constructor(path) {
|
|
44838
|
-
this.path = path;
|
|
44839
|
-
}
|
|
44840
|
-
failure(expected, badValue) {
|
|
44841
|
-
return failure([{
|
|
44842
|
-
expected: wrapInArray(expected),
|
|
44843
|
-
badValue,
|
|
44844
|
-
path: this.path
|
|
44845
|
-
}]);
|
|
44846
|
-
}
|
|
44847
|
-
forSubtree(key) {
|
|
44848
|
-
return new ErrorTrackingParseContext([...this.path, key]);
|
|
44849
|
-
}
|
|
44850
|
-
success(value) {
|
|
44851
|
-
return success(value);
|
|
44852
|
-
}
|
|
44853
|
-
}
|
|
44854
|
-
function wrapInArray(a) {
|
|
44855
|
-
return Array.isArray(a) ? a : [a];
|
|
44856
|
-
}
|
|
44857
|
-
|
|
44858
|
-
function formatPath(path) {
|
|
44859
|
-
return "(root)" + path.map(formatPathSegment).join("");
|
|
44860
|
-
}
|
|
44861
|
-
function formatPathSegment(segment) {
|
|
44862
|
-
if (typeof segment === "string") {
|
|
44863
|
-
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
44864
|
-
}
|
|
44865
|
-
return `[${segment.toString()}]`;
|
|
44866
|
-
}
|
|
44867
|
-
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
44868
|
-
|
|
44869
|
-
function message(failure) {
|
|
44870
|
-
const expected = conjoin(failure.expected);
|
|
44871
|
-
const path = formatPath(failure.path);
|
|
44872
|
-
const badValue = JSON.stringify(failure.badValue);
|
|
44873
|
-
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
44874
|
-
}
|
|
44875
|
-
function conjoin(items) {
|
|
44876
|
-
switch (items.length) {
|
|
44877
|
-
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
44878
|
-
case 1:
|
|
44879
|
-
return items[0];
|
|
44880
|
-
case 2:
|
|
44881
|
-
return items.join(" or ");
|
|
44882
|
-
default:
|
|
44883
|
-
{
|
|
44884
|
-
const allButLast = items.slice(0, items.length - 1);
|
|
44885
|
-
const last = items[items.length - 1];
|
|
44886
|
-
return allButLast.join(", ") + ", or " + last;
|
|
44887
|
-
}
|
|
44888
|
-
}
|
|
44889
|
-
}
|
|
44890
|
-
|
|
44891
|
-
function parse(value, parser) {
|
|
44892
|
-
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
44893
|
-
if (isFailure(result)) {
|
|
44894
|
-
return failure(result.detail.map(message).join("; "));
|
|
44895
|
-
}
|
|
44896
|
-
return result;
|
|
44897
|
-
}
|
|
44898
|
-
|
|
44899
|
-
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
44900
|
-
|
|
44901
|
-
function array(elementParser) {
|
|
44902
|
-
return (rawValue, ctx) => {
|
|
44903
|
-
if (!Array.isArray(rawValue)) {
|
|
44904
|
-
return ctx.failure("array", rawValue);
|
|
44905
|
-
}
|
|
44906
|
-
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
44907
|
-
return all(elementResults, concat);
|
|
44908
|
-
};
|
|
44909
|
-
}
|
|
44910
|
-
function concat(a, b) {
|
|
44911
|
-
return [...a, ...b];
|
|
44912
|
-
}
|
|
44913
|
-
|
|
44914
|
-
function boolean(rawValue, ctx) {
|
|
44915
|
-
if (typeof rawValue === "boolean") {
|
|
44916
|
-
return ctx.success(rawValue);
|
|
44917
|
-
}
|
|
44918
|
-
return ctx.failure("boolean", rawValue);
|
|
44919
|
-
}
|
|
44920
|
-
|
|
44921
|
-
function constant(acceptedValue) {
|
|
44922
|
-
return (rawValue, ctx) => {
|
|
44923
|
-
if (rawValue !== acceptedValue) {
|
|
44924
|
-
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
44925
|
-
}
|
|
44926
|
-
return ctx.success(acceptedValue);
|
|
44927
|
-
};
|
|
44928
|
-
}
|
|
44929
|
-
|
|
44930
|
-
function enumeration(...acceptedValues) {
|
|
44931
|
-
return (rawValue, ctx) => {
|
|
44932
|
-
if (typeof rawValue === "string") {
|
|
44933
|
-
const index = acceptedValues.indexOf(rawValue);
|
|
44934
|
-
if (index > -1) {
|
|
44935
|
-
return ctx.success(acceptedValues[index]);
|
|
44936
|
-
}
|
|
44937
|
-
}
|
|
44938
|
-
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
44939
|
-
return ctx.failure(expected, rawValue);
|
|
44940
|
-
};
|
|
44941
|
-
}
|
|
44942
|
-
|
|
44943
|
-
function isObject(x) {
|
|
44944
|
-
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
44945
|
-
}
|
|
44946
|
-
|
|
44947
|
-
function nullable(parseValue) {
|
|
44948
|
-
return (rawValue, ctx) => {
|
|
44949
|
-
if (rawValue === null) {
|
|
44950
|
-
return ctx.success(rawValue);
|
|
44951
|
-
}
|
|
44952
|
-
return parseValue(rawValue, ctx);
|
|
44953
|
-
};
|
|
44954
|
-
}
|
|
44955
|
-
|
|
44956
|
-
const number = (rawValue, ctx) => {
|
|
44957
|
-
if (typeof rawValue === "number") {
|
|
44958
|
-
return ctx.success(rawValue);
|
|
44959
|
-
}
|
|
44960
|
-
return ctx.failure("number", rawValue);
|
|
44961
|
-
};
|
|
44962
|
-
|
|
44963
|
-
function object(schema) {
|
|
44964
|
-
return (rawValue, ctx) => {
|
|
44965
|
-
if (!isObject(rawValue)) {
|
|
44966
|
-
return ctx.failure("object", rawValue);
|
|
44967
|
-
}
|
|
44968
|
-
const ret = _extends({}, rawValue);
|
|
44969
|
-
const mismatches = [];
|
|
44970
|
-
for (const [prop, propParser] of Object.entries(schema)) {
|
|
44971
|
-
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
44972
|
-
if (isSuccess(result)) {
|
|
44973
|
-
if (result.value !== undefined || prop in rawValue) {
|
|
44974
|
-
ret[prop] = result.value;
|
|
44975
|
-
}
|
|
44976
|
-
} else {
|
|
44977
|
-
mismatches.push(...result.detail);
|
|
44978
|
-
}
|
|
44979
|
-
}
|
|
44980
|
-
if (mismatches.length > 0) {
|
|
44981
|
-
return failure(mismatches);
|
|
44982
|
-
}
|
|
44983
|
-
return ctx.success(ret);
|
|
44984
|
-
};
|
|
44985
|
-
}
|
|
44986
|
-
|
|
44987
|
-
function optional(parseValue) {
|
|
44988
|
-
return (rawValue, ctx) => {
|
|
44989
|
-
if (rawValue === undefined) {
|
|
44990
|
-
return ctx.success(rawValue);
|
|
44991
|
-
}
|
|
44992
|
-
return parseValue(rawValue, ctx);
|
|
44993
|
-
};
|
|
44994
|
-
}
|
|
44995
|
-
|
|
44996
|
-
function pair(parseA, parseB) {
|
|
44997
|
-
return (rawValue, ctx) => {
|
|
44998
|
-
if (!Array.isArray(rawValue)) {
|
|
44999
|
-
return ctx.failure("array", rawValue);
|
|
45000
|
-
}
|
|
45001
|
-
if (rawValue.length !== 2) {
|
|
45002
|
-
return ctx.failure("array of length 2", rawValue);
|
|
45003
|
-
}
|
|
45004
|
-
const [rawA, rawB] = rawValue;
|
|
45005
|
-
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
45006
|
-
if (isFailure(resultA)) {
|
|
45007
|
-
return resultA;
|
|
45008
|
-
}
|
|
45009
|
-
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
45010
|
-
if (isFailure(resultB)) {
|
|
45011
|
-
return resultB;
|
|
45012
|
-
}
|
|
45013
|
-
return ctx.success([resultA.value, resultB.value]);
|
|
45014
|
-
};
|
|
45015
|
-
}
|
|
45016
|
-
|
|
45017
|
-
function pipeParsers(p) {
|
|
45018
|
-
return new ParserPipeline(p);
|
|
45019
|
-
}
|
|
45020
|
-
class ParserPipeline {
|
|
45021
|
-
constructor(parser) {
|
|
45022
|
-
this.parser = parser;
|
|
45023
|
-
}
|
|
45024
|
-
then(nextParser) {
|
|
45025
|
-
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
45026
|
-
}
|
|
45027
|
-
}
|
|
45028
|
-
function composeParsers(parserA, parserB) {
|
|
45029
|
-
return (rawValue, ctx) => {
|
|
45030
|
-
const partialResult = parserA(rawValue, ctx);
|
|
45031
|
-
if (isFailure(partialResult)) {
|
|
45032
|
-
return partialResult;
|
|
45033
|
-
}
|
|
45034
|
-
return parserB(partialResult.value, ctx);
|
|
45035
|
-
};
|
|
45036
|
-
}
|
|
45037
|
-
|
|
45038
|
-
function record(parseKey, parseValue) {
|
|
45039
|
-
return (rawValue, ctx) => {
|
|
45040
|
-
if (!isObject(rawValue)) {
|
|
45041
|
-
return ctx.failure("object", rawValue);
|
|
45042
|
-
}
|
|
45043
|
-
const result = {};
|
|
45044
|
-
const mismatches = [];
|
|
45045
|
-
for (const [key, value] of Object.entries(rawValue)) {
|
|
45046
|
-
const entryCtx = ctx.forSubtree(key);
|
|
45047
|
-
const keyResult = parseKey(key, entryCtx);
|
|
45048
|
-
if (isFailure(keyResult)) {
|
|
45049
|
-
mismatches.push(...keyResult.detail);
|
|
45050
|
-
}
|
|
45051
|
-
const valueResult = parseValue(value, entryCtx);
|
|
45052
|
-
if (isFailure(valueResult)) {
|
|
45053
|
-
mismatches.push(...valueResult.detail);
|
|
45054
|
-
}
|
|
45055
|
-
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
45056
|
-
result[keyResult.value] = valueResult.value;
|
|
45057
|
-
}
|
|
45058
|
-
}
|
|
45059
|
-
if (mismatches.length > 0) {
|
|
45060
|
-
return failure(mismatches);
|
|
45061
|
-
}
|
|
45062
|
-
return ctx.success(result);
|
|
45063
|
-
};
|
|
45064
|
-
}
|
|
45065
|
-
|
|
45066
|
-
const string = (rawValue, ctx) => {
|
|
45067
|
-
if (typeof rawValue === "string") {
|
|
45068
|
-
return ctx.success(rawValue);
|
|
45069
|
-
}
|
|
45070
|
-
return ctx.failure("string", rawValue);
|
|
45071
|
-
};
|
|
45072
|
-
|
|
45073
|
-
function trio(parseA, parseB, parseC) {
|
|
45074
|
-
return (rawValue, ctx) => {
|
|
45075
|
-
if (!Array.isArray(rawValue)) {
|
|
45076
|
-
return ctx.failure("array", rawValue);
|
|
45077
|
-
}
|
|
45078
|
-
if (rawValue.length !== 3) {
|
|
45079
|
-
return ctx.failure("array of length 3", rawValue);
|
|
45080
|
-
}
|
|
45081
|
-
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
45082
|
-
if (isFailure(resultA)) {
|
|
45083
|
-
return resultA;
|
|
45084
|
-
}
|
|
45085
|
-
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
45086
|
-
if (isFailure(resultB)) {
|
|
45087
|
-
return resultB;
|
|
45088
|
-
}
|
|
45089
|
-
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
45090
|
-
if (isFailure(resultC)) {
|
|
45091
|
-
return resultC;
|
|
45092
|
-
}
|
|
45093
|
-
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
45094
|
-
};
|
|
45095
|
-
}
|
|
45096
|
-
|
|
45097
|
-
function union(parseBranch) {
|
|
45098
|
-
return new UnionBuilder(parseBranch);
|
|
45099
|
-
}
|
|
45100
|
-
class UnionBuilder {
|
|
45101
|
-
constructor(parser) {
|
|
45102
|
-
this.parser = parser;
|
|
45103
|
-
}
|
|
45104
|
-
or(newBranch) {
|
|
45105
|
-
return new UnionBuilder(either(this.parser, newBranch));
|
|
45106
|
-
}
|
|
45107
|
-
}
|
|
45108
|
-
function either(parseA, parseB) {
|
|
45109
|
-
return (rawValue, ctx) => {
|
|
45110
|
-
const resultA = parseA(rawValue, ctx);
|
|
45111
|
-
if (isSuccess(resultA)) {
|
|
45112
|
-
return resultA;
|
|
45113
|
-
}
|
|
45114
|
-
return parseB(rawValue, ctx);
|
|
45115
|
-
};
|
|
45116
|
-
}
|
|
45117
|
-
|
|
45118
|
-
function defaulted(parser, fallback) {
|
|
45119
|
-
return (rawValue, ctx) => {
|
|
45120
|
-
if (rawValue == null) {
|
|
45121
|
-
return success(fallback(rawValue));
|
|
45122
|
-
}
|
|
45123
|
-
return parser(rawValue, ctx);
|
|
45124
|
-
};
|
|
45125
|
-
}
|
|
45126
|
-
|
|
45127
|
-
const parseImages = defaulted(record(string, object({
|
|
45128
|
-
width: number,
|
|
45129
|
-
height: number
|
|
45130
|
-
})), () => ({}));
|
|
45131
|
-
|
|
45132
|
-
function parseWidget(parseType, parseOptions) {
|
|
45133
|
-
return object({
|
|
45134
|
-
type: parseType,
|
|
45135
|
-
static: optional(boolean),
|
|
45136
|
-
graded: optional(boolean),
|
|
45137
|
-
alignment: optional(string),
|
|
45138
|
-
options: parseOptions,
|
|
45139
|
-
key: optional(number),
|
|
45140
|
-
version: optional(object({
|
|
45141
|
-
major: number,
|
|
45142
|
-
minor: number
|
|
45143
|
-
}))
|
|
45144
|
-
});
|
|
45145
|
-
}
|
|
45146
|
-
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
45147
|
-
return object({
|
|
45148
|
-
type: parseType,
|
|
45149
|
-
static: optional(boolean),
|
|
45150
|
-
graded: optional(boolean),
|
|
45151
|
-
alignment: optional(string),
|
|
45152
|
-
options: parseOptions,
|
|
45153
|
-
key: optional(number),
|
|
45154
|
-
version: parseVersion
|
|
45155
|
-
});
|
|
45156
|
-
}
|
|
45157
|
-
|
|
45158
|
-
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
45159
|
-
items: array(string),
|
|
45160
|
-
categories: array(string),
|
|
45161
|
-
randomizeItems: defaulted(boolean, () => false),
|
|
45162
|
-
static: defaulted(boolean, () => false),
|
|
45163
|
-
values: array(defaulted(number, () => 0)),
|
|
45164
|
-
highlightLint: optional(boolean),
|
|
45165
|
-
linterContext: optional(object({
|
|
45166
|
-
contentType: string,
|
|
45167
|
-
paths: array(string),
|
|
45168
|
-
stack: array(string)
|
|
45169
|
-
}))
|
|
45170
|
-
}));
|
|
45171
|
-
|
|
45172
|
-
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
45173
|
-
programID: string,
|
|
45174
|
-
programType: any,
|
|
45175
|
-
settings: array(object({
|
|
45176
|
-
name: string,
|
|
45177
|
-
value: string
|
|
45178
|
-
})),
|
|
45179
|
-
showEditor: boolean,
|
|
45180
|
-
showButtons: boolean,
|
|
45181
|
-
height: number,
|
|
45182
|
-
static: defaulted(boolean, () => false)
|
|
45183
|
-
}));
|
|
45184
|
-
|
|
45185
|
-
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
45186
|
-
togglePrompt: string,
|
|
45187
|
-
definition: string,
|
|
45188
|
-
static: defaulted(boolean, () => false)
|
|
45189
|
-
}));
|
|
45190
|
-
|
|
45191
|
-
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
45192
|
-
placeholder: defaulted(string, () => ""),
|
|
45193
|
-
ariaLabel: optional(string),
|
|
45194
|
-
visibleLabel: optional(string),
|
|
45195
|
-
static: defaulted(boolean, () => false),
|
|
45196
|
-
choices: array(object({
|
|
45197
|
-
content: string,
|
|
45198
|
-
correct: boolean
|
|
45199
|
-
}))
|
|
45200
|
-
}));
|
|
45201
|
-
|
|
45202
|
-
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
45203
|
-
showPrompt: string,
|
|
45204
|
-
hidePrompt: string,
|
|
45205
|
-
explanation: string,
|
|
45206
|
-
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
45207
|
-
// referenced before it is defined. There is an import cycle between
|
|
45208
|
-
// this file and widgets-map.ts that could cause it to be undefined.
|
|
45209
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
45210
|
-
static: defaulted(boolean, () => false)
|
|
45211
|
-
}));
|
|
45212
|
-
|
|
45213
|
-
// Given a function, creates a PartialParser that converts one type to another
|
|
45214
|
-
// using that function. The returned parser never fails.
|
|
45215
|
-
function convert(f) {
|
|
45216
|
-
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
45217
|
-
}
|
|
45218
|
-
|
|
45219
|
-
/**
|
|
45220
|
-
* Creates a parser for a widget options type with multiple major versions. Old
|
|
45221
|
-
* versions are migrated to the latest version. The parse fails if the input
|
|
45222
|
-
* data does not match any of the versions.
|
|
45223
|
-
*
|
|
45224
|
-
* @example
|
|
45225
|
-
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
45226
|
-
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
45227
|
-
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
45228
|
-
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
45229
|
-
* .parser;
|
|
45230
|
-
*
|
|
45231
|
-
* @param latestMajorVersion the latest major version of the widget options.
|
|
45232
|
-
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
45233
|
-
* options.
|
|
45234
|
-
* @returns a builder object, to which migrations from earlier versions can be
|
|
45235
|
-
* added. Migrations must be added in "reverse chronological" order as in the
|
|
45236
|
-
* example above.
|
|
45237
|
-
*/
|
|
45238
|
-
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
45239
|
-
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
45240
|
-
}
|
|
45241
|
-
class VersionedWidgetOptionsParserBuilder {
|
|
45242
|
-
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
45243
|
-
this.parser = void 0;
|
|
45244
|
-
this.migrateToLatest = migrateToLatest;
|
|
45245
|
-
this.parseOtherVersions = parseOtherVersions;
|
|
45246
|
-
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
45247
|
-
this.parser = (raw, ctx) => {
|
|
45248
|
-
if (!isObject(raw)) {
|
|
45249
|
-
return ctx.failure("object", raw);
|
|
45250
|
-
}
|
|
45251
|
-
const versionParseResult = parseVersionedObject(raw, ctx);
|
|
45252
|
-
if (isFailure(versionParseResult)) {
|
|
45253
|
-
return versionParseResult;
|
|
45254
|
-
}
|
|
45255
|
-
if (versionParseResult.value.version.major !== majorVersion) {
|
|
45256
|
-
return this.parseOtherVersions(raw, ctx);
|
|
45257
|
-
}
|
|
45258
|
-
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
45259
|
-
};
|
|
45260
|
-
}
|
|
45261
|
-
|
|
45262
|
-
/**
|
|
45263
|
-
* Add a migration from an old version of the widget options.
|
|
45264
|
-
*/
|
|
45265
|
-
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
45266
|
-
const parseOtherVersions = this.parser;
|
|
45267
|
-
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
45268
|
-
return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
|
|
45269
|
-
}
|
|
45270
|
-
}
|
|
45271
|
-
const parseVersionedObject = object({
|
|
45272
|
-
version: defaulted(object({
|
|
45273
|
-
major: number,
|
|
45274
|
-
minor: number
|
|
45275
|
-
}), () => ({
|
|
45276
|
-
major: 0,
|
|
45277
|
-
minor: 0
|
|
45278
|
-
}))
|
|
45279
|
-
});
|
|
45280
|
-
|
|
45281
|
-
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
45282
|
-
const parsePossiblyInvalidAnswerForm = object({
|
|
45283
|
-
// `value` is the possibly invalid part of this. It should always be a
|
|
45284
|
-
// string, but some answer forms don't have it. The Expression widget
|
|
45285
|
-
// ignores invalid values, so we can safely filter them out during parsing.
|
|
45286
|
-
value: optional(string),
|
|
45287
|
-
form: defaulted(boolean, () => false),
|
|
45288
|
-
simplify: defaulted(boolean, () => false),
|
|
45289
|
-
considered: enumeration("correct", "wrong", "ungraded"),
|
|
45290
|
-
key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
|
|
45291
|
-
});
|
|
45292
|
-
function removeInvalidAnswerForms(possiblyInvalid) {
|
|
45293
|
-
const valid = [];
|
|
45294
|
-
for (const answerForm of possiblyInvalid) {
|
|
45295
|
-
const {
|
|
45296
|
-
value
|
|
45297
|
-
} = answerForm;
|
|
45298
|
-
if (value != null) {
|
|
45299
|
-
// Copying the object seems to be needed to make TypeScript happy
|
|
45300
|
-
valid.push(_extends({}, answerForm, {
|
|
45301
|
-
value
|
|
45302
|
-
}));
|
|
45303
|
-
}
|
|
45304
|
-
}
|
|
45305
|
-
return valid;
|
|
45306
|
-
}
|
|
45307
|
-
|
|
45308
|
-
// NOTE(benchristel): I copied the default buttonSets from
|
|
45309
|
-
// expression.tsx. See the parse-perseus-json/README.md for
|
|
45310
|
-
// an explanation of why we want to duplicate the default here.
|
|
45311
|
-
const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
45312
|
-
const version1 = object({
|
|
45313
|
-
major: constant(1),
|
|
45314
|
-
minor: number
|
|
45315
|
-
});
|
|
45316
|
-
const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
|
|
45317
|
-
answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
|
|
45318
|
-
functions: array(string),
|
|
45319
|
-
times: boolean,
|
|
45320
|
-
visibleLabel: optional(string),
|
|
45321
|
-
ariaLabel: optional(string),
|
|
45322
|
-
buttonSets: parseButtonSets,
|
|
45323
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45324
|
-
}));
|
|
45325
|
-
const version0 = optional(object({
|
|
45326
|
-
major: constant(0),
|
|
45327
|
-
minor: number
|
|
45328
|
-
}));
|
|
45329
|
-
const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
|
|
45330
|
-
functions: array(string),
|
|
45331
|
-
times: boolean,
|
|
45332
|
-
visibleLabel: optional(string),
|
|
45333
|
-
ariaLabel: optional(string),
|
|
45334
|
-
form: boolean,
|
|
45335
|
-
simplify: boolean,
|
|
45336
|
-
value: string,
|
|
45337
|
-
buttonSets: parseButtonSets,
|
|
45338
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45339
|
-
}));
|
|
45340
|
-
function migrateV0ToV1(widget) {
|
|
45341
|
-
const {
|
|
45342
|
-
options
|
|
45343
|
-
} = widget;
|
|
45344
|
-
return _extends({}, widget, {
|
|
45345
|
-
version: {
|
|
45346
|
-
major: 1,
|
|
45347
|
-
minor: 0
|
|
45348
|
-
},
|
|
45349
|
-
options: {
|
|
45350
|
-
times: options.times,
|
|
45351
|
-
buttonSets: options.buttonSets,
|
|
45352
|
-
functions: options.functions,
|
|
45353
|
-
buttonsVisible: options.buttonsVisible,
|
|
45354
|
-
visibleLabel: options.visibleLabel,
|
|
45355
|
-
ariaLabel: options.ariaLabel,
|
|
45356
|
-
answerForms: [{
|
|
45357
|
-
considered: "correct",
|
|
45358
|
-
form: options.form,
|
|
45359
|
-
simplify: options.simplify,
|
|
45360
|
-
value: options.value
|
|
45361
|
-
}]
|
|
45362
|
-
}
|
|
45363
|
-
});
|
|
45364
|
-
}
|
|
45365
|
-
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
45366
|
-
|
|
45367
|
-
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
45368
|
-
const parseGradedGroupWidgetOptions = object({
|
|
45369
|
-
title: defaulted(string, () => ""),
|
|
45370
|
-
hasHint: optional(nullable(boolean)),
|
|
45371
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45372
|
-
// The anonymous function below ensures that we don't try to access
|
|
45373
|
-
// parsePerseusRenderer before it's defined.
|
|
45374
|
-
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
45375
|
-
content: string,
|
|
45376
|
-
// This module has an import cycle with parseWidgetsMap.
|
|
45377
|
-
// The anonymous function below ensures that we don't try to access
|
|
45378
|
-
// parseWidgetsMap before it's defined.
|
|
45379
|
-
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
45380
|
-
widgetEnabled: optional(nullable(boolean)),
|
|
45381
|
-
immutableWidgets: optional(nullable(boolean)),
|
|
45382
|
-
images: record(string, object({
|
|
45383
|
-
width: number,
|
|
45384
|
-
height: number
|
|
45385
|
-
}))
|
|
45386
|
-
});
|
|
45387
|
-
const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
|
|
45388
|
-
|
|
45389
|
-
const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
|
|
45390
|
-
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
45391
|
-
}));
|
|
45392
|
-
|
|
45393
|
-
/**
|
|
45394
|
-
* discriminatedUnion() should be preferred over union() when parsing a
|
|
45395
|
-
* discriminated union type, because discriminatedUnion() produces more
|
|
45396
|
-
* understandable failure messages. It takes the discriminant as the source of
|
|
45397
|
-
* truth for which variant is to be parsed, and expects the other data to match
|
|
45398
|
-
* that variant.
|
|
45399
|
-
*/
|
|
45400
|
-
function discriminatedUnionOn(discriminantKey) {
|
|
45401
|
-
const noMoreBranches = (raw, ctx) => {
|
|
45402
|
-
if (!isObject(raw)) {
|
|
45403
|
-
return ctx.failure("object", raw);
|
|
45404
|
-
}
|
|
45405
|
-
return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
|
|
45406
|
-
};
|
|
45407
|
-
return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
|
|
45408
|
-
}
|
|
45409
|
-
class DiscriminatedUnionBuilder {
|
|
45410
|
-
constructor(discriminantKey, parser) {
|
|
45411
|
-
this.discriminantKey = discriminantKey;
|
|
45412
|
-
this.parser = parser;
|
|
45413
|
-
}
|
|
45414
|
-
withBranch(discriminantValue, parseNewVariant) {
|
|
45415
|
-
const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
|
|
45416
|
-
return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
|
|
45417
|
-
}
|
|
45418
|
-
}
|
|
45419
|
-
function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
|
|
45420
|
-
return (raw, ctx) => {
|
|
45421
|
-
if (!isObject(raw)) {
|
|
45422
|
-
return ctx.failure("object", raw);
|
|
45423
|
-
}
|
|
45424
|
-
if (raw[discriminantKey] === discriminantValue) {
|
|
45425
|
-
return parseVariant(raw, ctx);
|
|
45426
|
-
}
|
|
45427
|
-
return parseOtherBranches(raw, ctx);
|
|
45428
|
-
};
|
|
45429
|
-
}
|
|
45430
|
-
|
|
45431
|
-
const pairOfNumbers$3 = pair(number, number);
|
|
45432
|
-
const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
|
|
45433
|
-
const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
45434
|
-
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
45435
|
-
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
45436
|
-
type: constant("absolute_value"),
|
|
45437
|
-
coords: nullable(pairOfPoints)
|
|
45438
|
-
})).withBranch("exponential", object({
|
|
45439
|
-
type: constant("exponential"),
|
|
45440
|
-
asymptote: pairOfPoints,
|
|
45441
|
-
coords: nullable(pairOfPoints)
|
|
45442
|
-
})).withBranch("linear", object({
|
|
45443
|
-
type: constant("linear"),
|
|
45444
|
-
coords: nullable(pairOfPoints)
|
|
45445
|
-
})).withBranch("logarithm", object({
|
|
45446
|
-
type: constant("logarithm"),
|
|
45447
|
-
asymptote: pairOfPoints,
|
|
45448
|
-
coords: nullable(pairOfPoints)
|
|
45449
|
-
})).withBranch("quadratic", object({
|
|
45450
|
-
type: constant("quadratic"),
|
|
45451
|
-
coords: nullable(pairOfPoints)
|
|
45452
|
-
})).withBranch("sinusoid", object({
|
|
45453
|
-
type: constant("sinusoid"),
|
|
45454
|
-
coords: nullable(pairOfPoints)
|
|
45455
|
-
})).withBranch("tangent", object({
|
|
45456
|
-
type: constant("tangent"),
|
|
45457
|
-
coords: nullable(pairOfPoints)
|
|
45458
|
-
})).parser,
|
|
45459
|
-
graph: object({
|
|
45460
|
-
backgroundImage: object({
|
|
45461
|
-
bottom: optional(number),
|
|
45462
|
-
height: optional(number),
|
|
45463
|
-
left: optional(number),
|
|
45464
|
-
scale: optional(number),
|
|
45465
|
-
url: optional(nullable(string)),
|
|
45466
|
-
width: optional(number)
|
|
45467
|
-
}),
|
|
45468
|
-
box: optional(pairOfNumbers$3),
|
|
45469
|
-
editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
|
|
45470
|
-
gridStep: optional(pairOfNumbers$3),
|
|
45471
|
-
labels: pair(string, string),
|
|
45472
|
-
markings: enumeration("graph", "none", "grid"),
|
|
45473
|
-
range: pair(pairOfNumbers$3, pairOfNumbers$3),
|
|
45474
|
-
rulerLabel: constant(""),
|
|
45475
|
-
rulerTicks: number,
|
|
45476
|
-
showProtractor: optional(boolean),
|
|
45477
|
-
showRuler: optional(boolean),
|
|
45478
|
-
showTooltips: optional(boolean),
|
|
45479
|
-
snapStep: optional(pairOfNumbers$3),
|
|
45480
|
-
step: pairOfNumbers$3,
|
|
45481
|
-
valid: optional(union(boolean).or(string).parser)
|
|
45482
|
-
})
|
|
45483
|
-
}));
|
|
45484
|
-
|
|
45485
|
-
const parseGroupWidget = parseWidget(constant("group"),
|
|
45486
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45487
|
-
// The anonymous function below ensures that we don't try to access
|
|
45488
|
-
// parsePerseusRenderer before it's defined.
|
|
45489
|
-
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
45490
|
-
|
|
45491
|
-
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
45492
|
-
url: string,
|
|
45493
|
-
settings: optional(array(object({
|
|
45494
|
-
name: string,
|
|
45495
|
-
value: string
|
|
45496
|
-
}))),
|
|
45497
|
-
width: union(number).or(string).parser,
|
|
45498
|
-
height: union(number).or(string).parser,
|
|
45499
|
-
allowFullScreen: defaulted(boolean, () => false),
|
|
45500
|
-
allowTopNavigation: optional(boolean),
|
|
45501
|
-
static: defaulted(boolean, () => false)
|
|
45502
|
-
}));
|
|
45503
|
-
|
|
45504
|
-
const stringToNumber = (rawValue, ctx) => {
|
|
45505
|
-
if (typeof rawValue === "number") {
|
|
45506
|
-
return ctx.success(rawValue);
|
|
45507
|
-
}
|
|
45508
|
-
const parsedNumber = +rawValue;
|
|
45509
|
-
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
45510
|
-
return ctx.failure("a number or numeric string", rawValue);
|
|
45511
|
-
}
|
|
45512
|
-
return ctx.success(parsedNumber);
|
|
45513
|
-
};
|
|
45514
|
-
|
|
45515
|
-
function emptyToZero(x) {
|
|
45516
|
-
return x === "" ? 0 : x;
|
|
45517
|
-
}
|
|
45518
|
-
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
45519
|
-
// In this specific case, empty string is equivalent to zero. An empty
|
|
45520
|
-
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
45521
|
-
// CSS will treat NaN as invalid and default to 0 instead.
|
|
45522
|
-
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
45523
|
-
const parsePerseusImageBackground = object({
|
|
45524
|
-
url: optional(nullable(string)),
|
|
45525
|
-
width: optional(imageDimensionToNumber),
|
|
45526
|
-
height: optional(imageDimensionToNumber),
|
|
45527
|
-
top: optional(imageDimensionToNumber),
|
|
45528
|
-
left: optional(imageDimensionToNumber),
|
|
45529
|
-
bottom: optional(imageDimensionToNumber),
|
|
45530
|
-
scale: optional(imageDimensionToNumber)
|
|
45531
|
-
});
|
|
45532
|
-
|
|
45533
|
-
const pairOfNumbers$2 = pair(number, number);
|
|
45534
|
-
const parseImageWidget = parseWidget(constant("image"), object({
|
|
45535
|
-
title: optional(string),
|
|
45536
|
-
caption: optional(string),
|
|
45537
|
-
alt: optional(string),
|
|
45538
|
-
backgroundImage: parsePerseusImageBackground,
|
|
45539
|
-
static: optional(boolean),
|
|
45540
|
-
labels: optional(array(object({
|
|
45541
|
-
content: string,
|
|
45542
|
-
alignment: string,
|
|
45543
|
-
coordinates: array(number)
|
|
45544
|
-
}))),
|
|
45545
|
-
range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
|
|
45546
|
-
box: optional(pairOfNumbers$2)
|
|
45547
|
-
}));
|
|
45548
|
-
|
|
45549
|
-
const booleanToString = (rawValue, ctx) => {
|
|
45550
|
-
if (typeof rawValue === "boolean") {
|
|
45551
|
-
return ctx.success(String(rawValue));
|
|
45552
|
-
}
|
|
45553
|
-
return ctx.failure("boolean", rawValue);
|
|
45554
|
-
};
|
|
45555
|
-
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
45556
|
-
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
45557
|
-
inexact: optional(boolean),
|
|
45558
|
-
maxError: optional(union(number).or(string).parser),
|
|
45559
|
-
rightAlign: optional(boolean),
|
|
45560
|
-
simplify: enumeration("required", "optional", "enforced"),
|
|
45561
|
-
size: enumeration("normal", "small"),
|
|
45562
|
-
// TODO(benchristel): there are some content items where value is a
|
|
45563
|
-
// boolean, even though that makes no sense. We should figure out if
|
|
45564
|
-
// those content items are actually published anywhere, and consider
|
|
45565
|
-
// updating them.
|
|
45566
|
-
value: union(number).or(string).or(booleanToString).parser,
|
|
45567
|
-
customKeypad: optional(boolean)
|
|
45568
|
-
}));
|
|
45569
|
-
|
|
45570
|
-
const pairOfNumbers$1 = pair(number, number);
|
|
45571
|
-
const stringOrEmpty = defaulted(string, () => "");
|
|
45572
|
-
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
45573
|
-
const parseFunctionElement = object({
|
|
45574
|
-
type: constant("function"),
|
|
45575
|
-
key: parseKey,
|
|
45576
|
-
options: object({
|
|
45577
|
-
value: string,
|
|
45578
|
-
funcName: string,
|
|
45579
|
-
rangeMin: string,
|
|
45580
|
-
rangeMax: string,
|
|
45581
|
-
color: string,
|
|
45582
|
-
strokeDasharray: string,
|
|
45583
|
-
strokeWidth: number
|
|
45584
|
-
})
|
|
45585
|
-
});
|
|
45586
|
-
const parseLabelElement = object({
|
|
45587
|
-
type: constant("label"),
|
|
45588
|
-
key: parseKey,
|
|
45589
|
-
options: object({
|
|
45590
|
-
label: string,
|
|
45591
|
-
color: string,
|
|
45592
|
-
coordX: string,
|
|
45593
|
-
coordY: string
|
|
45594
|
-
})
|
|
45595
|
-
});
|
|
45596
|
-
const parseLineElement = object({
|
|
45597
|
-
type: constant("line"),
|
|
45598
|
-
key: parseKey,
|
|
45599
|
-
options: object({
|
|
45600
|
-
color: string,
|
|
45601
|
-
startX: string,
|
|
45602
|
-
startY: string,
|
|
45603
|
-
endX: string,
|
|
45604
|
-
endY: string,
|
|
45605
|
-
strokeDasharray: string,
|
|
45606
|
-
strokeWidth: number,
|
|
45607
|
-
arrows: string
|
|
45608
|
-
})
|
|
45609
|
-
});
|
|
45610
|
-
const parseMovableLineElement = object({
|
|
45611
|
-
type: constant("movable-line"),
|
|
45612
|
-
key: parseKey,
|
|
45613
|
-
options: object({
|
|
45614
|
-
startX: string,
|
|
45615
|
-
startY: string,
|
|
45616
|
-
startSubscript: number,
|
|
45617
|
-
endX: string,
|
|
45618
|
-
endY: string,
|
|
45619
|
-
endSubscript: number,
|
|
45620
|
-
constraint: string,
|
|
45621
|
-
snap: number,
|
|
45622
|
-
constraintFn: string,
|
|
45623
|
-
constraintXMin: string,
|
|
45624
|
-
constraintXMax: string,
|
|
45625
|
-
constraintYMin: string,
|
|
45626
|
-
constraintYMax: string
|
|
45627
|
-
})
|
|
45628
|
-
});
|
|
45629
|
-
const parseMovablePointElement = object({
|
|
45630
|
-
type: constant("movable-point"),
|
|
45631
|
-
key: parseKey,
|
|
45632
|
-
options: object({
|
|
45633
|
-
startX: string,
|
|
45634
|
-
startY: string,
|
|
45635
|
-
varSubscript: number,
|
|
45636
|
-
constraint: string,
|
|
45637
|
-
snap: number,
|
|
45638
|
-
constraintFn: string,
|
|
45639
|
-
constraintXMin: stringOrEmpty,
|
|
45640
|
-
constraintXMax: stringOrEmpty,
|
|
45641
|
-
constraintYMin: stringOrEmpty,
|
|
45642
|
-
constraintYMax: stringOrEmpty
|
|
45643
|
-
})
|
|
45644
|
-
});
|
|
45645
|
-
const parseParametricElement = object({
|
|
45646
|
-
type: constant("parametric"),
|
|
45647
|
-
key: parseKey,
|
|
45648
|
-
options: object({
|
|
45649
|
-
x: string,
|
|
45650
|
-
y: string,
|
|
45651
|
-
rangeMin: string,
|
|
45652
|
-
rangeMax: string,
|
|
45653
|
-
color: string,
|
|
45654
|
-
strokeDasharray: string,
|
|
45655
|
-
strokeWidth: number
|
|
45656
|
-
})
|
|
45657
|
-
});
|
|
45658
|
-
const parsePointElement = object({
|
|
45659
|
-
type: constant("point"),
|
|
45660
|
-
key: parseKey,
|
|
45661
|
-
options: object({
|
|
45662
|
-
color: string,
|
|
45663
|
-
coordX: string,
|
|
45664
|
-
coordY: string
|
|
45665
|
-
})
|
|
45666
|
-
});
|
|
45667
|
-
const parseRectangleElement = object({
|
|
45668
|
-
type: constant("rectangle"),
|
|
45669
|
-
key: parseKey,
|
|
45670
|
-
options: object({
|
|
45671
|
-
color: string,
|
|
45672
|
-
coordX: string,
|
|
45673
|
-
coordY: string,
|
|
45674
|
-
width: string,
|
|
45675
|
-
height: string
|
|
45676
|
-
})
|
|
45677
|
-
});
|
|
45678
|
-
const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
45679
|
-
static: defaulted(boolean, () => false),
|
|
45680
|
-
graph: object({
|
|
45681
|
-
editableSettings: optional(array(enumeration("canvas", "graph"))),
|
|
45682
|
-
box: pairOfNumbers$1,
|
|
45683
|
-
labels: array(string),
|
|
45684
|
-
range: pair(pairOfNumbers$1, pairOfNumbers$1),
|
|
45685
|
-
gridStep: pairOfNumbers$1,
|
|
45686
|
-
markings: enumeration("graph", "grid", "none"),
|
|
45687
|
-
snapStep: optional(pairOfNumbers$1),
|
|
45688
|
-
valid: optional(union(boolean).or(string).parser),
|
|
45689
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
45690
|
-
showProtractor: optional(boolean),
|
|
45691
|
-
showRuler: optional(boolean),
|
|
45692
|
-
rulerLabel: optional(string),
|
|
45693
|
-
rulerTicks: optional(number),
|
|
45694
|
-
tickStep: pairOfNumbers$1
|
|
45695
|
-
}),
|
|
45696
|
-
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)
|
|
45697
|
-
}));
|
|
45698
|
-
|
|
45699
|
-
// Used to represent 2-D points and ranges
|
|
45700
|
-
const pairOfNumbers = pair(number, number);
|
|
45701
|
-
const parsePerseusGraphTypeAngle = object({
|
|
45702
|
-
type: constant("angle"),
|
|
45703
|
-
showAngles: optional(boolean),
|
|
45704
|
-
allowReflexAngles: optional(boolean),
|
|
45705
|
-
angleOffsetDeg: optional(number),
|
|
45706
|
-
snapDegrees: optional(number),
|
|
45707
|
-
match: optional(constant("congruent")),
|
|
45708
|
-
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
45709
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
45710
|
-
});
|
|
45711
|
-
const parsePerseusGraphTypeCircle = object({
|
|
45712
|
-
type: constant("circle"),
|
|
45713
|
-
center: optional(pairOfNumbers),
|
|
45714
|
-
radius: optional(number),
|
|
45715
|
-
startCoords: optional(object({
|
|
45716
|
-
center: pairOfNumbers,
|
|
45717
|
-
radius: number
|
|
45718
|
-
})),
|
|
45719
|
-
// TODO: remove coord? it's legacy.
|
|
45720
|
-
coord: optional(pairOfNumbers)
|
|
45721
|
-
});
|
|
45722
|
-
const parsePerseusGraphTypeLinear = object({
|
|
45723
|
-
type: constant("linear"),
|
|
45724
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
45725
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
45726
|
-
// TODO: remove coord? it's legacy.
|
|
45727
|
-
coord: optional(pairOfNumbers)
|
|
45728
|
-
});
|
|
45729
|
-
const parsePerseusGraphTypeLinearSystem = object({
|
|
45730
|
-
type: constant("linear-system"),
|
|
45731
|
-
// TODO(benchristel): default coords to empty array?
|
|
45732
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
45733
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
45734
|
-
// TODO: remove coord? it's legacy.
|
|
45735
|
-
coord: optional(pairOfNumbers)
|
|
45736
|
-
});
|
|
45737
|
-
const parsePerseusGraphTypeNone = object({
|
|
45738
|
-
type: constant("none")
|
|
45739
|
-
});
|
|
45740
|
-
const parsePerseusGraphTypePoint = object({
|
|
45741
|
-
type: constant("point"),
|
|
45742
|
-
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
45743
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
45744
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45745
|
-
// TODO: remove coord? it's legacy.
|
|
45746
|
-
coord: optional(pairOfNumbers)
|
|
45747
|
-
});
|
|
45748
|
-
const parsePerseusGraphTypePolygon = object({
|
|
45749
|
-
type: constant("polygon"),
|
|
45750
|
-
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
45751
|
-
showAngles: optional(boolean),
|
|
45752
|
-
showSides: optional(boolean),
|
|
45753
|
-
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
45754
|
-
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
45755
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45756
|
-
// TODO: remove coord? it's legacy.
|
|
45757
|
-
coord: optional(pairOfNumbers)
|
|
45758
|
-
});
|
|
45759
|
-
const parsePerseusGraphTypeQuadratic = object({
|
|
45760
|
-
type: constant("quadratic"),
|
|
45761
|
-
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
45762
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
45763
|
-
// TODO: remove coord? it's legacy.
|
|
45764
|
-
coord: optional(pairOfNumbers)
|
|
45765
|
-
});
|
|
45766
|
-
const parsePerseusGraphTypeRay = object({
|
|
45767
|
-
type: constant("ray"),
|
|
45768
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
45769
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
45770
|
-
// TODO: remove coord? it's legacy.
|
|
45771
|
-
coord: optional(pairOfNumbers)
|
|
45772
|
-
});
|
|
45773
|
-
const parsePerseusGraphTypeSegment = object({
|
|
45774
|
-
type: constant("segment"),
|
|
45775
|
-
// TODO(benchristel): default numSegments?
|
|
45776
|
-
numSegments: optional(number),
|
|
45777
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
45778
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
45779
|
-
// TODO: remove coord? it's legacy.
|
|
45780
|
-
coord: optional(pairOfNumbers)
|
|
45781
|
-
});
|
|
45782
|
-
const parsePerseusGraphTypeSinusoid = object({
|
|
45783
|
-
type: constant("sinusoid"),
|
|
45784
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
45785
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
45786
|
-
// TODO: remove coord? it's legacy.
|
|
45787
|
-
coord: optional(pairOfNumbers)
|
|
45788
|
-
});
|
|
45789
|
-
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;
|
|
45790
|
-
const parseLockedFigureColor = enumeration(...lockedFigureColorNames);
|
|
45791
|
-
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
45792
|
-
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
45793
|
-
const parseLockedLabelType = object({
|
|
45794
|
-
type: constant("label"),
|
|
45795
|
-
coord: pairOfNumbers,
|
|
45796
|
-
text: string,
|
|
45797
|
-
color: parseLockedFigureColor,
|
|
45798
|
-
size: enumeration("small", "medium", "large")
|
|
45799
|
-
});
|
|
45800
|
-
const parseLockedPointType = object({
|
|
45801
|
-
type: constant("point"),
|
|
45802
|
-
coord: pairOfNumbers,
|
|
45803
|
-
color: parseLockedFigureColor,
|
|
45804
|
-
filled: boolean,
|
|
45805
|
-
// TODO(benchristel): default labels to empty array?
|
|
45806
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45807
|
-
ariaLabel: optional(string)
|
|
45808
|
-
});
|
|
45809
|
-
const parseLockedLineType = object({
|
|
45810
|
-
type: constant("line"),
|
|
45811
|
-
kind: enumeration("line", "ray", "segment"),
|
|
45812
|
-
points: pair(parseLockedPointType, parseLockedPointType),
|
|
45813
|
-
color: parseLockedFigureColor,
|
|
45814
|
-
lineStyle: parseLockedLineStyle,
|
|
45815
|
-
showPoint1: defaulted(boolean, () => false),
|
|
45816
|
-
showPoint2: defaulted(boolean, () => false),
|
|
45817
|
-
// TODO(benchristel): default labels to empty array?
|
|
45818
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45819
|
-
ariaLabel: optional(string)
|
|
45820
|
-
});
|
|
45821
|
-
const parseLockedVectorType = object({
|
|
45822
|
-
type: constant("vector"),
|
|
45823
|
-
points: pair(pairOfNumbers, pairOfNumbers),
|
|
45824
|
-
color: parseLockedFigureColor,
|
|
45825
|
-
// TODO(benchristel): default labels to empty array?
|
|
45826
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45827
|
-
ariaLabel: optional(string)
|
|
45828
|
-
});
|
|
45829
|
-
const parseLockedEllipseType = object({
|
|
45830
|
-
type: constant("ellipse"),
|
|
45831
|
-
center: pairOfNumbers,
|
|
45832
|
-
radius: pairOfNumbers,
|
|
45833
|
-
angle: number,
|
|
45834
|
-
color: parseLockedFigureColor,
|
|
45835
|
-
fillStyle: parseLockedFigureFillType,
|
|
45836
|
-
strokeStyle: parseLockedLineStyle,
|
|
45837
|
-
// TODO(benchristel): default labels to empty array?
|
|
45838
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45839
|
-
ariaLabel: optional(string)
|
|
45840
|
-
});
|
|
45841
|
-
const parseLockedPolygonType = object({
|
|
45842
|
-
type: constant("polygon"),
|
|
45843
|
-
points: array(pairOfNumbers),
|
|
45844
|
-
color: parseLockedFigureColor,
|
|
45845
|
-
showVertices: boolean,
|
|
45846
|
-
fillStyle: parseLockedFigureFillType,
|
|
45847
|
-
strokeStyle: parseLockedLineStyle,
|
|
45848
|
-
// TODO(benchristel): default labels to empty array?
|
|
45849
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45850
|
-
ariaLabel: optional(string)
|
|
45851
|
-
});
|
|
45852
|
-
const parseLockedFunctionType = object({
|
|
45853
|
-
type: constant("function"),
|
|
45854
|
-
color: parseLockedFigureColor,
|
|
45855
|
-
strokeStyle: parseLockedLineStyle,
|
|
45856
|
-
equation: string,
|
|
45857
|
-
directionalAxis: enumeration("x", "y"),
|
|
45858
|
-
domain: optional(pairOfNumbers),
|
|
45859
|
-
// TODO(benchristel): default labels to empty array?
|
|
45860
|
-
labels: optional(array(parseLockedLabelType)),
|
|
45861
|
-
ariaLabel: optional(string)
|
|
45862
|
-
});
|
|
45863
|
-
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;
|
|
45864
|
-
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
45865
|
-
step: pairOfNumbers,
|
|
45866
|
-
// TODO(benchristel): rather than making gridStep and snapStep
|
|
45867
|
-
// optional, we should duplicate the defaulting logic from the
|
|
45868
|
-
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
45869
|
-
// why.
|
|
45870
|
-
gridStep: optional(pairOfNumbers),
|
|
45871
|
-
snapStep: optional(pairOfNumbers),
|
|
45872
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
45873
|
-
markings: enumeration("graph", "grid", "none"),
|
|
45874
|
-
labels: optional(array(string)),
|
|
45875
|
-
showProtractor: boolean,
|
|
45876
|
-
showRuler: optional(boolean),
|
|
45877
|
-
showTooltips: optional(boolean),
|
|
45878
|
-
rulerLabel: optional(string),
|
|
45879
|
-
rulerTicks: optional(number),
|
|
45880
|
-
range: pair(pairOfNumbers, pairOfNumbers),
|
|
45881
|
-
// NOTE(benchristel): I copied the default graph from
|
|
45882
|
-
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
45883
|
-
// an explanation of why we want to duplicate the default here.
|
|
45884
|
-
graph: defaulted(parsePerseusGraphType, () => ({
|
|
45885
|
-
type: "linear"
|
|
45886
|
-
})),
|
|
45887
|
-
correct: parsePerseusGraphType,
|
|
45888
|
-
// TODO(benchristel): default lockedFigures to empty array
|
|
45889
|
-
lockedFigures: optional(array(parseLockedFigure)),
|
|
45890
|
-
fullGraphLabel: optional(string),
|
|
45891
|
-
fullGraphAriaDescription: optional(string)
|
|
45892
|
-
}));
|
|
45893
|
-
|
|
45894
|
-
const parseLabelImageWidget = parseWidget(constant("label-image"), object({
|
|
45895
|
-
choices: array(string),
|
|
45896
|
-
imageUrl: string,
|
|
45897
|
-
imageAlt: string,
|
|
45898
|
-
imageHeight: number,
|
|
45899
|
-
imageWidth: number,
|
|
45900
|
-
markers: array(object({
|
|
45901
|
-
answers: array(string),
|
|
45902
|
-
label: string,
|
|
45903
|
-
x: number,
|
|
45904
|
-
y: number
|
|
45905
|
-
})),
|
|
45906
|
-
hideChoicesFromInstructions: boolean,
|
|
45907
|
-
multipleAnswers: boolean,
|
|
45908
|
-
static: defaulted(boolean, () => false)
|
|
45909
|
-
}));
|
|
45910
|
-
|
|
45911
|
-
const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
45912
|
-
labels: array(string),
|
|
45913
|
-
left: array(string),
|
|
45914
|
-
right: array(string),
|
|
45915
|
-
orderMatters: boolean,
|
|
45916
|
-
padding: boolean
|
|
45917
|
-
}));
|
|
45918
|
-
|
|
45919
|
-
const numberOrString = union(number).or(string).parser;
|
|
45920
|
-
const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
|
|
45921
|
-
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
45922
|
-
prefix: optional(string),
|
|
45923
|
-
suffix: optional(string),
|
|
45924
|
-
answers: array(array(numeric)),
|
|
45925
|
-
cursorPosition: optional(array(number)),
|
|
45926
|
-
matrixBoardSize: array(number),
|
|
45927
|
-
static: optional(boolean)
|
|
45928
|
-
}));
|
|
45929
|
-
|
|
45930
|
-
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
45931
|
-
// The default value for image comes from measurer.tsx.
|
|
45932
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
45933
|
-
// defaults here.
|
|
45934
|
-
image: defaulted(parsePerseusImageBackground, () => ({
|
|
45935
|
-
url: null,
|
|
45936
|
-
top: 0,
|
|
45937
|
-
left: 0
|
|
45938
|
-
})),
|
|
45939
|
-
showProtractor: boolean,
|
|
45940
|
-
showRuler: boolean,
|
|
45941
|
-
rulerLabel: string,
|
|
45942
|
-
rulerTicks: number,
|
|
45943
|
-
rulerPixels: number,
|
|
45944
|
-
rulerLength: number,
|
|
45945
|
-
box: pair(number, number),
|
|
45946
|
-
// TODO(benchristel): static is not used. Remove it?
|
|
45947
|
-
static: defaulted(boolean, () => false)
|
|
45948
|
-
}));
|
|
45949
|
-
|
|
45950
|
-
const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
|
|
45951
|
-
widgetId: string,
|
|
45952
|
-
rotationAngle: optional(number),
|
|
45953
|
-
smiles: optional(string)
|
|
45954
|
-
}));
|
|
45955
|
-
|
|
45956
|
-
const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
|
|
45957
|
-
const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
45958
|
-
range: array(number),
|
|
45959
|
-
labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
|
|
45960
|
-
labelStyle: string,
|
|
45961
|
-
labelTicks: boolean,
|
|
45962
|
-
isTickCtrl: optional(nullable(boolean)),
|
|
45963
|
-
divisionRange: array(number),
|
|
45964
|
-
numDivisions: optional(nullable(number)),
|
|
45965
|
-
// NOTE(benchristel): I copied the default snapDivisions from
|
|
45966
|
-
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
45967
|
-
// an explanation of why we want to duplicate the default here.
|
|
45968
|
-
snapDivisions: defaulted(number, () => 2),
|
|
45969
|
-
tickStep: optional(nullable(number)),
|
|
45970
|
-
correctRel: optional(nullable(string)),
|
|
45971
|
-
correctX: nullable(number),
|
|
45972
|
-
initialX: optional(nullable(number)),
|
|
45973
|
-
showTooltip: optional(boolean),
|
|
45974
|
-
static: defaulted(boolean, () => false)
|
|
45975
|
-
}));
|
|
45976
|
-
|
|
45977
|
-
const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
|
|
45978
|
-
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
45979
|
-
answers: array(object({
|
|
45980
|
-
message: string,
|
|
45981
|
-
// TODO(benchristel): value should never be null or undefined,
|
|
45982
|
-
// but we have some content where it is anyway. If we backfill
|
|
45983
|
-
// the data, simplify this.
|
|
45984
|
-
value: optional(nullable(number)),
|
|
45985
|
-
status: string,
|
|
45986
|
-
answerForms: optional(array(parseMathFormat)),
|
|
45987
|
-
strict: boolean,
|
|
45988
|
-
maxError: optional(nullable(number)),
|
|
45989
|
-
// TODO(benchristel): simplify should never be a boolean, but we
|
|
45990
|
-
// have some content where it is anyway. If we ever backfill
|
|
45991
|
-
// the data, we should simplify `simplify`.
|
|
45992
|
-
simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
|
|
45993
|
-
})),
|
|
45994
|
-
labelText: optional(string),
|
|
45995
|
-
size: string,
|
|
45996
|
-
coefficient: defaulted(boolean, () => false),
|
|
45997
|
-
rightAlign: optional(boolean),
|
|
45998
|
-
static: defaulted(boolean, () => false),
|
|
45999
|
-
answerForms: optional(array(object({
|
|
46000
|
-
name: parseMathFormat,
|
|
46001
|
-
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
46002
|
-
})))
|
|
46003
|
-
}));
|
|
46004
|
-
|
|
46005
|
-
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
46006
|
-
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
46007
|
-
// it's defined.
|
|
46008
|
-
function parseRenderer(rawValue, ctx) {
|
|
46009
|
-
return parsePerseusRenderer(rawValue, ctx);
|
|
46010
|
-
}
|
|
46011
|
-
const largeToAuto = (height, ctx) => {
|
|
46012
|
-
if (height === "large") {
|
|
46013
|
-
return ctx.success("auto");
|
|
46014
|
-
}
|
|
46015
|
-
return ctx.success(height);
|
|
46016
|
-
};
|
|
46017
|
-
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
46018
|
-
options: defaulted(array(parseRenderer), () => []),
|
|
46019
|
-
correctOptions: array(parseRenderer),
|
|
46020
|
-
otherOptions: array(parseRenderer),
|
|
46021
|
-
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
46022
|
-
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
46023
|
-
}));
|
|
46024
|
-
|
|
46025
|
-
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
46026
|
-
passageNumber: number,
|
|
46027
|
-
referenceNumber: number,
|
|
46028
|
-
summaryText: optional(string)
|
|
46029
|
-
}));
|
|
46030
|
-
|
|
46031
|
-
const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
46032
|
-
footnotes: defaulted(string, () => ""),
|
|
46033
|
-
passageText: string,
|
|
46034
|
-
passageTitle: defaulted(string, () => ""),
|
|
46035
|
-
showLineNumbers: boolean,
|
|
46036
|
-
static: defaulted(boolean, () => false)
|
|
46037
|
-
}));
|
|
46038
|
-
|
|
46039
|
-
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
46040
|
-
url: string,
|
|
46041
|
-
description: string
|
|
46042
|
-
}));
|
|
46043
|
-
|
|
46044
|
-
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
46045
|
-
labels: array(string),
|
|
46046
|
-
categories: array(string),
|
|
46047
|
-
type: enumeration(...plotterPlotTypes),
|
|
46048
|
-
maxY: number,
|
|
46049
|
-
// The default value for scaleY comes from plotter.tsx.
|
|
46050
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46051
|
-
// defaults here.
|
|
46052
|
-
scaleY: defaulted(number, () => 1),
|
|
46053
|
-
labelInterval: optional(nullable(number)),
|
|
46054
|
-
// The default value for snapsPerLine comes from plotter.tsx.
|
|
46055
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46056
|
-
// defaults here.
|
|
46057
|
-
snapsPerLine: defaulted(number, () => 2),
|
|
46058
|
-
starting: array(number),
|
|
46059
|
-
correct: array(number),
|
|
46060
|
-
picUrl: optional(nullable(string)),
|
|
46061
|
-
picSize: optional(nullable(number)),
|
|
46062
|
-
picBoxHeight: optional(nullable(number)),
|
|
46063
|
-
// NOTE(benchristel): I copied the default plotDimensions from
|
|
46064
|
-
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
46065
|
-
// of why we want to duplicate the defaults here.
|
|
46066
|
-
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
46067
|
-
}));
|
|
46068
|
-
|
|
46069
|
-
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
46070
|
-
programID: string,
|
|
46071
|
-
height: number
|
|
46072
|
-
}));
|
|
46073
|
-
|
|
46074
|
-
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
46075
|
-
choices: array(object({
|
|
46076
|
-
content: defaulted(string, () => ""),
|
|
46077
|
-
clue: optional(string),
|
|
46078
|
-
correct: optional(boolean),
|
|
46079
|
-
isNoneOfTheAbove: optional(boolean),
|
|
46080
|
-
// deprecated
|
|
46081
|
-
// There is an import cycle between radio-widget.ts and
|
|
46082
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
46083
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
46084
|
-
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
46085
|
-
})),
|
|
46086
|
-
hasNoneOfTheAbove: optional(boolean),
|
|
46087
|
-
countChoices: optional(boolean),
|
|
46088
|
-
randomize: optional(boolean),
|
|
46089
|
-
multipleSelect: optional(boolean),
|
|
46090
|
-
deselectEnabled: optional(boolean),
|
|
46091
|
-
// deprecated
|
|
46092
|
-
onePerLine: optional(boolean),
|
|
46093
|
-
// deprecated
|
|
46094
|
-
displayCount: optional(any),
|
|
46095
|
-
// v0 props
|
|
46096
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
46097
|
-
noneOfTheAbove: optional(constant(false))
|
|
46098
|
-
}));
|
|
46099
|
-
|
|
46100
|
-
const parseSorterWidget = parseWidget(constant("sorter"), object({
|
|
46101
|
-
correct: array(string),
|
|
46102
|
-
padding: boolean,
|
|
46103
|
-
layout: enumeration("horizontal", "vertical")
|
|
46104
|
-
}));
|
|
46105
|
-
|
|
46106
|
-
const parseTableWidget = parseWidget(constant("table"), object({
|
|
46107
|
-
headers: array(string),
|
|
46108
|
-
rows: number,
|
|
46109
|
-
columns: number,
|
|
46110
|
-
answers: array(array(string))
|
|
46111
|
-
}));
|
|
46112
|
-
|
|
46113
|
-
const parseVideoWidget = parseWidget(constant("video"), object({
|
|
46114
|
-
location: string,
|
|
46115
|
-
static: optional(boolean)
|
|
46116
|
-
}));
|
|
46117
|
-
|
|
46118
|
-
const parseWidgetsMap = (rawValue, ctx) => {
|
|
46119
|
-
if (!isObject(rawValue)) {
|
|
46120
|
-
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
46121
|
-
}
|
|
46122
|
-
const widgetsMap = {};
|
|
46123
|
-
for (const key of Object.keys(rawValue)) {
|
|
46124
|
-
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
46125
|
-
// but it's the only way I could find to make TypeScript check the key
|
|
46126
|
-
// against the widget type.
|
|
46127
|
-
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
46128
|
-
if (isFailure(entryResult)) {
|
|
46129
|
-
return entryResult;
|
|
46130
|
-
}
|
|
46131
|
-
}
|
|
46132
|
-
return ctx.success(widgetsMap);
|
|
46133
|
-
};
|
|
46134
|
-
const parseWidgetsMapEntry = ([id, widget], widgetMap, ctx) => {
|
|
46135
|
-
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
46136
|
-
if (isFailure(idComponentsResult)) {
|
|
46137
|
-
return idComponentsResult;
|
|
46138
|
-
}
|
|
46139
|
-
const [type, n] = idComponentsResult.value;
|
|
46140
|
-
function parseAndAssign(key, parse) {
|
|
46141
|
-
const widgetResult = parse(widget, ctx);
|
|
46142
|
-
if (isFailure(widgetResult)) {
|
|
46143
|
-
return widgetResult;
|
|
46144
|
-
}
|
|
46145
|
-
widgetMap[key] = widgetResult.value;
|
|
46146
|
-
return ctx.success(undefined);
|
|
46147
|
-
}
|
|
46148
|
-
switch (type) {
|
|
46149
|
-
case "categorizer":
|
|
46150
|
-
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
46151
|
-
case "cs-program":
|
|
46152
|
-
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
46153
|
-
case "definition":
|
|
46154
|
-
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
46155
|
-
case "dropdown":
|
|
46156
|
-
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
46157
|
-
case "explanation":
|
|
46158
|
-
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
46159
|
-
case "expression":
|
|
46160
|
-
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
46161
|
-
case "grapher":
|
|
46162
|
-
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
46163
|
-
case "group":
|
|
46164
|
-
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
46165
|
-
case "graded-group":
|
|
46166
|
-
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
46167
|
-
case "graded-group-set":
|
|
46168
|
-
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
46169
|
-
case "iframe":
|
|
46170
|
-
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
46171
|
-
case "image":
|
|
46172
|
-
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
46173
|
-
case "input-number":
|
|
46174
|
-
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
46175
|
-
case "interaction":
|
|
46176
|
-
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
46177
|
-
case "interactive-graph":
|
|
46178
|
-
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
46179
|
-
case "label-image":
|
|
46180
|
-
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
46181
|
-
case "matcher":
|
|
46182
|
-
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
46183
|
-
case "matrix":
|
|
46184
|
-
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
46185
|
-
case "measurer":
|
|
46186
|
-
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
46187
|
-
case "molecule-renderer":
|
|
46188
|
-
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
46189
|
-
case "number-line":
|
|
46190
|
-
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
46191
|
-
case "numeric-input":
|
|
46192
|
-
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
46193
|
-
case "orderer":
|
|
46194
|
-
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
46195
|
-
case "passage":
|
|
46196
|
-
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
46197
|
-
case "passage-ref":
|
|
46198
|
-
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
46199
|
-
case "passage-ref-target":
|
|
46200
|
-
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
46201
|
-
// used in test content. See:
|
|
46202
|
-
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
46203
|
-
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
46204
|
-
case "phet-simulation":
|
|
46205
|
-
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
46206
|
-
case "plotter":
|
|
46207
|
-
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
46208
|
-
case "python-program":
|
|
46209
|
-
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
46210
|
-
case "radio":
|
|
46211
|
-
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
46212
|
-
case "sorter":
|
|
46213
|
-
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
46214
|
-
case "table":
|
|
46215
|
-
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
46216
|
-
case "video":
|
|
46217
|
-
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
46218
|
-
case "sequence":
|
|
46219
|
-
// sequence is a deprecated widget type, and the corresponding
|
|
46220
|
-
// widget component no longer exists.
|
|
46221
|
-
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
46222
|
-
case "lights-puzzle":
|
|
46223
|
-
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
46224
|
-
case "simulator":
|
|
46225
|
-
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
46226
|
-
case "transformer":
|
|
46227
|
-
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
46228
|
-
default:
|
|
46229
|
-
if (getWidget(type)) {
|
|
46230
|
-
// @ts-expect-error - 'type' is not a valid widget type
|
|
46231
|
-
return parseAndAssign(`${type} ${n}`, any);
|
|
46232
|
-
}
|
|
46233
|
-
return ctx.failure("a valid widget type", type);
|
|
46234
|
-
}
|
|
46235
|
-
};
|
|
46236
|
-
const parseDeprecatedWidget = parseWidget(
|
|
46237
|
-
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
46238
|
-
(_, ctx) => ctx.success("deprecated-standin"),
|
|
46239
|
-
// Allow any widget options
|
|
46240
|
-
object({}));
|
|
46241
|
-
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
46242
|
-
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
46243
|
-
return ctx.failure("a string representing a positive integer", rawValue);
|
|
46244
|
-
}
|
|
46245
|
-
return ctx.success(+rawValue);
|
|
46246
|
-
};
|
|
46247
|
-
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
46248
|
-
|
|
46249
|
-
const parsePerseusRenderer = defaulted(object({
|
|
46250
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
46251
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
46252
|
-
content: defaulted(string, () => ""),
|
|
46253
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
46254
|
-
// `group` widget can contain another renderer.
|
|
46255
|
-
// The anonymous function below ensures that we don't try to access
|
|
46256
|
-
// parseWidgetsMap before it's defined.
|
|
46257
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
46258
|
-
metadata: optional(array(string)),
|
|
46259
|
-
images: parseImages
|
|
46260
|
-
}),
|
|
46261
|
-
// Default value
|
|
46262
|
-
() => ({
|
|
46263
|
-
content: "",
|
|
46264
|
-
widgets: {},
|
|
46265
|
-
images: {}
|
|
46266
|
-
}));
|
|
46267
|
-
|
|
46268
|
-
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
46269
|
-
|
|
46270
|
-
const parseHint = object({
|
|
46271
|
-
replace: optional(boolean),
|
|
46272
|
-
content: string,
|
|
46273
|
-
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
46274
|
-
metadata: optional(array(string)),
|
|
46275
|
-
images: parseImages
|
|
46276
|
-
});
|
|
46277
|
-
|
|
46278
|
-
const _excluded = ["type", "options"];
|
|
46279
|
-
const parsePerseusItem$1 = object({
|
|
46280
|
-
question: parsePerseusRenderer,
|
|
46281
|
-
hints: defaulted(array(parseHint), () => []),
|
|
46282
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...ItemExtras), boolean)).parser,
|
|
46283
|
-
itemDataVersion: optional(object({
|
|
46284
|
-
major: number,
|
|
46285
|
-
minor: number
|
|
46286
|
-
})),
|
|
46287
|
-
// Deprecated field
|
|
46288
|
-
answer: any
|
|
46289
|
-
});
|
|
46290
|
-
|
|
46291
|
-
// Some answerAreas have extra fields, like:
|
|
46292
|
-
//
|
|
46293
|
-
// "answerArea": {
|
|
46294
|
-
// "type": "multiple",
|
|
46295
|
-
// "options": {
|
|
46296
|
-
// "content": "",
|
|
46297
|
-
// "images": {},
|
|
46298
|
-
// "widgets": {}
|
|
46299
|
-
// }
|
|
46300
|
-
// }
|
|
46301
|
-
//
|
|
46302
|
-
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
46303
|
-
// migration function removes them.
|
|
46304
|
-
function migrateAnswerArea(rawValue, ctx) {
|
|
46305
|
-
const rest = _objectWithoutPropertiesLoose(rawValue, _excluded);
|
|
46306
|
-
return ctx.success(rest);
|
|
46307
|
-
}
|
|
46308
|
-
|
|
46309
|
-
/**
|
|
46310
|
-
* Helper to parse PerseusItem JSON
|
|
46311
|
-
* Why not just use JSON.parse? We want:
|
|
46312
|
-
* - To make sure types are correct
|
|
46313
|
-
* - To give us a central place to validate/transform output if needed
|
|
46314
|
-
* @deprecated - use parseAndMigratePerseusItem instead
|
|
46315
|
-
* @param {string} json - the stringified PerseusItem JSON
|
|
46316
|
-
* @returns {PerseusItem} the parsed PerseusItem object
|
|
46317
|
-
*/
|
|
46318
|
-
function parsePerseusItem(json) {
|
|
46319
|
-
// Try to block a cheating vector which relies on monkey-patching
|
|
46320
|
-
// JSON.parse
|
|
46321
|
-
if (isRealJSONParse(JSON.parse)) {
|
|
46322
|
-
return JSON.parse(json);
|
|
46323
|
-
}
|
|
46324
|
-
throw new Error("Something went wrong.");
|
|
46325
|
-
}
|
|
46326
|
-
/**
|
|
46327
|
-
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
46328
|
-
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
46329
|
-
* data.
|
|
46330
|
-
*
|
|
46331
|
-
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
46332
|
-
* failure, it will contain an error message describing where in the tree
|
|
46333
|
-
* parsing failed.
|
|
46334
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46335
|
-
*/
|
|
46336
|
-
function parseAndMigratePerseusItem(json) {
|
|
46337
|
-
throwErrorIfCheatingDetected();
|
|
46338
|
-
const object = JSON.parse(json);
|
|
46339
|
-
const result = parse(object, parsePerseusItem$1);
|
|
46340
|
-
if (isFailure(result)) {
|
|
46341
|
-
return failure({
|
|
46342
|
-
message: result.detail,
|
|
46343
|
-
invalidObject: object
|
|
46344
|
-
});
|
|
46345
|
-
}
|
|
46346
|
-
return result;
|
|
46347
|
-
}
|
|
46348
|
-
|
|
46349
|
-
/**
|
|
46350
|
-
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
46351
|
-
* latest schema, and runtime-typechecks the result.
|
|
46352
|
-
*
|
|
46353
|
-
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
46354
|
-
* failure, it will contain an error message describing where in the tree
|
|
46355
|
-
* parsing failed.
|
|
46356
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46357
|
-
*/
|
|
46358
|
-
function parseAndMigratePerseusArticle(json) {
|
|
46359
|
-
throwErrorIfCheatingDetected();
|
|
46360
|
-
const object = JSON.parse(json);
|
|
46361
|
-
const result = parse(object, parsePerseusArticle);
|
|
46362
|
-
if (isFailure(result)) {
|
|
46363
|
-
return failure({
|
|
46364
|
-
message: result.detail,
|
|
46365
|
-
invalidObject: object
|
|
46366
|
-
});
|
|
46367
|
-
}
|
|
46368
|
-
return result;
|
|
46369
|
-
}
|
|
46370
|
-
|
|
46371
|
-
/**
|
|
46372
|
-
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
46373
|
-
*/
|
|
46374
|
-
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
46375
|
-
function throwErrorIfCheatingDetected() {
|
|
46376
|
-
if (!isRealJSONParse(JSON.parse)) {
|
|
46377
|
-
throw new Error("Something went wrong.");
|
|
46378
|
-
}
|
|
46379
|
-
}
|
|
46380
|
-
|
|
46381
45066
|
const genericPerseusItemData = {
|
|
46382
45067
|
question: {
|
|
46383
45068
|
content: "",
|
|
@@ -46595,5 +45280,5 @@ const ScoringUtil = {
|
|
|
46595
45280
|
* Types
|
|
46596
45281
|
*/
|
|
46597
45282
|
|
|
46598
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget,
|
|
45283
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseDataFromJSONP, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
46599
45284
|
//# sourceMappingURL=index.js.map
|