@khanacademy/perseus 51.0.0 → 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 +682 -1997
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +103 -3
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +36 -6
- package/dist/index.js +737 -2005
- 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 +159 -14
- 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/index.js
CHANGED
|
@@ -283,14 +283,36 @@ const strings = {
|
|
|
283
283
|
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
284
284
|
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
285
285
|
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",
|
|
286
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
287
|
+
srMultipleSegmentGraphAriaLabel: "%(countOfSegments)s line segments on a coordinate plane.",
|
|
288
|
+
srMultipleSegmentIndividualLabel: "Segment %(indexOfSegment)s: Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
|
|
289
|
+
srSingleSegmentLabel: "Endpoint 1 at %(point1X)s comma %(point1Y)s. Endpoint 2 %(point2X)s comma %(point2Y)s.",
|
|
290
|
+
srSegmentLength: "Segment length %(length)s units.",
|
|
291
|
+
srSingleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s at %(x)s comma %(y)s.",
|
|
292
|
+
srMultipleSegmentGraphEndpointAriaLabel: "Endpoint %(endpointNumber)s on segment %(indexOfSegment)s at %(x)s comma %(y)s.",
|
|
293
|
+
srSegmentGrabHandle: "Segment from %(point1X)s comma %(point1Y)s to %(point2X)s comma %(point2Y)s.",
|
|
286
294
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
287
295
|
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
288
296
|
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
289
297
|
srRayGraph: "A ray on a coordinate plane.",
|
|
290
298
|
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
291
299
|
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
292
|
-
srRayEndpoint: "Endpoint at %(
|
|
293
|
-
srRayTerminalPoint: "Through point at %(
|
|
300
|
+
srRayEndpoint: "Endpoint at %(x)s comma %(y)s.",
|
|
301
|
+
srRayTerminalPoint: "Through point at %(x)s comma %(y)s.",
|
|
302
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
303
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
304
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
305
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
306
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
307
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
308
|
+
srQuadraticGraphVertexQuadrant: "Vertex is in quadrant %(quadrant)s.",
|
|
309
|
+
srQuadraticTwoXIntercepts: "The X-intercepts are at %(intercept1)s comma 0 and %(intercept2)s comma 0.",
|
|
310
|
+
srQuadraticOneXIntercept: "The X-intercept is at %(intercept)s comma 0.",
|
|
311
|
+
srQuadraticYIntercept: "The Y-intercept is at 0 comma %(intercept)s.",
|
|
312
|
+
srQuadraticPointOrigin: "Point %(pointNumber)s on parabola at the origin.",
|
|
313
|
+
srQuadraticPointAxis: "Point %(pointNumber)s on parabola at %(x)s comma %(y)s.",
|
|
314
|
+
srQuadraticPointQuadrant: "Point %(pointNumber)s on parabola in quadrant %(quadrant)s at %(x)s comma %(y)s.",
|
|
315
|
+
srQuadraticInteractiveElements: "Parabola with points at %(point1X)s comma %(point1Y)s, %(point2X)s comma %(point2Y)s, and %(point3X)s comma %(point3Y)s."
|
|
294
316
|
// The above strings are used for interactive graph SR descriptions.
|
|
295
317
|
};
|
|
296
318
|
|
|
@@ -680,58 +702,181 @@ const mockStrings = {
|
|
|
680
702
|
} = _ref40;
|
|
681
703
|
return `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}.`;
|
|
682
704
|
},
|
|
705
|
+
srSingleSegmentGraphAriaLabel: "A line segment on a coordinate plane.",
|
|
706
|
+
srMultipleSegmentGraphAriaLabel: _ref41 => {
|
|
707
|
+
let {
|
|
708
|
+
countOfSegments
|
|
709
|
+
} = _ref41;
|
|
710
|
+
return `${countOfSegments} segments on a coordinate plane.`;
|
|
711
|
+
},
|
|
712
|
+
srMultipleSegmentIndividualLabel: _ref42 => {
|
|
713
|
+
let {
|
|
714
|
+
point1X,
|
|
715
|
+
point1Y,
|
|
716
|
+
point2X,
|
|
717
|
+
point2Y,
|
|
718
|
+
indexOfSegment
|
|
719
|
+
} = _ref42;
|
|
720
|
+
return `Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
|
|
721
|
+
},
|
|
722
|
+
srSingleSegmentLabel: _ref43 => {
|
|
723
|
+
let {
|
|
724
|
+
point1X,
|
|
725
|
+
point1Y,
|
|
726
|
+
point2X,
|
|
727
|
+
point2Y
|
|
728
|
+
} = _ref43;
|
|
729
|
+
return `Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`;
|
|
730
|
+
},
|
|
731
|
+
srSegmentLength: _ref44 => {
|
|
732
|
+
let {
|
|
733
|
+
length
|
|
734
|
+
} = _ref44;
|
|
735
|
+
return `Segment length ${length} units.`;
|
|
736
|
+
},
|
|
737
|
+
srSingleSegmentGraphEndpointAriaLabel: _ref45 => {
|
|
738
|
+
let {
|
|
739
|
+
endpointNumber,
|
|
740
|
+
x,
|
|
741
|
+
y
|
|
742
|
+
} = _ref45;
|
|
743
|
+
return `Endpoint ${endpointNumber} at ${x} comma ${y}.`;
|
|
744
|
+
},
|
|
745
|
+
srMultipleSegmentGraphEndpointAriaLabel: _ref46 => {
|
|
746
|
+
let {
|
|
747
|
+
endpointNumber,
|
|
748
|
+
x,
|
|
749
|
+
y,
|
|
750
|
+
indexOfSegment
|
|
751
|
+
} = _ref46;
|
|
752
|
+
return `Endpoint ${endpointNumber} on segment ${indexOfSegment} at ${x} comma ${y}.`;
|
|
753
|
+
},
|
|
754
|
+
srSegmentGrabHandle: _ref47 => {
|
|
755
|
+
let {
|
|
756
|
+
point1X,
|
|
757
|
+
point1Y,
|
|
758
|
+
point2X,
|
|
759
|
+
point2Y
|
|
760
|
+
} = _ref47;
|
|
761
|
+
return `Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`;
|
|
762
|
+
},
|
|
683
763
|
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
684
|
-
srLinearSystemPoints:
|
|
764
|
+
srLinearSystemPoints: _ref48 => {
|
|
685
765
|
let {
|
|
686
766
|
lineNumber,
|
|
687
767
|
point1X,
|
|
688
768
|
point1Y,
|
|
689
769
|
point2X,
|
|
690
770
|
point2Y
|
|
691
|
-
} =
|
|
771
|
+
} = _ref48;
|
|
692
772
|
return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
|
|
693
773
|
},
|
|
694
|
-
srLinearSystemPoint:
|
|
774
|
+
srLinearSystemPoint: _ref49 => {
|
|
695
775
|
let {
|
|
696
776
|
lineNumber,
|
|
697
777
|
pointSequence,
|
|
698
778
|
x,
|
|
699
779
|
y
|
|
700
|
-
} =
|
|
780
|
+
} = _ref49;
|
|
701
781
|
return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
|
|
702
782
|
},
|
|
703
783
|
srRayGraph: "A ray on a coordinate plane.",
|
|
704
|
-
srRayPoints:
|
|
784
|
+
srRayPoints: _ref50 => {
|
|
705
785
|
let {
|
|
706
786
|
point1X,
|
|
707
787
|
point1Y,
|
|
708
788
|
point2X,
|
|
709
789
|
point2Y
|
|
710
|
-
} =
|
|
790
|
+
} = _ref50;
|
|
711
791
|
return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
|
|
712
792
|
},
|
|
713
|
-
srRayGrabHandle:
|
|
793
|
+
srRayGrabHandle: _ref51 => {
|
|
714
794
|
let {
|
|
715
795
|
point1X,
|
|
716
796
|
point1Y,
|
|
717
797
|
point2X,
|
|
718
798
|
point2Y
|
|
719
|
-
} =
|
|
799
|
+
} = _ref51;
|
|
720
800
|
return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
|
|
721
801
|
},
|
|
722
|
-
srRayEndpoint:
|
|
802
|
+
srRayEndpoint: _ref52 => {
|
|
723
803
|
let {
|
|
724
804
|
x,
|
|
725
805
|
y
|
|
726
|
-
} =
|
|
806
|
+
} = _ref52;
|
|
727
807
|
return `Endpoint at ${x} comma ${y}.`;
|
|
728
808
|
},
|
|
729
|
-
srRayTerminalPoint:
|
|
809
|
+
srRayTerminalPoint: _ref53 => {
|
|
730
810
|
let {
|
|
731
811
|
x,
|
|
732
812
|
y
|
|
733
|
-
} =
|
|
813
|
+
} = _ref53;
|
|
734
814
|
return `Through point at ${x} comma ${y}.`;
|
|
815
|
+
},
|
|
816
|
+
srQuadraticGraph: "A parabola on a 4-quadrant coordinate plane.",
|
|
817
|
+
srQuadraticFaceUp: "The parabola opens upward.",
|
|
818
|
+
srQuadraticFaceDown: "The parabola opens downward.",
|
|
819
|
+
srQuadraticGraphVertexOrigin: "Vertex is at the origin.",
|
|
820
|
+
srQuadraticGraphVertexXAxis: "Vertex is on the X-axis.",
|
|
821
|
+
srQuadraticGraphVertexYAxis: "Vertex is on the Y-axis.",
|
|
822
|
+
srQuadraticGraphVertexQuadrant: _ref54 => {
|
|
823
|
+
let {
|
|
824
|
+
quadrant
|
|
825
|
+
} = _ref54;
|
|
826
|
+
return `Vertex is in quadrant ${quadrant}.`;
|
|
827
|
+
},
|
|
828
|
+
srQuadraticTwoXIntercepts: _ref55 => {
|
|
829
|
+
let {
|
|
830
|
+
intercept1,
|
|
831
|
+
intercept2
|
|
832
|
+
} = _ref55;
|
|
833
|
+
return `The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`;
|
|
834
|
+
},
|
|
835
|
+
srQuadraticOneXIntercept: _ref56 => {
|
|
836
|
+
let {
|
|
837
|
+
intercept
|
|
838
|
+
} = _ref56;
|
|
839
|
+
return `The X-intercept is at ${intercept} comma 0.`;
|
|
840
|
+
},
|
|
841
|
+
srQuadraticYIntercept: _ref57 => {
|
|
842
|
+
let {
|
|
843
|
+
intercept
|
|
844
|
+
} = _ref57;
|
|
845
|
+
return `The Y-intercept is at 0 comma ${intercept}.`;
|
|
846
|
+
},
|
|
847
|
+
srQuadraticPointOrigin: _ref58 => {
|
|
848
|
+
let {
|
|
849
|
+
pointNumber
|
|
850
|
+
} = _ref58;
|
|
851
|
+
return `Point ${pointNumber} on parabola at the origin.`;
|
|
852
|
+
},
|
|
853
|
+
srQuadraticPointAxis: _ref59 => {
|
|
854
|
+
let {
|
|
855
|
+
pointNumber,
|
|
856
|
+
x,
|
|
857
|
+
y
|
|
858
|
+
} = _ref59;
|
|
859
|
+
return `Point ${pointNumber} on parabola at ${x} comma ${y}.`;
|
|
860
|
+
},
|
|
861
|
+
srQuadraticPointQuadrant: _ref60 => {
|
|
862
|
+
let {
|
|
863
|
+
pointNumber,
|
|
864
|
+
x,
|
|
865
|
+
y,
|
|
866
|
+
quadrant
|
|
867
|
+
} = _ref60;
|
|
868
|
+
return `Point ${pointNumber} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`;
|
|
869
|
+
},
|
|
870
|
+
srQuadraticInteractiveElements: _ref61 => {
|
|
871
|
+
let {
|
|
872
|
+
point1X,
|
|
873
|
+
point1Y,
|
|
874
|
+
point2X,
|
|
875
|
+
point2Y,
|
|
876
|
+
point3X,
|
|
877
|
+
point3Y
|
|
878
|
+
} = _ref61;
|
|
879
|
+
return `Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`;
|
|
735
880
|
}
|
|
736
881
|
// The above strings are used for interactive graph SR descriptions.
|
|
737
882
|
};
|
|
@@ -2006,23 +2151,6 @@ const keypadConfigurationForProps = widgetOptions => {
|
|
|
2006
2151
|
times: widgetOptions.times
|
|
2007
2152
|
};
|
|
2008
2153
|
};
|
|
2009
|
-
const propUpgrades$2 = {
|
|
2010
|
-
/* c8 ignore next */
|
|
2011
|
-
"1": v0props => ({
|
|
2012
|
-
times: v0props.times,
|
|
2013
|
-
buttonSets: v0props.buttonSets,
|
|
2014
|
-
functions: v0props.functions,
|
|
2015
|
-
buttonsVisible: v0props.buttonsVisible,
|
|
2016
|
-
visibleLabel: v0props.visibleLabel,
|
|
2017
|
-
ariaLabel: v0props.ariaLabel,
|
|
2018
|
-
answerForms: [{
|
|
2019
|
-
considered: "correct",
|
|
2020
|
-
form: v0props.form,
|
|
2021
|
-
simplify: v0props.simplify,
|
|
2022
|
-
value: v0props.value
|
|
2023
|
-
}]
|
|
2024
|
-
})
|
|
2025
|
-
};
|
|
2026
2154
|
const ExpressionWithDependencies = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
|
|
2027
2155
|
const deps = useDependencies();
|
|
2028
2156
|
return /*#__PURE__*/React__namespace.createElement(Expression, _extends({
|
|
@@ -2062,17 +2190,18 @@ var Expression$1 = {
|
|
|
2062
2190
|
ariaLabel
|
|
2063
2191
|
};
|
|
2064
2192
|
},
|
|
2065
|
-
version:
|
|
2066
|
-
|
|
2067
|
-
minor: 0
|
|
2068
|
-
},
|
|
2069
|
-
propUpgrades: propUpgrades$2,
|
|
2193
|
+
version: perseusCore.expressionLogic.version,
|
|
2194
|
+
propUpgrades: perseusCore.expressionLogic.widgetOptionsUpgrades,
|
|
2070
2195
|
// For use by the editor
|
|
2071
2196
|
isLintable: true,
|
|
2072
2197
|
// TODO(LEMS-2656): remove TS suppression
|
|
2073
2198
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2074
2199
|
scorer: perseusScore.scoreExpression,
|
|
2075
2200
|
// TODO(LEMS-2656): remove TS suppression
|
|
2201
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2202
|
+
validator: perseusScore.validateExpression,
|
|
2203
|
+
getPublicWidgetOptions: perseusCore.getExpressionPublicWidgetOptions,
|
|
2204
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
2076
2205
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2077
2206
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
2078
2207
|
const correctAnswers = (rubric.answerForms || []).filter(answerForm => answerForm.considered === "correct");
|
|
@@ -17580,7 +17709,7 @@ const inlineNodeTypes = {
|
|
|
17580
17709
|
|
|
17581
17710
|
// @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'.
|
|
17582
17711
|
const builtParser$1 = SimpleMarkdown__default["default"].parserFor(rules$1);
|
|
17583
|
-
const parse$
|
|
17712
|
+
const parse$3 = (source, state) => {
|
|
17584
17713
|
const paragraphedSource = source + "\n\n";
|
|
17585
17714
|
return builtParser$1(paragraphedSource, {
|
|
17586
17715
|
...state,
|
|
@@ -17665,14 +17794,14 @@ const getContent = ast => {
|
|
|
17665
17794
|
*/
|
|
17666
17795
|
const characterCount = source => {
|
|
17667
17796
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
17668
|
-
const ast = parse$
|
|
17797
|
+
const ast = parse$3(source);
|
|
17669
17798
|
const content = getContent(ast).join("");
|
|
17670
17799
|
return content.length;
|
|
17671
17800
|
};
|
|
17672
17801
|
var PerseusMarkdown = {
|
|
17673
17802
|
characterCount: characterCount,
|
|
17674
17803
|
traverseContent: traverseContent,
|
|
17675
|
-
parse: parse$
|
|
17804
|
+
parse: parse$3,
|
|
17676
17805
|
parseInline: inlineParser,
|
|
17677
17806
|
reactFor: SimpleMarkdown__default["default"].reactFor,
|
|
17678
17807
|
ruleOutput: SimpleMarkdown__default["default"].ruleOutput(rules$1, "react"),
|
|
@@ -17813,136 +17942,6 @@ guess, state) {
|
|
|
17813
17942
|
});
|
|
17814
17943
|
}
|
|
17815
17944
|
|
|
17816
|
-
// These are all the current widgets that require user-input and are supported for automatic scoring
|
|
17817
|
-
const QUESTION_WIDGETS = ["numeric-input", "input-number", "radio", "interactive-graph", "interactive-number-line", "categorizer", "plotter", "orderer", "protractor", "matcher", "sorter"];
|
|
17818
|
-
|
|
17819
|
-
/**
|
|
17820
|
-
* Regex for widget placeholders in a string.
|
|
17821
|
-
*
|
|
17822
|
-
* First capture group is the widget ID (ex. 'radio 1')
|
|
17823
|
-
* Second capture group is the widget type (ex. "radio)
|
|
17824
|
-
* exec return will look like: ['[[☃ radio 1]]', 'radio 1', 'radio']
|
|
17825
|
-
*/
|
|
17826
|
-
function getWidgetRegex() {
|
|
17827
|
-
return /\[\[☃ ([A-Za-z0-9- ]+)\]\]/g;
|
|
17828
|
-
}
|
|
17829
|
-
|
|
17830
|
-
/**
|
|
17831
|
-
* Add a widget placeholder using the widget ID.
|
|
17832
|
-
* ex. addWidget("radio 1") => "[[☃ radio 1]]"
|
|
17833
|
-
*
|
|
17834
|
-
* @param {string} id
|
|
17835
|
-
* @returns {string}
|
|
17836
|
-
*/
|
|
17837
|
-
function addWidget(id) {
|
|
17838
|
-
return `[[☃ ${id}]]`;
|
|
17839
|
-
}
|
|
17840
|
-
|
|
17841
|
-
/**
|
|
17842
|
-
* Get a widget type by a widget's ID
|
|
17843
|
-
* (please don't derive type from ID)
|
|
17844
|
-
*
|
|
17845
|
-
* @param {string} widgetId the ID of the widget
|
|
17846
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17847
|
-
* @returns {string} the widget type (ie "radio")
|
|
17848
|
-
*/
|
|
17849
|
-
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
17850
|
-
const widget = widgetMap[WidgetId];
|
|
17851
|
-
return widget?.type ?? null;
|
|
17852
|
-
}
|
|
17853
|
-
|
|
17854
|
-
/**
|
|
17855
|
-
* Does the content have a specific type of widget?
|
|
17856
|
-
*
|
|
17857
|
-
* @param {string} type the type of the widget in question (ie "radio")
|
|
17858
|
-
* @param {string} content the string to search through
|
|
17859
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17860
|
-
* @returns {boolean} if the content includes the widget type
|
|
17861
|
-
*/
|
|
17862
|
-
function contentHasWidgetType(type, content, widgetMap) {
|
|
17863
|
-
return getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
17864
|
-
}
|
|
17865
|
-
|
|
17866
|
-
/**
|
|
17867
|
-
* Extract all widget IDs, which includes the widget type and instance number.
|
|
17868
|
-
* example output: ['radio 1', 'categorizer 1', 'categorizor 2']
|
|
17869
|
-
*
|
|
17870
|
-
* Content should contain Perseus widget placeholders,
|
|
17871
|
-
* which look like: '[[☃ radio 1]]'.
|
|
17872
|
-
*
|
|
17873
|
-
* @param {string} content
|
|
17874
|
-
* @returns {ReadonlyArray<string>} widgetIds
|
|
17875
|
-
*/
|
|
17876
|
-
function getWidgetIdsFromContent(content) {
|
|
17877
|
-
const widgets = [];
|
|
17878
|
-
const localWidgetRegex = getWidgetRegex();
|
|
17879
|
-
let match = localWidgetRegex.exec(content);
|
|
17880
|
-
while (match !== null) {
|
|
17881
|
-
widgets.push(match[1]);
|
|
17882
|
-
match = localWidgetRegex.exec(content);
|
|
17883
|
-
}
|
|
17884
|
-
return widgets;
|
|
17885
|
-
}
|
|
17886
|
-
|
|
17887
|
-
/**
|
|
17888
|
-
* Get a list of widget IDs from content,
|
|
17889
|
-
* but only for specific widget types
|
|
17890
|
-
*
|
|
17891
|
-
* @param {string} type the type of widget (ie "radio")
|
|
17892
|
-
* @param {string} content the string to parse
|
|
17893
|
-
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
17894
|
-
* @returns {ReadonlyArray<string>} the widget type (ie "radio")
|
|
17895
|
-
*/
|
|
17896
|
-
function getWidgetIdsFromContentByType(type, content, widgetMap) {
|
|
17897
|
-
const rv = [];
|
|
17898
|
-
const widgetIdsInContent = getWidgetIdsFromContent(content);
|
|
17899
|
-
widgetIdsInContent.forEach(widgetId => {
|
|
17900
|
-
const widget = widgetMap[widgetId];
|
|
17901
|
-
if (widget?.type === type) {
|
|
17902
|
-
rv.push(widgetId);
|
|
17903
|
-
}
|
|
17904
|
-
});
|
|
17905
|
-
return rv;
|
|
17906
|
-
}
|
|
17907
|
-
|
|
17908
|
-
/**
|
|
17909
|
-
* Pull the widget map out of ItemData
|
|
17910
|
-
*
|
|
17911
|
-
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
17912
|
-
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
17913
|
-
*/
|
|
17914
|
-
function getWidgetsMapFromItemData(itemData) {
|
|
17915
|
-
return itemData.question.widgets;
|
|
17916
|
-
}
|
|
17917
|
-
|
|
17918
|
-
/**
|
|
17919
|
-
* Get the widget information from a WidgetMap
|
|
17920
|
-
*
|
|
17921
|
-
* @param {string} widgetId the ID of the widget
|
|
17922
|
-
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
17923
|
-
*/
|
|
17924
|
-
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
17925
|
-
return widgetMap[widgetId] ?? null;
|
|
17926
|
-
}
|
|
17927
|
-
|
|
17928
|
-
/**
|
|
17929
|
-
* Get select widgets from a widget map.
|
|
17930
|
-
*
|
|
17931
|
-
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
17932
|
-
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
17933
|
-
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
17934
|
-
*/
|
|
17935
|
-
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
17936
|
-
const widgets = {};
|
|
17937
|
-
widgetIds.forEach(widgetId => {
|
|
17938
|
-
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
17939
|
-
if (widget) {
|
|
17940
|
-
widgets[widgetId] = widget;
|
|
17941
|
-
}
|
|
17942
|
-
});
|
|
17943
|
-
return widgets;
|
|
17944
|
-
}
|
|
17945
|
-
|
|
17946
17945
|
const DEFAULT_ALIGNMENT$1 = "block";
|
|
17947
17946
|
// NOTE(kevinb): "default" is not one in `validAlignments`.
|
|
17948
17947
|
const DEFAULT_SUPPORTED_ALIGNMENTS = ["default"];
|
|
@@ -18044,6 +18043,9 @@ const getWidget = name => {
|
|
|
18044
18043
|
const getWidgetExport = name => {
|
|
18045
18044
|
return widgets[name] ?? null;
|
|
18046
18045
|
};
|
|
18046
|
+
const getWidgetValidator = name => {
|
|
18047
|
+
return widgets[name]?.validator ?? null;
|
|
18048
|
+
};
|
|
18047
18049
|
const getWidgetScorer = name => {
|
|
18048
18050
|
return widgets[name]?.scorer ?? null;
|
|
18049
18051
|
};
|
|
@@ -18356,6 +18358,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18356
18358
|
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
18357
18359
|
getWidget: getWidget,
|
|
18358
18360
|
getWidgetExport: getWidgetExport,
|
|
18361
|
+
getWidgetValidator: getWidgetValidator,
|
|
18359
18362
|
getWidgetScorer: getWidgetScorer,
|
|
18360
18363
|
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18361
18364
|
getEditor: getEditor,
|
|
@@ -18378,7 +18381,6 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18378
18381
|
});
|
|
18379
18382
|
|
|
18380
18383
|
function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
18381
|
-
// @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; }'.
|
|
18382
18384
|
return perseusCore.mapObject(oldWidgetOptions, (widgetInfo, widgetId) => {
|
|
18383
18385
|
if (!widgetInfo.type || !widgetInfo.alignment) {
|
|
18384
18386
|
const newValues = {};
|
|
@@ -18396,22 +18398,30 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
|
|
|
18396
18398
|
...newValues
|
|
18397
18399
|
};
|
|
18398
18400
|
}
|
|
18401
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
18399
18402
|
return upgradeWidgetInfoToLatestVersion(widgetInfo);
|
|
18400
18403
|
});
|
|
18401
18404
|
}
|
|
18405
|
+
|
|
18406
|
+
/**
|
|
18407
|
+
* Checks the given user input to see if any answerable widgets have not been
|
|
18408
|
+
* "filled in" (ie. if they're empty). Another way to think about this
|
|
18409
|
+
* function is that its a check to see if we can score the provided input.
|
|
18410
|
+
*/
|
|
18402
18411
|
function emptyWidgetsFunctional(widgets,
|
|
18403
18412
|
// This is a port of old code, I'm not sure why
|
|
18404
18413
|
// we need widgetIds vs the keys of the widgets object
|
|
18405
18414
|
widgetIds, userInputMap, strings, locale) {
|
|
18406
|
-
const upgradedWidgets = getUpgradedWidgetOptions(widgets);
|
|
18407
18415
|
return widgetIds.filter(id => {
|
|
18408
|
-
const widget =
|
|
18409
|
-
if (!widget || widget.static) {
|
|
18416
|
+
const widget = widgets[id];
|
|
18417
|
+
if (!widget || widget.static === true) {
|
|
18410
18418
|
// Static widgets shouldn't count as empty
|
|
18411
18419
|
return false;
|
|
18412
18420
|
}
|
|
18413
|
-
const
|
|
18414
|
-
const
|
|
18421
|
+
const validator = getWidgetValidator(widget.type);
|
|
18422
|
+
const userInput = userInputMap[id];
|
|
18423
|
+
const validationData = widget.options;
|
|
18424
|
+
const score = validator?.(userInput, validationData, strings, locale);
|
|
18415
18425
|
if (score) {
|
|
18416
18426
|
return scoreIsEmpty(score);
|
|
18417
18427
|
}
|
|
@@ -18427,7 +18437,7 @@ strings, locale) {
|
|
|
18427
18437
|
// There seems to be a chance that PerseusRenderer.widgets might include
|
|
18428
18438
|
// widget data for widgets that are not in PerseusRenderer.content,
|
|
18429
18439
|
// so this checks that the widgets are being used before scoring them
|
|
18430
|
-
const usedWidgetIds = getWidgetIdsFromContent(perseusRenderData.content);
|
|
18440
|
+
const usedWidgetIds = perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
|
|
18431
18441
|
const scores = scoreWidgetsFunctional(perseusRenderData.widgets, usedWidgetIds, userInputMap, strings, locale);
|
|
18432
18442
|
return flattenScores(scores);
|
|
18433
18443
|
}
|
|
@@ -18450,8 +18460,12 @@ widgetIds, userInputMap, strings, locale) {
|
|
|
18450
18460
|
return;
|
|
18451
18461
|
}
|
|
18452
18462
|
const userInput = userInputMap[id];
|
|
18463
|
+
const validator = getWidgetValidator(widget.type);
|
|
18453
18464
|
const scorer = getWidgetScorer(widget.type);
|
|
18454
|
-
|
|
18465
|
+
|
|
18466
|
+
// We do validation (empty checks) first and then scoring. If
|
|
18467
|
+
// validation fails, it's result is itself a PerseusScore.
|
|
18468
|
+
const score = validator?.(userInput, widget.options, strings, locale) ?? scorer?.(userInput, widget.options, strings, locale);
|
|
18455
18469
|
if (score != null) {
|
|
18456
18470
|
widgetScores[id] = score;
|
|
18457
18471
|
}
|
|
@@ -19060,7 +19074,7 @@ class Renderer extends React__namespace.Component {
|
|
|
19060
19074
|
const apiOptions = this.getApiOptions();
|
|
19061
19075
|
const widgetProps = this.state.widgetProps[widgetId] || {};
|
|
19062
19076
|
|
|
19063
|
-
// The widget needs access to its "
|
|
19077
|
+
// The widget needs access to its "scoring data" at all times when in review
|
|
19064
19078
|
// mode (which is really just part of its widget info).
|
|
19065
19079
|
const widgetInfo = this.state.widgetInfo[widgetId];
|
|
19066
19080
|
const reviewModeRubric = this.props.reviewMode && widgetInfo ? widgetInfo.options : null;
|
|
@@ -23020,7 +23034,7 @@ const CIRCLE_LABEL_STYLE = {
|
|
|
23020
23034
|
// TODO(LEMS-2656): remove TS suppression
|
|
23021
23035
|
// @ts-expect-error: State is not assignable to ParseState
|
|
23022
23036
|
const builtParser = SimpleMarkdown__default["default"].parserFor(rules);
|
|
23023
|
-
const parse$
|
|
23037
|
+
const parse$2 = (source, state) => {
|
|
23024
23038
|
// @ts-expect-error - TS2322 - Type 'ParseState | {}' is not assignable to type 'ParseState | null | undefined'.
|
|
23025
23039
|
state = state || {};
|
|
23026
23040
|
const paragraphedSource = source + "\n\n";
|
|
@@ -23030,7 +23044,7 @@ const parse$3 = (source, state) => {
|
|
|
23030
23044
|
// @ts-expect-error - TS2322 - Type 'ReactOutput' is not assignable to type '(arg1: SingleASTNode[]) => ReactElement<any, string | JSXElementConstructor<any>>'.
|
|
23031
23045
|
const output = SimpleMarkdown__default["default"].reactFor(SimpleMarkdown__default["default"].ruleOutput(rules, "react"));
|
|
23032
23046
|
var PassageMarkdown = {
|
|
23033
|
-
parse: parse$
|
|
23047
|
+
parse: parse$2,
|
|
23034
23048
|
output,
|
|
23035
23049
|
START_REF_PREFIX,
|
|
23036
23050
|
END_REF_PREFIX,
|
|
@@ -23585,10 +23599,7 @@ var PassageRef$1 = {
|
|
|
23585
23599
|
referenceNumber: widgetOptions.referenceNumber,
|
|
23586
23600
|
summaryText: widgetOptions.summaryText
|
|
23587
23601
|
}),
|
|
23588
|
-
version:
|
|
23589
|
-
major: 0,
|
|
23590
|
-
minor: 1
|
|
23591
|
-
},
|
|
23602
|
+
version: perseusCore.passageRefLogic.version,
|
|
23592
23603
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
23593
23604
|
scorer: () => scoreNoop()
|
|
23594
23605
|
};
|
|
@@ -25274,7 +25285,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25274
25285
|
// Current versions of the radio widget always pass in the
|
|
25275
25286
|
// "correct" value through the choices. Old serialized state
|
|
25276
25287
|
// for radio widgets doesn't have this though, so we have to
|
|
25277
|
-
// pull the correctness out of the review mode
|
|
25288
|
+
// pull the correctness out of the review mode scoring data. This
|
|
25278
25289
|
// only works because all of the places we use
|
|
25279
25290
|
// `restoreSerializedState()` also turn on reviewMode, but is
|
|
25280
25291
|
// fine for now.
|
|
@@ -25398,21 +25409,6 @@ const transform = (widgetOptions, strings, problemNum) => {
|
|
|
25398
25409
|
selectedChoices: ___default["default"].pluck(choices, "correct")
|
|
25399
25410
|
};
|
|
25400
25411
|
};
|
|
25401
|
-
const propUpgrades$1 = {
|
|
25402
|
-
"1": v0props => {
|
|
25403
|
-
const {
|
|
25404
|
-
noneOfTheAbove,
|
|
25405
|
-
...rest
|
|
25406
|
-
} = v0props;
|
|
25407
|
-
if (noneOfTheAbove) {
|
|
25408
|
-
throw new Error("radio widget v0 no longer supports auto noneOfTheAbove");
|
|
25409
|
-
}
|
|
25410
|
-
return {
|
|
25411
|
-
...rest,
|
|
25412
|
-
hasNoneOfTheAbove: false
|
|
25413
|
-
};
|
|
25414
|
-
}
|
|
25415
|
-
};
|
|
25416
25412
|
var Radio = {
|
|
25417
25413
|
name: "radio",
|
|
25418
25414
|
displayName: "Radio / Multiple choice",
|
|
@@ -25420,15 +25416,15 @@ var Radio = {
|
|
|
25420
25416
|
widget: Radio$1,
|
|
25421
25417
|
transform: transform,
|
|
25422
25418
|
staticTransform: transform,
|
|
25423
|
-
version:
|
|
25424
|
-
|
|
25425
|
-
minor: 0
|
|
25426
|
-
},
|
|
25427
|
-
propUpgrades: propUpgrades$1,
|
|
25419
|
+
version: perseusCore.radioLogic.version,
|
|
25420
|
+
propUpgrades: perseusCore.radioLogic.widgetOptionsUpgrades,
|
|
25428
25421
|
isLintable: true,
|
|
25429
25422
|
// TODO(LEMS-2656): remove TS suppression
|
|
25430
25423
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25431
|
-
scorer: perseusScore.scoreRadio
|
|
25424
|
+
scorer: perseusScore.scoreRadio,
|
|
25425
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25426
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
25427
|
+
validator: perseusScore.validateRadio
|
|
25432
25428
|
};
|
|
25433
25429
|
|
|
25434
25430
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -25447,24 +25443,6 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
25447
25443
|
};
|
|
25448
25444
|
};
|
|
25449
25445
|
|
|
25450
|
-
/**
|
|
25451
|
-
* For details on the individual options, see the
|
|
25452
|
-
* PerseusCategorizerWidgetOptions type
|
|
25453
|
-
*/
|
|
25454
|
-
|
|
25455
|
-
/**
|
|
25456
|
-
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
25457
|
-
* the public options that should be exposed to the client.
|
|
25458
|
-
*/
|
|
25459
|
-
function getCategorizerPublicWidgetOptions(options) {
|
|
25460
|
-
return {
|
|
25461
|
-
items: options.items,
|
|
25462
|
-
categories: options.categories,
|
|
25463
|
-
randomizeItems: options.randomizeItems,
|
|
25464
|
-
static: options.static
|
|
25465
|
-
};
|
|
25466
|
-
}
|
|
25467
|
-
|
|
25468
25446
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
25469
25447
|
class Categorizer extends React__namespace.Component {
|
|
25470
25448
|
static contextType = PerseusI18nContext;
|
|
@@ -25647,7 +25625,10 @@ var Categorizer$1 = {
|
|
|
25647
25625
|
// TODO(LEMS-2656): remove TS suppression
|
|
25648
25626
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25649
25627
|
scorer: perseusScore.scoreCategorizer,
|
|
25650
|
-
|
|
25628
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
25629
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
25630
|
+
validator: perseusScore.validateCategorizer,
|
|
25631
|
+
getPublicWidgetOptions: perseusCore.getCategorizerPublicWidgetOptions
|
|
25651
25632
|
};
|
|
25652
25633
|
|
|
25653
25634
|
/**
|
|
@@ -26086,7 +26067,10 @@ var Dropdown$1 = {
|
|
|
26086
26067
|
transform: optionsTransform,
|
|
26087
26068
|
// TODO(LEMS-2656): remove TS suppression
|
|
26088
26069
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
26089
|
-
scorer: perseusScore.scoreDropdown
|
|
26070
|
+
scorer: perseusScore.scoreDropdown,
|
|
26071
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
26072
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
26073
|
+
validator: perseusScore.validateDropdown
|
|
26090
26074
|
};
|
|
26091
26075
|
|
|
26092
26076
|
const getPromptJSON$j = renderProps => {
|
|
@@ -27604,11 +27588,22 @@ const getPromptJSON$f = rendererJSON => {
|
|
|
27604
27588
|
|
|
27605
27589
|
// The `group` widget is basically a widget hosting a full Perseus system in
|
|
27606
27590
|
// it. As such, scoring a group means scoring all widgets it contains.
|
|
27607
|
-
function scoreGroup(userInput,
|
|
27608
|
-
const scores = scoreWidgetsFunctional(
|
|
27591
|
+
function scoreGroup(userInput, rubric, strings, locale) {
|
|
27592
|
+
const scores = scoreWidgetsFunctional(rubric.widgets, Object.keys(rubric.widgets), userInput, strings, locale);
|
|
27609
27593
|
return flattenScores(scores);
|
|
27610
27594
|
}
|
|
27611
27595
|
|
|
27596
|
+
function validateGroup(userInput, validationData, strings, locale) {
|
|
27597
|
+
const emptyWidgets = emptyWidgetsFunctional(validationData.widgets, Object.keys(validationData.widgets), userInput, strings, locale);
|
|
27598
|
+
if (emptyWidgets.length === 0) {
|
|
27599
|
+
return null;
|
|
27600
|
+
}
|
|
27601
|
+
return {
|
|
27602
|
+
type: "invalid",
|
|
27603
|
+
message: null
|
|
27604
|
+
};
|
|
27605
|
+
}
|
|
27606
|
+
|
|
27612
27607
|
// exports has no 'transform'
|
|
27613
27608
|
|
|
27614
27609
|
class Group extends React__namespace.Component {
|
|
@@ -27761,8 +27756,11 @@ var Group$1 = {
|
|
|
27761
27756
|
widget: Group,
|
|
27762
27757
|
traverseChildWidgets: traverseChildWidgets,
|
|
27763
27758
|
// TODO(LEMS-2656): remove TS suppression
|
|
27764
|
-
// @ts-expect-error: Type 'UserInput' is not assignable to type '
|
|
27759
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27765
27760
|
scorer: scoreGroup,
|
|
27761
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
27762
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGroupUserInput'.
|
|
27763
|
+
validator: validateGroup,
|
|
27766
27764
|
hidden: true,
|
|
27767
27765
|
isLintable: true
|
|
27768
27766
|
};
|
|
@@ -30682,6 +30680,96 @@ function getInterceptStringForLine(line, strings, locale) {
|
|
|
30682
30680
|
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30683
30681
|
});
|
|
30684
30682
|
}
|
|
30683
|
+
function getCoordQuadrant(coord) {
|
|
30684
|
+
const [unroundedX, unroundedY] = coord;
|
|
30685
|
+
const x = Number(unroundedX.toFixed(3));
|
|
30686
|
+
const y = Number(unroundedY.toFixed(3));
|
|
30687
|
+
if (x === 0 && y === 0) {
|
|
30688
|
+
return "origin";
|
|
30689
|
+
}
|
|
30690
|
+
if (y === 0) {
|
|
30691
|
+
return "x-axis";
|
|
30692
|
+
}
|
|
30693
|
+
if (x === 0) {
|
|
30694
|
+
return "y-axis";
|
|
30695
|
+
}
|
|
30696
|
+
if (x > 0 && y > 0) {
|
|
30697
|
+
return 1;
|
|
30698
|
+
}
|
|
30699
|
+
if (x < 0 && y > 0) {
|
|
30700
|
+
return 2;
|
|
30701
|
+
}
|
|
30702
|
+
if (x < 0 && y < 0) {
|
|
30703
|
+
return 3;
|
|
30704
|
+
}
|
|
30705
|
+
return 4;
|
|
30706
|
+
}
|
|
30707
|
+
function getQuadraticVertexString(vertex, strings) {
|
|
30708
|
+
const location = getCoordQuadrant(vertex);
|
|
30709
|
+
switch (location) {
|
|
30710
|
+
case "origin":
|
|
30711
|
+
return strings.srQuadraticGraphVertexOrigin;
|
|
30712
|
+
case "x-axis":
|
|
30713
|
+
return strings.srQuadraticGraphVertexXAxis;
|
|
30714
|
+
case "y-axis":
|
|
30715
|
+
return strings.srQuadraticGraphVertexYAxis;
|
|
30716
|
+
default:
|
|
30717
|
+
return strings.srQuadraticGraphVertexQuadrant({
|
|
30718
|
+
quadrant: location
|
|
30719
|
+
});
|
|
30720
|
+
}
|
|
30721
|
+
}
|
|
30722
|
+
function getQuadraticPointString(pointNumber, coord, strings, locale) {
|
|
30723
|
+
const location = getCoordQuadrant(coord);
|
|
30724
|
+
const [x, y] = coord;
|
|
30725
|
+
switch (location) {
|
|
30726
|
+
case "origin":
|
|
30727
|
+
return strings.srQuadraticPointOrigin({
|
|
30728
|
+
pointNumber: pointNumber
|
|
30729
|
+
});
|
|
30730
|
+
case "x-axis":
|
|
30731
|
+
case "y-axis":
|
|
30732
|
+
return strings.srQuadraticPointAxis({
|
|
30733
|
+
pointNumber: pointNumber,
|
|
30734
|
+
x: srFormatNumber(x, locale),
|
|
30735
|
+
y: srFormatNumber(y, locale)
|
|
30736
|
+
});
|
|
30737
|
+
default:
|
|
30738
|
+
// return `Point ${pointNumber} on parabola in quadrant ${location} at ${srFormatNumber(x, locale)} comma ${srFormatNumber(y, locale)}.`;
|
|
30739
|
+
return strings.srQuadraticPointQuadrant({
|
|
30740
|
+
pointNumber: pointNumber,
|
|
30741
|
+
quadrant: location,
|
|
30742
|
+
x: srFormatNumber(x, locale),
|
|
30743
|
+
y: srFormatNumber(y, locale)
|
|
30744
|
+
});
|
|
30745
|
+
}
|
|
30746
|
+
}
|
|
30747
|
+
function getQuadraticXIntercepts(
|
|
30748
|
+
// Coefficients of the quadratic equation
|
|
30749
|
+
a, b, c) {
|
|
30750
|
+
// If a is 0, the equation is a line and has maybe only one x-intercept.
|
|
30751
|
+
if (a === 0) {
|
|
30752
|
+
// If b is 0, the equation a horizontal line and has no x-intercepts.
|
|
30753
|
+
if (b === 0) {
|
|
30754
|
+
return [];
|
|
30755
|
+
}
|
|
30756
|
+
return [-c / b];
|
|
30757
|
+
}
|
|
30758
|
+
|
|
30759
|
+
// Find the x-intercepts by plugging in y = 0 into the quadratic equation.
|
|
30760
|
+
// 0 = ax^2 + bx + c
|
|
30761
|
+
// x = (-b ± sqrt(b^2 - 4ac)) / 2a
|
|
30762
|
+
const discriminant = b * b - 4 * a * c;
|
|
30763
|
+
if (discriminant < 0) {
|
|
30764
|
+
return [];
|
|
30765
|
+
}
|
|
30766
|
+
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
|
|
30767
|
+
const x2 = (-b - Math.sqrt(discriminant)) / (2 * a);
|
|
30768
|
+
if (x1 === x2) {
|
|
30769
|
+
return [x1];
|
|
30770
|
+
}
|
|
30771
|
+
return [x1, x2];
|
|
30772
|
+
}
|
|
30685
30773
|
|
|
30686
30774
|
const {
|
|
30687
30775
|
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
@@ -32519,7 +32607,9 @@ function renderLinearGraph(state, dispatch) {
|
|
|
32519
32607
|
graphState: state,
|
|
32520
32608
|
dispatch: dispatch
|
|
32521
32609
|
}),
|
|
32522
|
-
interactiveElementsDescription:
|
|
32610
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearGraphDescription, {
|
|
32611
|
+
state: state
|
|
32612
|
+
})
|
|
32523
32613
|
};
|
|
32524
32614
|
}
|
|
32525
32615
|
const LinearGraph = (props, key) => {
|
|
@@ -32538,33 +32628,29 @@ const LinearGraph = (props, key) => {
|
|
|
32538
32628
|
const interceptDescriptionId = id + "-intercept";
|
|
32539
32629
|
const slopeDescriptionId = id + "-slope";
|
|
32540
32630
|
|
|
32541
|
-
// Aria
|
|
32542
|
-
const
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32547
|
-
|
|
32548
|
-
|
|
32549
|
-
|
|
32550
|
-
|
|
32551
|
-
point2X: srFormatNumber(line[1][0], locale),
|
|
32552
|
-
point2Y: srFormatNumber(line[1][1], locale)
|
|
32631
|
+
// Aria strings
|
|
32632
|
+
const {
|
|
32633
|
+
srLinearGraph,
|
|
32634
|
+
srLinearGraphPoints,
|
|
32635
|
+
srLinearGrabHandle,
|
|
32636
|
+
slopeString,
|
|
32637
|
+
interceptString
|
|
32638
|
+
} = describeLinearGraph(props.graphState, {
|
|
32639
|
+
strings,
|
|
32640
|
+
locale
|
|
32553
32641
|
});
|
|
32554
|
-
const slopeString = getSlopeStringForLine(line, strings);
|
|
32555
|
-
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32556
32642
|
|
|
32557
32643
|
// Linear graphs only have one line
|
|
32558
32644
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
32559
32645
|
// and another action for moving multiple lines
|
|
32560
32646
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32561
32647
|
// Outer line minimal description
|
|
32562
|
-
"aria-label":
|
|
32648
|
+
"aria-label": srLinearGraph,
|
|
32563
32649
|
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
32564
32650
|
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
32565
32651
|
key: 0,
|
|
32566
32652
|
ariaLabels: {
|
|
32567
|
-
grabHandleAriaLabel:
|
|
32653
|
+
grabHandleAriaLabel: srLinearGrabHandle
|
|
32568
32654
|
},
|
|
32569
32655
|
ariaDescribedBy: `${interceptDescriptionId} ${slopeDescriptionId}`,
|
|
32570
32656
|
points: line,
|
|
@@ -32580,7 +32666,7 @@ const LinearGraph = (props, key) => {
|
|
|
32580
32666
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32581
32667
|
id: pointsDescriptionId,
|
|
32582
32668
|
style: a11y.srOnly
|
|
32583
|
-
},
|
|
32669
|
+
}, srLinearGraphPoints), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32584
32670
|
id: interceptDescriptionId,
|
|
32585
32671
|
style: a11y.srOnly
|
|
32586
32672
|
}, interceptString), /*#__PURE__*/React__namespace.createElement("g", {
|
|
@@ -32588,6 +32674,56 @@ const LinearGraph = (props, key) => {
|
|
|
32588
32674
|
style: a11y.srOnly
|
|
32589
32675
|
}, slopeString));
|
|
32590
32676
|
};
|
|
32677
|
+
function LinearGraphDescription(_ref) {
|
|
32678
|
+
let {
|
|
32679
|
+
state
|
|
32680
|
+
} = _ref;
|
|
32681
|
+
// The reason that LinearGraphDescription is a component (rather than a
|
|
32682
|
+
// function that returns a string) is because it needs to use a
|
|
32683
|
+
// hook: `usePerseusI18n`.
|
|
32684
|
+
const i18n = usePerseusI18n();
|
|
32685
|
+
const strings = describeLinearGraph(state, i18n);
|
|
32686
|
+
return strings.srLinearInteractiveElement;
|
|
32687
|
+
}
|
|
32688
|
+
|
|
32689
|
+
// Exported for testing
|
|
32690
|
+
function describeLinearGraph(state, i18n) {
|
|
32691
|
+
const {
|
|
32692
|
+
coords: line
|
|
32693
|
+
} = state;
|
|
32694
|
+
const {
|
|
32695
|
+
strings,
|
|
32696
|
+
locale
|
|
32697
|
+
} = i18n;
|
|
32698
|
+
|
|
32699
|
+
// Aria label strings
|
|
32700
|
+
const srLinearGraph = strings.srLinearGraph;
|
|
32701
|
+
const srLinearGraphPoints = strings.srLinearGraphPoints({
|
|
32702
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32703
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32704
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32705
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32706
|
+
});
|
|
32707
|
+
const srLinearGrabHandle = strings.srLinearGrabHandle({
|
|
32708
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32709
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32710
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32711
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32712
|
+
});
|
|
32713
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32714
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
32715
|
+
const srLinearInteractiveElement = strings.srInteractiveElements({
|
|
32716
|
+
elements: [srLinearGraph, srLinearGraphPoints].join(" ")
|
|
32717
|
+
});
|
|
32718
|
+
return {
|
|
32719
|
+
srLinearGraph,
|
|
32720
|
+
srLinearGraphPoints,
|
|
32721
|
+
srLinearGrabHandle,
|
|
32722
|
+
slopeString,
|
|
32723
|
+
interceptString,
|
|
32724
|
+
srLinearInteractiveElement
|
|
32725
|
+
};
|
|
32726
|
+
}
|
|
32591
32727
|
|
|
32592
32728
|
function renderLinearSystemGraph(state, dispatch) {
|
|
32593
32729
|
return {
|
|
@@ -33146,13 +33282,13 @@ const hasFocusVisible = element => {
|
|
|
33146
33282
|
}
|
|
33147
33283
|
};
|
|
33148
33284
|
|
|
33149
|
-
function renderQuadraticGraph(state, dispatch) {
|
|
33285
|
+
function renderQuadraticGraph(state, dispatch, i18n) {
|
|
33150
33286
|
return {
|
|
33151
33287
|
graph: /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
|
|
33152
33288
|
graphState: state,
|
|
33153
33289
|
dispatch: dispatch
|
|
33154
33290
|
}),
|
|
33155
|
-
interactiveElementsDescription:
|
|
33291
|
+
interactiveElementsDescription: getQuadraticGraphDescription(state, i18n)
|
|
33156
33292
|
};
|
|
33157
33293
|
}
|
|
33158
33294
|
function QuadraticGraph(props) {
|
|
@@ -33163,6 +33299,14 @@ function QuadraticGraph(props) {
|
|
|
33163
33299
|
const {
|
|
33164
33300
|
coords
|
|
33165
33301
|
} = graphState;
|
|
33302
|
+
const {
|
|
33303
|
+
strings,
|
|
33304
|
+
locale
|
|
33305
|
+
} = usePerseusI18n();
|
|
33306
|
+
const id = React__namespace.useId();
|
|
33307
|
+
const quadraticDirectionId = id + "-direction";
|
|
33308
|
+
const quadraticVertexId = id + "-vertex";
|
|
33309
|
+
const quadraticInterceptsId = id + "-intercepts";
|
|
33166
33310
|
|
|
33167
33311
|
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
33168
33312
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
@@ -33178,15 +33322,46 @@ function QuadraticGraph(props) {
|
|
|
33178
33322
|
|
|
33179
33323
|
// Calculate the y value based on the current x value
|
|
33180
33324
|
const y = x => (a * x + b) * x + c;
|
|
33181
|
-
|
|
33325
|
+
|
|
33326
|
+
// Aria strings
|
|
33327
|
+
const {
|
|
33328
|
+
srQuadraticGraph,
|
|
33329
|
+
srQuadraticDirection,
|
|
33330
|
+
srQuadraticVertex,
|
|
33331
|
+
srQuadraticXIntercepts,
|
|
33332
|
+
srQuadraticYIntercept
|
|
33333
|
+
} = describeQuadraticGraph(graphState, {
|
|
33334
|
+
strings,
|
|
33335
|
+
locale
|
|
33336
|
+
});
|
|
33337
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33338
|
+
// Outer graph minimal description
|
|
33339
|
+
"aria-label": srQuadraticGraph,
|
|
33340
|
+
"aria-describedby": `${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`
|
|
33341
|
+
}, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
33182
33342
|
y: y,
|
|
33183
33343
|
color: wonderBlocksTokens.color.blue
|
|
33184
|
-
}), coords.map((coord, i) =>
|
|
33185
|
-
|
|
33186
|
-
|
|
33187
|
-
|
|
33188
|
-
|
|
33189
|
-
|
|
33344
|
+
}), coords.map((coord, i) => {
|
|
33345
|
+
const srQuadraticPoint = getQuadraticPointString(i + 1, coord, strings, locale);
|
|
33346
|
+
// Vertex might not exist if the quadratic graph is a line
|
|
33347
|
+
const srVertex = srQuadraticVertex ? ` ${srQuadraticVertex}` : "";
|
|
33348
|
+
return /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33349
|
+
key: "point-" + i,
|
|
33350
|
+
ariaLabel: `${srQuadraticPoint}${srVertex}`,
|
|
33351
|
+
point: coord,
|
|
33352
|
+
sequenceNumber: i + 1,
|
|
33353
|
+
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
33354
|
+
});
|
|
33355
|
+
}), srQuadraticDirection && /*#__PURE__*/React__namespace.createElement("g", {
|
|
33356
|
+
id: quadraticDirectionId,
|
|
33357
|
+
style: a11y.srOnly
|
|
33358
|
+
}, srQuadraticDirection), srQuadraticVertex && /*#__PURE__*/React__namespace.createElement("g", {
|
|
33359
|
+
id: quadraticVertexId,
|
|
33360
|
+
style: a11y.srOnly
|
|
33361
|
+
}, srQuadraticVertex), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33362
|
+
id: quadraticInterceptsId,
|
|
33363
|
+
style: a11y.srOnly
|
|
33364
|
+
}, srQuadraticXIntercepts ? `${srQuadraticXIntercepts} ${srQuadraticYIntercept}` : `${srQuadraticYIntercept}`));
|
|
33190
33365
|
}
|
|
33191
33366
|
|
|
33192
33367
|
// Get the quadratic coefficients from the 3 control points
|
|
@@ -33209,6 +33384,60 @@ const getQuadraticCoefficients$2 = coords => {
|
|
|
33209
33384
|
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;
|
|
33210
33385
|
return [a, b, c];
|
|
33211
33386
|
};
|
|
33387
|
+
function getQuadraticGraphDescription(state, i18n) {
|
|
33388
|
+
const strings = describeQuadraticGraph(state, i18n);
|
|
33389
|
+
return strings.srQuadraticInteractiveElements;
|
|
33390
|
+
}
|
|
33391
|
+
// Exported for testing
|
|
33392
|
+
function describeQuadraticGraph(state, i18n) {
|
|
33393
|
+
const {
|
|
33394
|
+
strings,
|
|
33395
|
+
locale
|
|
33396
|
+
} = i18n;
|
|
33397
|
+
const coeffs = getQuadraticCoefficients$2(state.coords);
|
|
33398
|
+
const [a, b, c] = coeffs ?? [0, 0, 0];
|
|
33399
|
+
const vertex = [-b / (2 * a), c - b * b / (4 * a)];
|
|
33400
|
+
const xIntercepts = getQuadraticXIntercepts(a, b, c);
|
|
33401
|
+
|
|
33402
|
+
// Aria label strings
|
|
33403
|
+
const srQuadraticGraph = strings.srQuadraticGraph;
|
|
33404
|
+
const srQuadraticFaceUp = strings.srQuadraticFaceUp;
|
|
33405
|
+
const srQuadraticFaceDown = strings.srQuadraticFaceDown;
|
|
33406
|
+
// The graph only has a direction if it is not a line.
|
|
33407
|
+
const srQuadraticDirection = a === 0 ? undefined : a > 0 ? srQuadraticFaceUp : srQuadraticFaceDown;
|
|
33408
|
+
// Only describe vertex if the quadratic graph is not a line.
|
|
33409
|
+
// (Undefined means the quadratic graph is a line and has no vertex.)
|
|
33410
|
+
const srQuadraticVertex = a !== 0 ? getQuadraticVertexString(vertex, strings) : undefined;
|
|
33411
|
+
// Undefined means the quadratic graph has no x-intercepts,
|
|
33412
|
+
// such as when the graph is a horizontal line.
|
|
33413
|
+
const srQuadraticXIntercepts = xIntercepts.length === 2 ? strings.srQuadraticTwoXIntercepts({
|
|
33414
|
+
intercept1: srFormatNumber(xIntercepts[0], locale),
|
|
33415
|
+
intercept2: srFormatNumber(xIntercepts[1], locale)
|
|
33416
|
+
}) : xIntercepts.length === 1 ? strings.srQuadraticOneXIntercept({
|
|
33417
|
+
intercept: srFormatNumber(xIntercepts[0], locale)
|
|
33418
|
+
}) : undefined;
|
|
33419
|
+
const srQuadraticYIntercept = strings.srQuadraticYIntercept({
|
|
33420
|
+
intercept: srFormatNumber(c, locale)
|
|
33421
|
+
});
|
|
33422
|
+
const srQuadraticInteractiveElements = strings.srInteractiveElements({
|
|
33423
|
+
elements: strings.srQuadraticInteractiveElements({
|
|
33424
|
+
point1X: srFormatNumber(state.coords[0][0], locale),
|
|
33425
|
+
point1Y: srFormatNumber(state.coords[0][1], locale),
|
|
33426
|
+
point2X: srFormatNumber(state.coords[1][0], locale),
|
|
33427
|
+
point2Y: srFormatNumber(state.coords[1][1], locale),
|
|
33428
|
+
point3X: srFormatNumber(state.coords[2][0], locale),
|
|
33429
|
+
point3Y: srFormatNumber(state.coords[2][1], locale)
|
|
33430
|
+
})
|
|
33431
|
+
});
|
|
33432
|
+
return {
|
|
33433
|
+
srQuadraticGraph,
|
|
33434
|
+
srQuadraticDirection,
|
|
33435
|
+
srQuadraticVertex,
|
|
33436
|
+
srQuadraticXIntercepts,
|
|
33437
|
+
srQuadraticYIntercept,
|
|
33438
|
+
srQuadraticInteractiveElements
|
|
33439
|
+
};
|
|
33440
|
+
}
|
|
33212
33441
|
|
|
33213
33442
|
function renderRayGraph(state, dispatch) {
|
|
33214
33443
|
return {
|
|
@@ -33335,17 +33564,71 @@ function renderSegmentGraph(state, dispatch) {
|
|
|
33335
33564
|
graphState: state,
|
|
33336
33565
|
dispatch: dispatch
|
|
33337
33566
|
}),
|
|
33338
|
-
interactiveElementsDescription:
|
|
33567
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(SegmentGraphDescription, {
|
|
33568
|
+
state: state
|
|
33569
|
+
})
|
|
33339
33570
|
};
|
|
33340
33571
|
}
|
|
33341
|
-
const SegmentGraph =
|
|
33342
|
-
|
|
33343
|
-
dispatch
|
|
33344
|
-
|
|
33572
|
+
const SegmentGraph = _ref => {
|
|
33573
|
+
let {
|
|
33574
|
+
dispatch,
|
|
33575
|
+
graphState
|
|
33576
|
+
} = _ref;
|
|
33345
33577
|
const {
|
|
33346
33578
|
coords: segments
|
|
33347
|
-
} =
|
|
33348
|
-
|
|
33579
|
+
} = graphState;
|
|
33580
|
+
const {
|
|
33581
|
+
strings,
|
|
33582
|
+
locale
|
|
33583
|
+
} = usePerseusI18n();
|
|
33584
|
+
const segmentUniqueId = React__namespace.useId();
|
|
33585
|
+
const lengthDescriptionId = segmentUniqueId + "-length";
|
|
33586
|
+
const wholeGraphDescriptionId = segmentUniqueId + "-whole-graph";
|
|
33587
|
+
function getWholeSegmentGraphAriaLabel() {
|
|
33588
|
+
return segments?.length > 1 ? strings.srMultipleSegmentGraphAriaLabel({
|
|
33589
|
+
countOfSegments: segments.length
|
|
33590
|
+
}) : strings.srSingleSegmentGraphAriaLabel;
|
|
33591
|
+
}
|
|
33592
|
+
const wholeSegmentGraphAriaLabel = getWholeSegmentGraphAriaLabel();
|
|
33593
|
+
function getIndividualSegmentAriaLabel(segment, index) {
|
|
33594
|
+
if (segments.length === 1) {
|
|
33595
|
+
return strings.srSingleSegmentLabel({
|
|
33596
|
+
point1X: srFormatNumber(segments[0][0][X], locale),
|
|
33597
|
+
point1Y: srFormatNumber(segments[0][0][Y], locale),
|
|
33598
|
+
point2X: srFormatNumber(segments[0][1][X], locale),
|
|
33599
|
+
point2Y: srFormatNumber(segments[0][1][Y], locale)
|
|
33600
|
+
});
|
|
33601
|
+
}
|
|
33602
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
33603
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33604
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33605
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33606
|
+
point2Y: srFormatNumber(segment[1][Y], locale),
|
|
33607
|
+
indexOfSegment: index + 1
|
|
33608
|
+
});
|
|
33609
|
+
}
|
|
33610
|
+
function getWholeSegmentGraphAriaDescription() {
|
|
33611
|
+
return segments.map((segment, index) => getIndividualSegmentAriaLabel(segment, index)).join(" ");
|
|
33612
|
+
}
|
|
33613
|
+
function formatSegment(endpointNumber, x, y, index) {
|
|
33614
|
+
const segObj = {
|
|
33615
|
+
endpointNumber: endpointNumber,
|
|
33616
|
+
x: srFormatNumber(x, locale),
|
|
33617
|
+
y: srFormatNumber(y, locale)
|
|
33618
|
+
};
|
|
33619
|
+
return segments.length > 1 ? strings.srMultipleSegmentGraphEndpointAriaLabel({
|
|
33620
|
+
...segObj,
|
|
33621
|
+
indexOfSegment: index
|
|
33622
|
+
}) : strings.srSingleSegmentGraphEndpointAriaLabel(segObj);
|
|
33623
|
+
}
|
|
33624
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33625
|
+
"aria-label": wholeSegmentGraphAriaLabel,
|
|
33626
|
+
"aria-describedby": wholeGraphDescriptionId
|
|
33627
|
+
}, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement("g", {
|
|
33628
|
+
"aria-label": getIndividualSegmentAriaLabel(segment, i),
|
|
33629
|
+
"aria-describedby": lengthDescriptionId,
|
|
33630
|
+
key: `${segmentUniqueId}-${i}`
|
|
33631
|
+
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
33349
33632
|
key: i,
|
|
33350
33633
|
points: segment,
|
|
33351
33634
|
onMoveLine: delta => {
|
|
@@ -33353,9 +33636,61 @@ const SegmentGraph = props => {
|
|
|
33353
33636
|
},
|
|
33354
33637
|
onMovePoint: (endpointIndex, destination) => {
|
|
33355
33638
|
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
33639
|
+
},
|
|
33640
|
+
ariaLabels: {
|
|
33641
|
+
point1AriaLabel: formatSegment(1, segment[0][X], segment[0][Y], i + 1),
|
|
33642
|
+
point2AriaLabel: formatSegment(2, segment[1][X], segment[1][Y], i + 1),
|
|
33643
|
+
grabHandleAriaLabel: strings.srSegmentGrabHandle({
|
|
33644
|
+
point1X: srFormatNumber(segment[0][X], locale),
|
|
33645
|
+
point1Y: srFormatNumber(segment[0][Y], locale),
|
|
33646
|
+
point2X: srFormatNumber(segment[1][X], locale),
|
|
33647
|
+
point2Y: srFormatNumber(segment[1][Y], locale)
|
|
33648
|
+
})
|
|
33356
33649
|
}
|
|
33357
|
-
})
|
|
33650
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33651
|
+
id: lengthDescriptionId,
|
|
33652
|
+
style: a11y.srOnly
|
|
33653
|
+
}, strings.srSegmentLength({
|
|
33654
|
+
length: srFormatNumber(getLengthOfSegment(segment), locale)
|
|
33655
|
+
})))), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33656
|
+
style: a11y.srOnly,
|
|
33657
|
+
id: wholeGraphDescriptionId
|
|
33658
|
+
}, getWholeSegmentGraphAriaDescription()));
|
|
33358
33659
|
};
|
|
33660
|
+
function getLengthOfSegment(segment) {
|
|
33661
|
+
return kmath.point.distanceToPoint(...segment);
|
|
33662
|
+
}
|
|
33663
|
+
function SegmentGraphDescription(_ref2) {
|
|
33664
|
+
let {
|
|
33665
|
+
state
|
|
33666
|
+
} = _ref2;
|
|
33667
|
+
// The reason that SegmentGraphDescription is a component (rather than a
|
|
33668
|
+
// function that returns a string) is because it needs to use a
|
|
33669
|
+
// hook: `usePerseusI18n`.
|
|
33670
|
+
const i18n = usePerseusI18n();
|
|
33671
|
+
return describeSegmentGraph(state, i18n);
|
|
33672
|
+
}
|
|
33673
|
+
|
|
33674
|
+
// Exported for testing
|
|
33675
|
+
function describeSegmentGraph(state, i18n) {
|
|
33676
|
+
const {
|
|
33677
|
+
strings,
|
|
33678
|
+
locale
|
|
33679
|
+
} = i18n;
|
|
33680
|
+
const segmentDescriptions = state.coords.map((_ref3, index) => {
|
|
33681
|
+
let [point1, point2] = _ref3;
|
|
33682
|
+
return strings.srMultipleSegmentIndividualLabel({
|
|
33683
|
+
point1X: srFormatNumber(point1[X], locale),
|
|
33684
|
+
point1Y: srFormatNumber(point1[Y], locale),
|
|
33685
|
+
point2X: srFormatNumber(point2[X], locale),
|
|
33686
|
+
point2Y: srFormatNumber(point2[Y], locale),
|
|
33687
|
+
indexOfSegment: index + 1
|
|
33688
|
+
});
|
|
33689
|
+
});
|
|
33690
|
+
return strings.srInteractiveElements({
|
|
33691
|
+
elements: segmentDescriptions.join(" ")
|
|
33692
|
+
});
|
|
33693
|
+
}
|
|
33359
33694
|
|
|
33360
33695
|
function renderSinusoidGraph(state, dispatch) {
|
|
33361
33696
|
return {
|
|
@@ -33631,9 +33966,10 @@ const MafsGraph = props => {
|
|
|
33631
33966
|
x: viewboxX,
|
|
33632
33967
|
y: viewboxY
|
|
33633
33968
|
};
|
|
33969
|
+
const i18n = usePerseusI18n();
|
|
33634
33970
|
const {
|
|
33635
33971
|
strings
|
|
33636
|
-
} =
|
|
33972
|
+
} = i18n;
|
|
33637
33973
|
const interactionPrompt = isUnlimitedGraphState(state) && state.showKeyboardInteractionInvitation;
|
|
33638
33974
|
wonderBlocksCore.useOnMountEffect(() => {
|
|
33639
33975
|
analytics.onAnalyticsEvent({
|
|
@@ -33650,7 +33986,8 @@ const MafsGraph = props => {
|
|
|
33650
33986
|
interactiveElementsDescription
|
|
33651
33987
|
} = renderGraphElements({
|
|
33652
33988
|
state,
|
|
33653
|
-
dispatch
|
|
33989
|
+
dispatch,
|
|
33990
|
+
i18n
|
|
33654
33991
|
});
|
|
33655
33992
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
33656
33993
|
value: {
|
|
@@ -34027,7 +34364,8 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
34027
34364
|
const renderGraphElements = props => {
|
|
34028
34365
|
const {
|
|
34029
34366
|
state,
|
|
34030
|
-
dispatch
|
|
34367
|
+
dispatch,
|
|
34368
|
+
i18n
|
|
34031
34369
|
} = props;
|
|
34032
34370
|
const {
|
|
34033
34371
|
type
|
|
@@ -34050,7 +34388,7 @@ const renderGraphElements = props => {
|
|
|
34050
34388
|
case "circle":
|
|
34051
34389
|
return renderCircleGraph(state, dispatch);
|
|
34052
34390
|
case "quadratic":
|
|
34053
|
-
return renderQuadraticGraph(state, dispatch);
|
|
34391
|
+
return renderQuadraticGraph(state, dispatch, i18n);
|
|
34054
34392
|
case "sinusoid":
|
|
34055
34393
|
return renderSinusoidGraph(state, dispatch);
|
|
34056
34394
|
case "none":
|
|
@@ -37670,19 +38008,21 @@ class LabelImage extends React__namespace.Component {
|
|
|
37670
38008
|
this._mounted = false;
|
|
37671
38009
|
}
|
|
37672
38010
|
getUserInput() {
|
|
37673
|
-
const {
|
|
37674
|
-
markers
|
|
37675
|
-
} = this.props;
|
|
37676
38011
|
return {
|
|
37677
|
-
markers
|
|
38012
|
+
markers: this.props.markers.map(marker => ({
|
|
38013
|
+
selected: marker.selected,
|
|
38014
|
+
label: marker.label
|
|
38015
|
+
}))
|
|
37678
38016
|
};
|
|
37679
38017
|
}
|
|
37680
38018
|
getPromptJSON() {
|
|
37681
38019
|
return getPromptJSON$a(this.props, this.getUserInput());
|
|
37682
38020
|
}
|
|
37683
38021
|
|
|
37684
|
-
// TODO(LEMS-2544): Investigate impact on scoring
|
|
37685
|
-
|
|
38022
|
+
// TODO(LEMS-2544): Investigate impact on scoring
|
|
38023
|
+
// Also consider how scoreMarker is being called as it seems to require the marker.answers property.
|
|
38024
|
+
// Removed rubric parameter, but it gets a full widget options object from the renderer
|
|
38025
|
+
showRationalesForCurrentlySelectedChoices() {
|
|
37686
38026
|
const {
|
|
37687
38027
|
markers
|
|
37688
38028
|
} = this.props;
|
|
@@ -37690,7 +38030,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37690
38030
|
onChange
|
|
37691
38031
|
} = this.props;
|
|
37692
38032
|
const updatedMarkers = markers.map(marker => {
|
|
37693
|
-
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
38033
|
+
const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37694
38034
|
return {
|
|
37695
38035
|
...marker,
|
|
37696
38036
|
// Reveal correctness state for markers with answers.
|
|
@@ -37798,6 +38138,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37798
38138
|
selected: selected.length ? selected : undefined
|
|
37799
38139
|
});
|
|
37800
38140
|
}
|
|
38141
|
+
// TODO(LEMS-2723): Investigate if possible to change this to not require answers
|
|
37801
38142
|
renderMarkers() {
|
|
37802
38143
|
const {
|
|
37803
38144
|
markers,
|
|
@@ -37839,7 +38180,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
37839
38180
|
bottom: "top"
|
|
37840
38181
|
}[markerPosition];
|
|
37841
38182
|
}
|
|
37842
|
-
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
38183
|
+
const score = perseusScore.scoreLabelImageMarker(marker.selected, marker.answers);
|
|
37843
38184
|
// Once the question is answered, show markers
|
|
37844
38185
|
// with correct answers, otherwise passthrough
|
|
37845
38186
|
// the correctness state.
|
|
@@ -39384,7 +39725,10 @@ var Matrix$1 = {
|
|
|
39384
39725
|
isLintable: true,
|
|
39385
39726
|
// TODO(LEMS-2656): remove TS suppression
|
|
39386
39727
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39387
|
-
scorer: perseusScore.scoreMatrix
|
|
39728
|
+
scorer: perseusScore.scoreMatrix,
|
|
39729
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
39730
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
39731
|
+
validator: perseusScore.validateMatrix
|
|
39388
39732
|
};
|
|
39389
39733
|
|
|
39390
39734
|
const getPromptJSON$7 = () => {
|
|
@@ -39493,34 +39837,13 @@ class Measurer extends React__namespace.Component {
|
|
|
39493
39837
|
}));
|
|
39494
39838
|
}
|
|
39495
39839
|
}
|
|
39496
|
-
const propUpgrades = {
|
|
39497
|
-
"1": v0props => {
|
|
39498
|
-
const {
|
|
39499
|
-
imageUrl,
|
|
39500
|
-
imageTop,
|
|
39501
|
-
imageLeft,
|
|
39502
|
-
...rest
|
|
39503
|
-
} = v0props;
|
|
39504
|
-
return {
|
|
39505
|
-
...rest,
|
|
39506
|
-
image: {
|
|
39507
|
-
url: imageUrl,
|
|
39508
|
-
top: imageTop,
|
|
39509
|
-
left: imageLeft
|
|
39510
|
-
}
|
|
39511
|
-
};
|
|
39512
|
-
}
|
|
39513
|
-
};
|
|
39514
39840
|
var Measurer$1 = {
|
|
39515
39841
|
name: "measurer",
|
|
39516
39842
|
displayName: "Measurer",
|
|
39517
39843
|
hidden: true,
|
|
39518
39844
|
widget: Measurer,
|
|
39519
|
-
version:
|
|
39520
|
-
|
|
39521
|
-
minor: 0
|
|
39522
|
-
},
|
|
39523
|
-
propUpgrades: propUpgrades,
|
|
39845
|
+
version: perseusCore.measurerLogic.version,
|
|
39846
|
+
propUpgrades: perseusCore.measurerLogic.widgetOptionsUpgrades,
|
|
39524
39847
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
39525
39848
|
scorer: () => scoreNoop(1)
|
|
39526
39849
|
};
|
|
@@ -40095,10 +40418,10 @@ function parseBondModifier(smiles, ctx) {
|
|
|
40095
40418
|
const firstChar = smiles[0];
|
|
40096
40419
|
const rest = smiles.slice(1);
|
|
40097
40420
|
if (firstChar === "=") {
|
|
40098
|
-
return parse$
|
|
40421
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "double"));
|
|
40099
40422
|
}
|
|
40100
40423
|
if (firstChar === "#") {
|
|
40101
|
-
return parse$
|
|
40424
|
+
return parse$1(rest, _mset(ctx, ["bond", "bondType"], "triple"));
|
|
40102
40425
|
}
|
|
40103
40426
|
throw new ParseError$1("Invalid character: " + firstChar);
|
|
40104
40427
|
}
|
|
@@ -40159,8 +40482,8 @@ function parseParenthesizedExpression(smiles, ctx) {
|
|
|
40159
40482
|
idx: newCtx.idx.concat([[inBranchIdx, 0]]),
|
|
40160
40483
|
parens: newCtx.parens.concat("(")
|
|
40161
40484
|
};
|
|
40162
|
-
const parenExpr = parse$
|
|
40163
|
-
const remainder = parse$
|
|
40485
|
+
const parenExpr = parse$1(rest, parenCtx);
|
|
40486
|
+
const remainder = parse$1(sliceFromMatchingCloseParen(rest, ["("]), newCtx);
|
|
40164
40487
|
return [parenExpr].concat(remainder);
|
|
40165
40488
|
}
|
|
40166
40489
|
if (firstChar === ")") {
|
|
@@ -40224,7 +40547,7 @@ function parseAtom(smiles, ctx) {
|
|
|
40224
40547
|
|
|
40225
40548
|
// increment the atom counter and reset the branch counter
|
|
40226
40549
|
const newCtx = _mset(ctx, ["idx", ctx.idx.length - 1], [1 + ctx.idx[ctx.idx.length - 1][0], 0]);
|
|
40227
|
-
let restOfMolecule = parse$
|
|
40550
|
+
let restOfMolecule = parse$1(rest, _mset(newCtx, ["bond", "bondType"], "single"));
|
|
40228
40551
|
if (!Array.isArray(restOfMolecule) && !!restOfMolecule) {
|
|
40229
40552
|
//TODO(colin): fix this awkwardness.
|
|
40230
40553
|
restOfMolecule = [restOfMolecule];
|
|
@@ -40263,7 +40586,7 @@ function isModifierChar(s) {
|
|
|
40263
40586
|
* ParseError: if the input is not valid SMILES or contains features not
|
|
40264
40587
|
* yet implemented.
|
|
40265
40588
|
*/
|
|
40266
|
-
function parse$
|
|
40589
|
+
function parse$1(smiles, ctx) {
|
|
40267
40590
|
if (!validate(smiles)) {
|
|
40268
40591
|
throw new ParseError$1("Invalid input.");
|
|
40269
40592
|
}
|
|
@@ -40288,7 +40611,7 @@ function parse$2(smiles, ctx) {
|
|
|
40288
40611
|
return parseParenthesizedExpression(smiles, ctx);
|
|
40289
40612
|
}
|
|
40290
40613
|
var SmilesParser = {
|
|
40291
|
-
parse: parse$
|
|
40614
|
+
parse: parse$1,
|
|
40292
40615
|
ParseError: ParseError$1
|
|
40293
40616
|
};
|
|
40294
40617
|
|
|
@@ -40296,7 +40619,7 @@ var SmilesParser = {
|
|
|
40296
40619
|
const {
|
|
40297
40620
|
layout
|
|
40298
40621
|
} = MoleculeLayout;
|
|
40299
|
-
const parse
|
|
40622
|
+
const parse = SmilesParser.parse;
|
|
40300
40623
|
const ParseError = SmilesParser.ParseError;
|
|
40301
40624
|
const borderSize = 30;
|
|
40302
40625
|
class Molecule extends React__namespace.Component {
|
|
@@ -40323,7 +40646,7 @@ class Molecule extends React__namespace.Component {
|
|
|
40323
40646
|
try {
|
|
40324
40647
|
this.setState({
|
|
40325
40648
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
40326
|
-
parsedSmiles: parse
|
|
40649
|
+
parsedSmiles: parse(smiles),
|
|
40327
40650
|
error: null
|
|
40328
40651
|
});
|
|
40329
40652
|
} catch (e) {
|
|
@@ -40988,7 +41311,10 @@ var NumberLine$1 = {
|
|
|
40988
41311
|
staticTransform: staticTransform$1,
|
|
40989
41312
|
// TODO(LEMS-2656): remove TS suppression
|
|
40990
41313
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
40991
|
-
scorer: perseusScore.scoreNumberLine
|
|
41314
|
+
scorer: perseusScore.scoreNumberLine,
|
|
41315
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41316
|
+
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
41317
|
+
validator: perseusScore.validateNumberLine
|
|
40992
41318
|
};
|
|
40993
41319
|
|
|
40994
41320
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -41542,7 +41868,11 @@ var Orderer$1 = {
|
|
|
41542
41868
|
isLintable: true,
|
|
41543
41869
|
// TODO(LEMS-2656): remove TS suppression
|
|
41544
41870
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41545
|
-
scorer: perseusScore.scoreOrderer
|
|
41871
|
+
scorer: perseusScore.scoreOrderer,
|
|
41872
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
41873
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
41874
|
+
validator: perseusScore.validateOrderer,
|
|
41875
|
+
getPublicWidgetOptions: perseusCore.getOrdererPublicWidgetOptions
|
|
41546
41876
|
};
|
|
41547
41877
|
|
|
41548
41878
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -41591,10 +41921,6 @@ var PassageRefTarget$1 = {
|
|
|
41591
41921
|
transform: editorProps => {
|
|
41592
41922
|
return ___default["default"].pick(editorProps, "content");
|
|
41593
41923
|
},
|
|
41594
|
-
version: {
|
|
41595
|
-
major: 0,
|
|
41596
|
-
minor: 0
|
|
41597
|
-
},
|
|
41598
41924
|
isLintable: true,
|
|
41599
41925
|
// TODO: things that aren't interactive shouldn't need scoring functions
|
|
41600
41926
|
scorer: () => scoreNoop()
|
|
@@ -42618,7 +42944,10 @@ var Plotter$1 = {
|
|
|
42618
42944
|
staticTransform: staticTransform,
|
|
42619
42945
|
// TODO(LEMS-2656): remove TS suppression
|
|
42620
42946
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42621
|
-
scorer: perseusScore.scorePlotter
|
|
42947
|
+
scorer: perseusScore.scorePlotter,
|
|
42948
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
42949
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
42950
|
+
validator: perseusScore.validatePlotter
|
|
42622
42951
|
};
|
|
42623
42952
|
|
|
42624
42953
|
const getPromptJSON$2 = () => {
|
|
@@ -42781,7 +43110,10 @@ var Sorter$1 = {
|
|
|
42781
43110
|
isLintable: true,
|
|
42782
43111
|
// TODO(LEMS-2656): remove TS suppression
|
|
42783
43112
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
42784
|
-
scorer: perseusScore.scoreSorter
|
|
43113
|
+
scorer: perseusScore.scoreSorter,
|
|
43114
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43115
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
43116
|
+
validator: perseusScore.validateSorter
|
|
42785
43117
|
};
|
|
42786
43118
|
|
|
42787
43119
|
const {
|
|
@@ -43000,7 +43332,10 @@ var Table$1 = {
|
|
|
43000
43332
|
isLintable: true,
|
|
43001
43333
|
// TODO(LEMS-2656): remove TS suppression
|
|
43002
43334
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43003
|
-
scorer: perseusScore.scoreTable
|
|
43335
|
+
scorer: perseusScore.scoreTable,
|
|
43336
|
+
// TODO(LEMS-2656): remove TS suppression
|
|
43337
|
+
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
43338
|
+
validator: perseusScore.validateTable
|
|
43004
43339
|
};
|
|
43005
43340
|
|
|
43006
43341
|
const getPromptJSON = () => {
|
|
@@ -43185,7 +43520,7 @@ const init = function () {
|
|
|
43185
43520
|
|
|
43186
43521
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43187
43522
|
const libName = "@khanacademy/perseus";
|
|
43188
|
-
const libVersion = "
|
|
43523
|
+
const libVersion = "52.0.0";
|
|
43189
43524
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43190
43525
|
|
|
43191
43526
|
const apiVersion = {
|
|
@@ -44631,6 +44966,69 @@ const registerAllWidgetsForTesting = () => {
|
|
|
44631
44966
|
replaceDeprecatedWidgets();
|
|
44632
44967
|
};
|
|
44633
44968
|
|
|
44969
|
+
/**
|
|
44970
|
+
* Get a widget type by a widget's ID
|
|
44971
|
+
* (please don't derive type from ID)
|
|
44972
|
+
*
|
|
44973
|
+
* @param {string} widgetId the ID of the widget
|
|
44974
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44975
|
+
* @returns {string} the widget type (ie "radio")
|
|
44976
|
+
*/
|
|
44977
|
+
function getWidgetTypeByWidgetId(WidgetId, widgetMap) {
|
|
44978
|
+
const widget = widgetMap[WidgetId];
|
|
44979
|
+
return widget?.type ?? null;
|
|
44980
|
+
}
|
|
44981
|
+
|
|
44982
|
+
/**
|
|
44983
|
+
* Does the content have a specific type of widget?
|
|
44984
|
+
*
|
|
44985
|
+
* @param {string} type the type of the widget in question (ie "radio")
|
|
44986
|
+
* @param {string} content the string to search through
|
|
44987
|
+
* @param {PerseusWidgetsMap} widgetMap widget ID to widget map
|
|
44988
|
+
* @returns {boolean} if the content includes the widget type
|
|
44989
|
+
*/
|
|
44990
|
+
function contentHasWidgetType(type, content, widgetMap) {
|
|
44991
|
+
return perseusCore.getWidgetIdsFromContentByType(type, content, widgetMap).length > 0;
|
|
44992
|
+
}
|
|
44993
|
+
|
|
44994
|
+
/**
|
|
44995
|
+
* Pull the widget map out of ItemData
|
|
44996
|
+
*
|
|
44997
|
+
* @param {PerseusItem} PerseusItem containing a widgetMap
|
|
44998
|
+
* @returns {WidgetMap} the widget map in the PerseusItem
|
|
44999
|
+
*/
|
|
45000
|
+
function getWidgetsMapFromItemData(itemData) {
|
|
45001
|
+
return itemData.question.widgets;
|
|
45002
|
+
}
|
|
45003
|
+
|
|
45004
|
+
/**
|
|
45005
|
+
* Get the widget information from a WidgetMap
|
|
45006
|
+
*
|
|
45007
|
+
* @param {string} widgetId the ID of the widget
|
|
45008
|
+
* @returns {PerseusWidget | null} the widget data if it exists, otherwise null
|
|
45009
|
+
*/
|
|
45010
|
+
function getWidgetFromWidgetMap(widgetId, widgetMap) {
|
|
45011
|
+
return widgetMap[widgetId] ?? null;
|
|
45012
|
+
}
|
|
45013
|
+
|
|
45014
|
+
/**
|
|
45015
|
+
* Get select widgets from a widget map.
|
|
45016
|
+
*
|
|
45017
|
+
* @param {ReadonlyArray<string>} widgetIds to extract
|
|
45018
|
+
* @param {PerseusWidgetsMap} widgetMap to extract from
|
|
45019
|
+
* @return {PerseusWidgetsMap} a new widget map with requested widgets
|
|
45020
|
+
*/
|
|
45021
|
+
function getWidgetsFromWidgetMap(widgetIds, widgetMap) {
|
|
45022
|
+
const widgets = {};
|
|
45023
|
+
widgetIds.forEach(widgetId => {
|
|
45024
|
+
const widget = getWidgetFromWidgetMap(widgetId, widgetMap);
|
|
45025
|
+
if (widget) {
|
|
45026
|
+
widgets[widgetId] = widget;
|
|
45027
|
+
}
|
|
45028
|
+
});
|
|
45029
|
+
return widgets;
|
|
45030
|
+
}
|
|
45031
|
+
|
|
44634
45032
|
/**
|
|
44635
45033
|
* This function extracts the answers from the widgets.
|
|
44636
45034
|
*
|
|
@@ -45154,1683 +45552,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
45154
45552
|
return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
45155
45553
|
};
|
|
45156
45554
|
|
|
45157
|
-
function isRealJSONParse(jsonParse) {
|
|
45158
|
-
const randomPhrase = buildRandomPhrase();
|
|
45159
|
-
const randomHintPhrase = buildRandomPhrase();
|
|
45160
|
-
const randomString = buildRandomString();
|
|
45161
|
-
const testingObject = JSON.stringify({
|
|
45162
|
-
answerArea: {
|
|
45163
|
-
calculator: false,
|
|
45164
|
-
chi2Table: false,
|
|
45165
|
-
financialCalculatorMonthlyPayment: false,
|
|
45166
|
-
financialCalculatorTimeToPayOff: false,
|
|
45167
|
-
financialCalculatorTotalAmount: false,
|
|
45168
|
-
periodicTable: false,
|
|
45169
|
-
periodicTableWithKey: false,
|
|
45170
|
-
tTable: false,
|
|
45171
|
-
zTable: false
|
|
45172
|
-
},
|
|
45173
|
-
hints: [randomHintPhrase, `=${Math.floor(Math.random() * 50) + 1}`],
|
|
45174
|
-
itemDataVersion: {
|
|
45175
|
-
major: 0,
|
|
45176
|
-
minor: 1
|
|
45177
|
-
},
|
|
45178
|
-
question: {
|
|
45179
|
-
content: `${randomPhrase}`,
|
|
45180
|
-
images: {},
|
|
45181
|
-
widgets: {
|
|
45182
|
-
expression1: {
|
|
45183
|
-
alignment: "default",
|
|
45184
|
-
graded: false,
|
|
45185
|
-
options: {
|
|
45186
|
-
answerForms: [{
|
|
45187
|
-
considered: "wrong",
|
|
45188
|
-
form: false,
|
|
45189
|
-
key: 0,
|
|
45190
|
-
simplify: false,
|
|
45191
|
-
value: `${randomString}`
|
|
45192
|
-
}],
|
|
45193
|
-
ariaLabel: "Answer",
|
|
45194
|
-
buttonSets: ["basic"],
|
|
45195
|
-
functions: ["f", "g", "h"],
|
|
45196
|
-
static: true,
|
|
45197
|
-
times: false,
|
|
45198
|
-
visibleLabel: "Answer"
|
|
45199
|
-
},
|
|
45200
|
-
static: true,
|
|
45201
|
-
type: "expression",
|
|
45202
|
-
version: {
|
|
45203
|
-
major: 1,
|
|
45204
|
-
minor: 0
|
|
45205
|
-
}
|
|
45206
|
-
}
|
|
45207
|
-
}
|
|
45208
|
-
}
|
|
45209
|
-
});
|
|
45210
|
-
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
45211
|
-
const parsedTestJSON = jsonParse(testJSON);
|
|
45212
|
-
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
45213
|
-
return perseusCore.approximateDeepEqual(parsedTestItemData, testingObject);
|
|
45214
|
-
}
|
|
45215
|
-
function buildRandomString() {
|
|
45216
|
-
let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
45217
|
-
let randomString = "";
|
|
45218
|
-
const randomLength = Math.floor(Math.random() * 8) + 3;
|
|
45219
|
-
for (let i = 0; i < randomLength; i++) {
|
|
45220
|
-
const randomLetter = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
45221
|
-
randomString += capitalize && i === 0 ? randomLetter.toUpperCase() : randomLetter;
|
|
45222
|
-
}
|
|
45223
|
-
return randomString;
|
|
45224
|
-
}
|
|
45225
|
-
function buildRandomPhrase() {
|
|
45226
|
-
const phrases = [];
|
|
45227
|
-
const randomLength = Math.floor(Math.random() * 10) + 5;
|
|
45228
|
-
for (let i = 0; i < randomLength; i++) {
|
|
45229
|
-
phrases.push(buildRandomString(i === 0));
|
|
45230
|
-
}
|
|
45231
|
-
const modifierStart = ["**", "$"];
|
|
45232
|
-
const modifierEnd = ["**", "$"];
|
|
45233
|
-
const modifierIndex = Math.floor(Math.random() * modifierStart.length);
|
|
45234
|
-
return `${modifierStart[modifierIndex]}${phrases.join(" ")}${modifierEnd[modifierIndex]}`;
|
|
45235
|
-
}
|
|
45236
|
-
function buildTestData(testObject) {
|
|
45237
|
-
return `{"data":{"assessmentItem":{"__typename":"AssessmentItemOrError","error":null,"item":{"__typename":"AssessmentItem","id":"x890b3c70f3e8f4a6","itemData":"${testObject}","problemType":"Type 1","sha":"c7284a3ad65214b4e62bccce236d92f7f5d35941"}}}}`;
|
|
45238
|
-
}
|
|
45239
|
-
|
|
45240
|
-
function success(value) {
|
|
45241
|
-
return {
|
|
45242
|
-
type: "success",
|
|
45243
|
-
value
|
|
45244
|
-
};
|
|
45245
|
-
}
|
|
45246
|
-
function failure(detail) {
|
|
45247
|
-
return {
|
|
45248
|
-
type: "failure",
|
|
45249
|
-
detail
|
|
45250
|
-
};
|
|
45251
|
-
}
|
|
45252
|
-
function isFailure(result) {
|
|
45253
|
-
return result.type === "failure";
|
|
45254
|
-
}
|
|
45255
|
-
function isSuccess(result) {
|
|
45256
|
-
return result.type === "success";
|
|
45257
|
-
}
|
|
45258
|
-
|
|
45259
|
-
// Result's `all` function is similar to Promise.all: given an array of
|
|
45260
|
-
// results, it returns success if all succeeded, and failure if any failed.
|
|
45261
|
-
function all(results) {
|
|
45262
|
-
let combineFailureDetails = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : a => a;
|
|
45263
|
-
const values = [];
|
|
45264
|
-
const failureDetails = [];
|
|
45265
|
-
for (const result of results) {
|
|
45266
|
-
if (result.type === "success") {
|
|
45267
|
-
values.push(result.value);
|
|
45268
|
-
} else {
|
|
45269
|
-
failureDetails.push(result.detail);
|
|
45270
|
-
}
|
|
45271
|
-
}
|
|
45272
|
-
if (failureDetails.length > 0) {
|
|
45273
|
-
return failure(failureDetails.reduce(combineFailureDetails));
|
|
45274
|
-
}
|
|
45275
|
-
return success(values);
|
|
45276
|
-
}
|
|
45277
|
-
|
|
45278
|
-
class ErrorTrackingParseContext {
|
|
45279
|
-
constructor(path) {
|
|
45280
|
-
this.path = path;
|
|
45281
|
-
}
|
|
45282
|
-
failure(expected, badValue) {
|
|
45283
|
-
return failure([{
|
|
45284
|
-
expected: wrapInArray(expected),
|
|
45285
|
-
badValue,
|
|
45286
|
-
path: this.path
|
|
45287
|
-
}]);
|
|
45288
|
-
}
|
|
45289
|
-
forSubtree(key) {
|
|
45290
|
-
return new ErrorTrackingParseContext([...this.path, key]);
|
|
45291
|
-
}
|
|
45292
|
-
success(value) {
|
|
45293
|
-
return success(value);
|
|
45294
|
-
}
|
|
45295
|
-
}
|
|
45296
|
-
function wrapInArray(a) {
|
|
45297
|
-
return Array.isArray(a) ? a : [a];
|
|
45298
|
-
}
|
|
45299
|
-
|
|
45300
|
-
function formatPath(path) {
|
|
45301
|
-
return "(root)" + path.map(formatPathSegment).join("");
|
|
45302
|
-
}
|
|
45303
|
-
function formatPathSegment(segment) {
|
|
45304
|
-
if (typeof segment === "string") {
|
|
45305
|
-
return validIdentifier.test(segment) ? "." + segment : `[${JSON.stringify(segment)}]`;
|
|
45306
|
-
}
|
|
45307
|
-
return `[${segment.toString()}]`;
|
|
45308
|
-
}
|
|
45309
|
-
const validIdentifier = /^[A-Za-z$_][A-Za-z$_0-9]*$/;
|
|
45310
|
-
|
|
45311
|
-
function message(failure) {
|
|
45312
|
-
const expected = conjoin(failure.expected);
|
|
45313
|
-
const path = formatPath(failure.path);
|
|
45314
|
-
const badValue = JSON.stringify(failure.badValue);
|
|
45315
|
-
return `At ${path} -- expected ${expected}, but got ${badValue}`;
|
|
45316
|
-
}
|
|
45317
|
-
function conjoin(items) {
|
|
45318
|
-
switch (items.length) {
|
|
45319
|
-
// TODO(benchristel): handle 0 if this is reused elsewhere.
|
|
45320
|
-
case 1:
|
|
45321
|
-
return items[0];
|
|
45322
|
-
case 2:
|
|
45323
|
-
return items.join(" or ");
|
|
45324
|
-
default:
|
|
45325
|
-
{
|
|
45326
|
-
const allButLast = items.slice(0, items.length - 1);
|
|
45327
|
-
const last = items[items.length - 1];
|
|
45328
|
-
return allButLast.join(", ") + ", or " + last;
|
|
45329
|
-
}
|
|
45330
|
-
}
|
|
45331
|
-
}
|
|
45332
|
-
|
|
45333
|
-
function parse(value, parser) {
|
|
45334
|
-
const result = parser(value, new ErrorTrackingParseContext([]));
|
|
45335
|
-
if (isFailure(result)) {
|
|
45336
|
-
return failure(result.detail.map(message).join("; "));
|
|
45337
|
-
}
|
|
45338
|
-
return result;
|
|
45339
|
-
}
|
|
45340
|
-
|
|
45341
|
-
const any = (rawValue, ctx) => ctx.success(rawValue);
|
|
45342
|
-
|
|
45343
|
-
function array(elementParser) {
|
|
45344
|
-
return (rawValue, ctx) => {
|
|
45345
|
-
if (!Array.isArray(rawValue)) {
|
|
45346
|
-
return ctx.failure("array", rawValue);
|
|
45347
|
-
}
|
|
45348
|
-
const elementResults = rawValue.map((elem, i) => elementParser(elem, ctx.forSubtree(i)));
|
|
45349
|
-
return all(elementResults, concat);
|
|
45350
|
-
};
|
|
45351
|
-
}
|
|
45352
|
-
function concat(a, b) {
|
|
45353
|
-
return [...a, ...b];
|
|
45354
|
-
}
|
|
45355
|
-
|
|
45356
|
-
function boolean(rawValue, ctx) {
|
|
45357
|
-
if (typeof rawValue === "boolean") {
|
|
45358
|
-
return ctx.success(rawValue);
|
|
45359
|
-
}
|
|
45360
|
-
return ctx.failure("boolean", rawValue);
|
|
45361
|
-
}
|
|
45362
|
-
|
|
45363
|
-
function constant(acceptedValue) {
|
|
45364
|
-
return (rawValue, ctx) => {
|
|
45365
|
-
if (rawValue !== acceptedValue) {
|
|
45366
|
-
return ctx.failure(String(JSON.stringify(acceptedValue)), rawValue);
|
|
45367
|
-
}
|
|
45368
|
-
return ctx.success(acceptedValue);
|
|
45369
|
-
};
|
|
45370
|
-
}
|
|
45371
|
-
|
|
45372
|
-
function enumeration() {
|
|
45373
|
-
for (var _len = arguments.length, acceptedValues = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
45374
|
-
acceptedValues[_key] = arguments[_key];
|
|
45375
|
-
}
|
|
45376
|
-
return (rawValue, ctx) => {
|
|
45377
|
-
if (typeof rawValue === "string") {
|
|
45378
|
-
const index = acceptedValues.indexOf(rawValue);
|
|
45379
|
-
if (index > -1) {
|
|
45380
|
-
return ctx.success(acceptedValues[index]);
|
|
45381
|
-
}
|
|
45382
|
-
}
|
|
45383
|
-
const expected = acceptedValues.map(v => JSON.stringify(v));
|
|
45384
|
-
return ctx.failure(expected, rawValue);
|
|
45385
|
-
};
|
|
45386
|
-
}
|
|
45387
|
-
|
|
45388
|
-
function isObject(x) {
|
|
45389
|
-
return x != null && Object.getPrototypeOf(x) === Object.prototype;
|
|
45390
|
-
}
|
|
45391
|
-
|
|
45392
|
-
function nullable(parseValue) {
|
|
45393
|
-
return (rawValue, ctx) => {
|
|
45394
|
-
if (rawValue === null) {
|
|
45395
|
-
return ctx.success(rawValue);
|
|
45396
|
-
}
|
|
45397
|
-
return parseValue(rawValue, ctx);
|
|
45398
|
-
};
|
|
45399
|
-
}
|
|
45400
|
-
|
|
45401
|
-
const number = (rawValue, ctx) => {
|
|
45402
|
-
if (typeof rawValue === "number") {
|
|
45403
|
-
return ctx.success(rawValue);
|
|
45404
|
-
}
|
|
45405
|
-
return ctx.failure("number", rawValue);
|
|
45406
|
-
};
|
|
45407
|
-
|
|
45408
|
-
function object(schema) {
|
|
45409
|
-
return (rawValue, ctx) => {
|
|
45410
|
-
if (!isObject(rawValue)) {
|
|
45411
|
-
return ctx.failure("object", rawValue);
|
|
45412
|
-
}
|
|
45413
|
-
const ret = {
|
|
45414
|
-
...rawValue
|
|
45415
|
-
};
|
|
45416
|
-
const mismatches = [];
|
|
45417
|
-
for (const [prop, propParser] of Object.entries(schema)) {
|
|
45418
|
-
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
45419
|
-
if (isSuccess(result)) {
|
|
45420
|
-
if (result.value !== undefined || prop in rawValue) {
|
|
45421
|
-
ret[prop] = result.value;
|
|
45422
|
-
}
|
|
45423
|
-
} else {
|
|
45424
|
-
mismatches.push(...result.detail);
|
|
45425
|
-
}
|
|
45426
|
-
}
|
|
45427
|
-
if (mismatches.length > 0) {
|
|
45428
|
-
return failure(mismatches);
|
|
45429
|
-
}
|
|
45430
|
-
return ctx.success(ret);
|
|
45431
|
-
};
|
|
45432
|
-
}
|
|
45433
|
-
|
|
45434
|
-
function optional(parseValue) {
|
|
45435
|
-
return (rawValue, ctx) => {
|
|
45436
|
-
if (rawValue === undefined) {
|
|
45437
|
-
return ctx.success(rawValue);
|
|
45438
|
-
}
|
|
45439
|
-
return parseValue(rawValue, ctx);
|
|
45440
|
-
};
|
|
45441
|
-
}
|
|
45442
|
-
|
|
45443
|
-
function pair(parseA, parseB) {
|
|
45444
|
-
return (rawValue, ctx) => {
|
|
45445
|
-
if (!Array.isArray(rawValue)) {
|
|
45446
|
-
return ctx.failure("array", rawValue);
|
|
45447
|
-
}
|
|
45448
|
-
if (rawValue.length !== 2) {
|
|
45449
|
-
return ctx.failure("array of length 2", rawValue);
|
|
45450
|
-
}
|
|
45451
|
-
const [rawA, rawB] = rawValue;
|
|
45452
|
-
const resultA = parseA(rawA, ctx.forSubtree(0));
|
|
45453
|
-
if (isFailure(resultA)) {
|
|
45454
|
-
return resultA;
|
|
45455
|
-
}
|
|
45456
|
-
const resultB = parseB(rawB, ctx.forSubtree(1));
|
|
45457
|
-
if (isFailure(resultB)) {
|
|
45458
|
-
return resultB;
|
|
45459
|
-
}
|
|
45460
|
-
return ctx.success([resultA.value, resultB.value]);
|
|
45461
|
-
};
|
|
45462
|
-
}
|
|
45463
|
-
|
|
45464
|
-
function pipeParsers(p) {
|
|
45465
|
-
return new ParserPipeline(p);
|
|
45466
|
-
}
|
|
45467
|
-
class ParserPipeline {
|
|
45468
|
-
constructor(parser) {
|
|
45469
|
-
this.parser = parser;
|
|
45470
|
-
}
|
|
45471
|
-
then(nextParser) {
|
|
45472
|
-
return new ParserPipeline(composeParsers(this.parser, nextParser));
|
|
45473
|
-
}
|
|
45474
|
-
}
|
|
45475
|
-
function composeParsers(parserA, parserB) {
|
|
45476
|
-
return (rawValue, ctx) => {
|
|
45477
|
-
const partialResult = parserA(rawValue, ctx);
|
|
45478
|
-
if (isFailure(partialResult)) {
|
|
45479
|
-
return partialResult;
|
|
45480
|
-
}
|
|
45481
|
-
return parserB(partialResult.value, ctx);
|
|
45482
|
-
};
|
|
45483
|
-
}
|
|
45484
|
-
|
|
45485
|
-
function record(parseKey, parseValue) {
|
|
45486
|
-
return (rawValue, ctx) => {
|
|
45487
|
-
if (!isObject(rawValue)) {
|
|
45488
|
-
return ctx.failure("object", rawValue);
|
|
45489
|
-
}
|
|
45490
|
-
const result = {};
|
|
45491
|
-
const mismatches = [];
|
|
45492
|
-
for (const [key, value] of Object.entries(rawValue)) {
|
|
45493
|
-
const entryCtx = ctx.forSubtree(key);
|
|
45494
|
-
const keyResult = parseKey(key, entryCtx);
|
|
45495
|
-
if (isFailure(keyResult)) {
|
|
45496
|
-
mismatches.push(...keyResult.detail);
|
|
45497
|
-
}
|
|
45498
|
-
const valueResult = parseValue(value, entryCtx);
|
|
45499
|
-
if (isFailure(valueResult)) {
|
|
45500
|
-
mismatches.push(...valueResult.detail);
|
|
45501
|
-
}
|
|
45502
|
-
if (isSuccess(keyResult) && isSuccess(valueResult)) {
|
|
45503
|
-
result[keyResult.value] = valueResult.value;
|
|
45504
|
-
}
|
|
45505
|
-
}
|
|
45506
|
-
if (mismatches.length > 0) {
|
|
45507
|
-
return failure(mismatches);
|
|
45508
|
-
}
|
|
45509
|
-
return ctx.success(result);
|
|
45510
|
-
};
|
|
45511
|
-
}
|
|
45512
|
-
|
|
45513
|
-
const string = (rawValue, ctx) => {
|
|
45514
|
-
if (typeof rawValue === "string") {
|
|
45515
|
-
return ctx.success(rawValue);
|
|
45516
|
-
}
|
|
45517
|
-
return ctx.failure("string", rawValue);
|
|
45518
|
-
};
|
|
45519
|
-
|
|
45520
|
-
function trio(parseA, parseB, parseC) {
|
|
45521
|
-
return (rawValue, ctx) => {
|
|
45522
|
-
if (!Array.isArray(rawValue)) {
|
|
45523
|
-
return ctx.failure("array", rawValue);
|
|
45524
|
-
}
|
|
45525
|
-
if (rawValue.length !== 3) {
|
|
45526
|
-
return ctx.failure("array of length 3", rawValue);
|
|
45527
|
-
}
|
|
45528
|
-
const resultA = parseA(rawValue[0], ctx.forSubtree(0));
|
|
45529
|
-
if (isFailure(resultA)) {
|
|
45530
|
-
return resultA;
|
|
45531
|
-
}
|
|
45532
|
-
const resultB = parseB(rawValue[1], ctx.forSubtree(1));
|
|
45533
|
-
if (isFailure(resultB)) {
|
|
45534
|
-
return resultB;
|
|
45535
|
-
}
|
|
45536
|
-
const resultC = parseC(rawValue[2], ctx.forSubtree(2));
|
|
45537
|
-
if (isFailure(resultC)) {
|
|
45538
|
-
return resultC;
|
|
45539
|
-
}
|
|
45540
|
-
return ctx.success([resultA.value, resultB.value, resultC.value]);
|
|
45541
|
-
};
|
|
45542
|
-
}
|
|
45543
|
-
|
|
45544
|
-
function union(parseBranch) {
|
|
45545
|
-
return new UnionBuilder(parseBranch);
|
|
45546
|
-
}
|
|
45547
|
-
class UnionBuilder {
|
|
45548
|
-
constructor(parser) {
|
|
45549
|
-
this.parser = parser;
|
|
45550
|
-
}
|
|
45551
|
-
or(newBranch) {
|
|
45552
|
-
return new UnionBuilder(either(this.parser, newBranch));
|
|
45553
|
-
}
|
|
45554
|
-
}
|
|
45555
|
-
function either(parseA, parseB) {
|
|
45556
|
-
return (rawValue, ctx) => {
|
|
45557
|
-
const resultA = parseA(rawValue, ctx);
|
|
45558
|
-
if (isSuccess(resultA)) {
|
|
45559
|
-
return resultA;
|
|
45560
|
-
}
|
|
45561
|
-
return parseB(rawValue, ctx);
|
|
45562
|
-
};
|
|
45563
|
-
}
|
|
45564
|
-
|
|
45565
|
-
function defaulted(parser, fallback) {
|
|
45566
|
-
return (rawValue, ctx) => {
|
|
45567
|
-
if (rawValue == null) {
|
|
45568
|
-
return success(fallback(rawValue));
|
|
45569
|
-
}
|
|
45570
|
-
return parser(rawValue, ctx);
|
|
45571
|
-
};
|
|
45572
|
-
}
|
|
45573
|
-
|
|
45574
|
-
const parseImages = defaulted(record(string, object({
|
|
45575
|
-
width: number,
|
|
45576
|
-
height: number
|
|
45577
|
-
})), () => ({}));
|
|
45578
|
-
|
|
45579
|
-
function parseWidget(parseType, parseOptions) {
|
|
45580
|
-
return object({
|
|
45581
|
-
type: parseType,
|
|
45582
|
-
static: optional(boolean),
|
|
45583
|
-
graded: optional(boolean),
|
|
45584
|
-
alignment: optional(string),
|
|
45585
|
-
options: parseOptions,
|
|
45586
|
-
key: optional(number),
|
|
45587
|
-
version: optional(object({
|
|
45588
|
-
major: number,
|
|
45589
|
-
minor: number
|
|
45590
|
-
}))
|
|
45591
|
-
});
|
|
45592
|
-
}
|
|
45593
|
-
function parseWidgetWithVersion(parseVersion, parseType, parseOptions) {
|
|
45594
|
-
return object({
|
|
45595
|
-
type: parseType,
|
|
45596
|
-
static: optional(boolean),
|
|
45597
|
-
graded: optional(boolean),
|
|
45598
|
-
alignment: optional(string),
|
|
45599
|
-
options: parseOptions,
|
|
45600
|
-
key: optional(number),
|
|
45601
|
-
version: parseVersion
|
|
45602
|
-
});
|
|
45603
|
-
}
|
|
45604
|
-
|
|
45605
|
-
const parseCategorizerWidget = parseWidget(constant("categorizer"), object({
|
|
45606
|
-
items: array(string),
|
|
45607
|
-
categories: array(string),
|
|
45608
|
-
randomizeItems: defaulted(boolean, () => false),
|
|
45609
|
-
static: defaulted(boolean, () => false),
|
|
45610
|
-
values: array(defaulted(number, () => 0)),
|
|
45611
|
-
highlightLint: optional(boolean),
|
|
45612
|
-
linterContext: optional(object({
|
|
45613
|
-
contentType: string,
|
|
45614
|
-
paths: array(string),
|
|
45615
|
-
stack: array(string)
|
|
45616
|
-
}))
|
|
45617
|
-
}));
|
|
45618
|
-
|
|
45619
|
-
const parseCSProgramWidget = parseWidget(constant("cs-program"), object({
|
|
45620
|
-
programID: string,
|
|
45621
|
-
programType: any,
|
|
45622
|
-
settings: array(object({
|
|
45623
|
-
name: string,
|
|
45624
|
-
value: string
|
|
45625
|
-
})),
|
|
45626
|
-
showEditor: boolean,
|
|
45627
|
-
showButtons: boolean,
|
|
45628
|
-
height: number,
|
|
45629
|
-
static: defaulted(boolean, () => false)
|
|
45630
|
-
}));
|
|
45631
|
-
|
|
45632
|
-
const parseDefinitionWidget = parseWidget(constant("definition"), object({
|
|
45633
|
-
togglePrompt: string,
|
|
45634
|
-
definition: string,
|
|
45635
|
-
static: defaulted(boolean, () => false)
|
|
45636
|
-
}));
|
|
45637
|
-
|
|
45638
|
-
const parseDropdownWidget = parseWidget(constant("dropdown"), object({
|
|
45639
|
-
placeholder: defaulted(string, () => ""),
|
|
45640
|
-
ariaLabel: optional(string),
|
|
45641
|
-
visibleLabel: optional(string),
|
|
45642
|
-
static: defaulted(boolean, () => false),
|
|
45643
|
-
choices: array(object({
|
|
45644
|
-
content: string,
|
|
45645
|
-
correct: boolean
|
|
45646
|
-
}))
|
|
45647
|
-
}));
|
|
45648
|
-
|
|
45649
|
-
const parseExplanationWidget = parseWidget(constant("explanation"), object({
|
|
45650
|
-
showPrompt: string,
|
|
45651
|
-
hidePrompt: string,
|
|
45652
|
-
explanation: string,
|
|
45653
|
-
// We wrap parseWidgetsMap in a function here to make sure it is not
|
|
45654
|
-
// referenced before it is defined. There is an import cycle between
|
|
45655
|
-
// this file and widgets-map.ts that could cause it to be undefined.
|
|
45656
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
45657
|
-
static: defaulted(boolean, () => false)
|
|
45658
|
-
}));
|
|
45659
|
-
|
|
45660
|
-
// Given a function, creates a PartialParser that converts one type to another
|
|
45661
|
-
// using that function. The returned parser never fails.
|
|
45662
|
-
function convert(f) {
|
|
45663
|
-
return (rawValue, ctx) => ctx.success(f(rawValue));
|
|
45664
|
-
}
|
|
45665
|
-
|
|
45666
|
-
/**
|
|
45667
|
-
* Creates a parser for a widget options type with multiple major versions. Old
|
|
45668
|
-
* versions are migrated to the latest version. The parse fails if the input
|
|
45669
|
-
* data does not match any of the versions.
|
|
45670
|
-
*
|
|
45671
|
-
* @example
|
|
45672
|
-
* const parseOptions = versionedWidgetOptions(3, parseOptionsV3)
|
|
45673
|
-
* .withMigrationFrom(2, parseOptionsV2, migrateV2ToV3)
|
|
45674
|
-
* .withMigrationFrom(1, parseOptionsV1, migrateV1ToV2)
|
|
45675
|
-
* .withMigrationFrom(0, parseOptionsV0, migrateV0ToV1)
|
|
45676
|
-
* .parser;
|
|
45677
|
-
*
|
|
45678
|
-
* @param latestMajorVersion the latest major version of the widget options.
|
|
45679
|
-
* @param parseLatest a {@link Parser} for the latest version of the widget
|
|
45680
|
-
* options.
|
|
45681
|
-
* @returns a builder object, to which migrations from earlier versions can be
|
|
45682
|
-
* added. Migrations must be added in "reverse chronological" order as in the
|
|
45683
|
-
* example above.
|
|
45684
|
-
*/
|
|
45685
|
-
function versionedWidgetOptions(latestMajorVersion, parseLatest) {
|
|
45686
|
-
return new VersionedWidgetOptionsParserBuilder(latestMajorVersion, parseLatest, latest => latest, (raw, ctx) => ctx.failure("widget options with a known version number", raw));
|
|
45687
|
-
}
|
|
45688
|
-
class VersionedWidgetOptionsParserBuilder {
|
|
45689
|
-
parser;
|
|
45690
|
-
constructor(majorVersion, parseThisVersion, migrateToLatest, parseOtherVersions) {
|
|
45691
|
-
this.migrateToLatest = migrateToLatest;
|
|
45692
|
-
this.parseOtherVersions = parseOtherVersions;
|
|
45693
|
-
const parseThisVersionAndMigrateToLatest = pipeParsers(parseThisVersion).then(convert(this.migrateToLatest)).parser;
|
|
45694
|
-
this.parser = (raw, ctx) => {
|
|
45695
|
-
if (!isObject(raw)) {
|
|
45696
|
-
return ctx.failure("object", raw);
|
|
45697
|
-
}
|
|
45698
|
-
const versionParseResult = parseVersionedObject(raw, ctx);
|
|
45699
|
-
if (isFailure(versionParseResult)) {
|
|
45700
|
-
return versionParseResult;
|
|
45701
|
-
}
|
|
45702
|
-
if (versionParseResult.value.version.major !== majorVersion) {
|
|
45703
|
-
return this.parseOtherVersions(raw, ctx);
|
|
45704
|
-
}
|
|
45705
|
-
return parseThisVersionAndMigrateToLatest(raw, ctx);
|
|
45706
|
-
};
|
|
45707
|
-
}
|
|
45708
|
-
|
|
45709
|
-
/**
|
|
45710
|
-
* Add a migration from an old version of the widget options.
|
|
45711
|
-
*/
|
|
45712
|
-
withMigrationFrom(majorVersion, parseOldVersion, migrateToNextVersion) {
|
|
45713
|
-
const parseOtherVersions = this.parser;
|
|
45714
|
-
const migrateToLatest = old => this.migrateToLatest(migrateToNextVersion(old));
|
|
45715
|
-
return new VersionedWidgetOptionsParserBuilder(majorVersion, parseOldVersion, migrateToLatest, parseOtherVersions);
|
|
45716
|
-
}
|
|
45717
|
-
}
|
|
45718
|
-
const parseVersionedObject = object({
|
|
45719
|
-
version: defaulted(object({
|
|
45720
|
-
major: number,
|
|
45721
|
-
minor: number
|
|
45722
|
-
}), () => ({
|
|
45723
|
-
major: 0,
|
|
45724
|
-
minor: 0
|
|
45725
|
-
}))
|
|
45726
|
-
});
|
|
45727
|
-
|
|
45728
|
-
const stringOrNumberOrNullOrUndefined = union(string).or(number).or(constant(null)).or(constant(undefined)).parser;
|
|
45729
|
-
const parsePossiblyInvalidAnswerForm = object({
|
|
45730
|
-
// `value` is the possibly invalid part of this. It should always be a
|
|
45731
|
-
// string, but some answer forms don't have it. The Expression widget
|
|
45732
|
-
// ignores invalid values, so we can safely filter them out during parsing.
|
|
45733
|
-
value: optional(string),
|
|
45734
|
-
form: defaulted(boolean, () => false),
|
|
45735
|
-
simplify: defaulted(boolean, () => false),
|
|
45736
|
-
considered: enumeration("correct", "wrong", "ungraded"),
|
|
45737
|
-
key: pipeParsers(stringOrNumberOrNullOrUndefined).then(convert(String)).parser
|
|
45738
|
-
});
|
|
45739
|
-
function removeInvalidAnswerForms(possiblyInvalid) {
|
|
45740
|
-
const valid = [];
|
|
45741
|
-
for (const answerForm of possiblyInvalid) {
|
|
45742
|
-
const {
|
|
45743
|
-
value
|
|
45744
|
-
} = answerForm;
|
|
45745
|
-
if (value != null) {
|
|
45746
|
-
// Copying the object seems to be needed to make TypeScript happy
|
|
45747
|
-
valid.push({
|
|
45748
|
-
...answerForm,
|
|
45749
|
-
value
|
|
45750
|
-
});
|
|
45751
|
-
}
|
|
45752
|
-
}
|
|
45753
|
-
return valid;
|
|
45754
|
-
}
|
|
45755
|
-
|
|
45756
|
-
// NOTE(benchristel): I copied the default buttonSets from
|
|
45757
|
-
// expression.tsx. See the parse-perseus-json/README.md for
|
|
45758
|
-
// an explanation of why we want to duplicate the default here.
|
|
45759
|
-
const parseButtonSets = defaulted(array(enumeration("basic", "basic+div", "trig", "prealgebra", "logarithms", "basic relations", "advanced relations")), () => ["basic", "trig", "prealgebra", "logarithms"]);
|
|
45760
|
-
const version1 = object({
|
|
45761
|
-
major: constant(1),
|
|
45762
|
-
minor: number
|
|
45763
|
-
});
|
|
45764
|
-
const parseExpressionWidgetV1 = parseWidgetWithVersion(version1, constant("expression"), object({
|
|
45765
|
-
answerForms: pipeParsers(array(parsePossiblyInvalidAnswerForm)).then(convert(removeInvalidAnswerForms)).parser,
|
|
45766
|
-
functions: array(string),
|
|
45767
|
-
times: boolean,
|
|
45768
|
-
visibleLabel: optional(string),
|
|
45769
|
-
ariaLabel: optional(string),
|
|
45770
|
-
buttonSets: parseButtonSets,
|
|
45771
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45772
|
-
}));
|
|
45773
|
-
const version0 = optional(object({
|
|
45774
|
-
major: constant(0),
|
|
45775
|
-
minor: number
|
|
45776
|
-
}));
|
|
45777
|
-
const parseExpressionWidgetV0 = parseWidgetWithVersion(version0, constant("expression"), object({
|
|
45778
|
-
functions: array(string),
|
|
45779
|
-
times: boolean,
|
|
45780
|
-
visibleLabel: optional(string),
|
|
45781
|
-
ariaLabel: optional(string),
|
|
45782
|
-
form: boolean,
|
|
45783
|
-
simplify: boolean,
|
|
45784
|
-
value: string,
|
|
45785
|
-
buttonSets: parseButtonSets,
|
|
45786
|
-
buttonsVisible: optional(enumeration("always", "never", "focused"))
|
|
45787
|
-
}));
|
|
45788
|
-
function migrateV0ToV1(widget) {
|
|
45789
|
-
const {
|
|
45790
|
-
options
|
|
45791
|
-
} = widget;
|
|
45792
|
-
return {
|
|
45793
|
-
...widget,
|
|
45794
|
-
version: {
|
|
45795
|
-
major: 1,
|
|
45796
|
-
minor: 0
|
|
45797
|
-
},
|
|
45798
|
-
options: {
|
|
45799
|
-
times: options.times,
|
|
45800
|
-
buttonSets: options.buttonSets,
|
|
45801
|
-
functions: options.functions,
|
|
45802
|
-
buttonsVisible: options.buttonsVisible,
|
|
45803
|
-
visibleLabel: options.visibleLabel,
|
|
45804
|
-
ariaLabel: options.ariaLabel,
|
|
45805
|
-
answerForms: [{
|
|
45806
|
-
considered: "correct",
|
|
45807
|
-
form: options.form,
|
|
45808
|
-
simplify: options.simplify,
|
|
45809
|
-
value: options.value
|
|
45810
|
-
}]
|
|
45811
|
-
}
|
|
45812
|
-
};
|
|
45813
|
-
}
|
|
45814
|
-
const parseExpressionWidget = versionedWidgetOptions(1, parseExpressionWidgetV1).withMigrationFrom(0, parseExpressionWidgetV0, migrateV0ToV1).parser;
|
|
45815
|
-
|
|
45816
|
-
const falseToNull = pipeParsers(constant(false)).then(convert(() => null)).parser;
|
|
45817
|
-
const parseGradedGroupWidgetOptions = object({
|
|
45818
|
-
title: defaulted(string, () => ""),
|
|
45819
|
-
hasHint: optional(nullable(boolean)),
|
|
45820
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45821
|
-
// The anonymous function below ensures that we don't try to access
|
|
45822
|
-
// parsePerseusRenderer before it's defined.
|
|
45823
|
-
hint: union(falseToNull).or(constant(null)).or(constant(undefined)).or((rawVal, ctx) => parsePerseusRenderer(rawVal, ctx)).parser,
|
|
45824
|
-
content: string,
|
|
45825
|
-
// This module has an import cycle with parseWidgetsMap.
|
|
45826
|
-
// The anonymous function below ensures that we don't try to access
|
|
45827
|
-
// parseWidgetsMap before it's defined.
|
|
45828
|
-
widgets: (rawVal, ctx) => parseWidgetsMap(rawVal, ctx),
|
|
45829
|
-
widgetEnabled: optional(nullable(boolean)),
|
|
45830
|
-
immutableWidgets: optional(nullable(boolean)),
|
|
45831
|
-
images: record(string, object({
|
|
45832
|
-
width: number,
|
|
45833
|
-
height: number
|
|
45834
|
-
}))
|
|
45835
|
-
});
|
|
45836
|
-
const parseGradedGroupWidget = parseWidget(constant("graded-group"), parseGradedGroupWidgetOptions);
|
|
45837
|
-
|
|
45838
|
-
const parseGradedGroupSetWidget = parseWidget(constant("graded-group-set"), object({
|
|
45839
|
-
gradedGroups: array(parseGradedGroupWidgetOptions)
|
|
45840
|
-
}));
|
|
45841
|
-
|
|
45842
|
-
/**
|
|
45843
|
-
* discriminatedUnion() should be preferred over union() when parsing a
|
|
45844
|
-
* discriminated union type, because discriminatedUnion() produces more
|
|
45845
|
-
* understandable failure messages. It takes the discriminant as the source of
|
|
45846
|
-
* truth for which variant is to be parsed, and expects the other data to match
|
|
45847
|
-
* that variant.
|
|
45848
|
-
*/
|
|
45849
|
-
function discriminatedUnionOn(discriminantKey) {
|
|
45850
|
-
const noMoreBranches = (raw, ctx) => {
|
|
45851
|
-
if (!isObject(raw)) {
|
|
45852
|
-
return ctx.failure("object", raw);
|
|
45853
|
-
}
|
|
45854
|
-
return ctx.forSubtree(discriminantKey).failure("a valid value", raw[discriminantKey]);
|
|
45855
|
-
};
|
|
45856
|
-
return new DiscriminatedUnionBuilder(discriminantKey, noMoreBranches);
|
|
45857
|
-
}
|
|
45858
|
-
class DiscriminatedUnionBuilder {
|
|
45859
|
-
constructor(discriminantKey, parser) {
|
|
45860
|
-
this.discriminantKey = discriminantKey;
|
|
45861
|
-
this.parser = parser;
|
|
45862
|
-
}
|
|
45863
|
-
withBranch(discriminantValue, parseNewVariant) {
|
|
45864
|
-
const parseNewBranch = discriminatedUnionBranch(this.discriminantKey, discriminantValue, parseNewVariant, this.parser);
|
|
45865
|
-
return new DiscriminatedUnionBuilder(this.discriminantKey, parseNewBranch);
|
|
45866
|
-
}
|
|
45867
|
-
}
|
|
45868
|
-
function discriminatedUnionBranch(discriminantKey, discriminantValue, parseVariant, parseOtherBranches) {
|
|
45869
|
-
return (raw, ctx) => {
|
|
45870
|
-
if (!isObject(raw)) {
|
|
45871
|
-
return ctx.failure("object", raw);
|
|
45872
|
-
}
|
|
45873
|
-
if (raw[discriminantKey] === discriminantValue) {
|
|
45874
|
-
return parseVariant(raw, ctx);
|
|
45875
|
-
}
|
|
45876
|
-
return parseOtherBranches(raw, ctx);
|
|
45877
|
-
};
|
|
45878
|
-
}
|
|
45879
|
-
|
|
45880
|
-
const pairOfNumbers$3 = pair(number, number);
|
|
45881
|
-
const pairOfPoints = pair(pairOfNumbers$3, pairOfNumbers$3);
|
|
45882
|
-
const parseGrapherWidget = parseWidget(constant("grapher"), object({
|
|
45883
|
-
availableTypes: array(enumeration("absolute_value", "exponential", "linear", "logarithm", "quadratic", "sinusoid", "tangent")),
|
|
45884
|
-
correct: discriminatedUnionOn("type").withBranch("absolute_value", object({
|
|
45885
|
-
type: constant("absolute_value"),
|
|
45886
|
-
coords: nullable(pairOfPoints)
|
|
45887
|
-
})).withBranch("exponential", object({
|
|
45888
|
-
type: constant("exponential"),
|
|
45889
|
-
asymptote: pairOfPoints,
|
|
45890
|
-
coords: nullable(pairOfPoints)
|
|
45891
|
-
})).withBranch("linear", object({
|
|
45892
|
-
type: constant("linear"),
|
|
45893
|
-
coords: nullable(pairOfPoints)
|
|
45894
|
-
})).withBranch("logarithm", object({
|
|
45895
|
-
type: constant("logarithm"),
|
|
45896
|
-
asymptote: pairOfPoints,
|
|
45897
|
-
coords: nullable(pairOfPoints)
|
|
45898
|
-
})).withBranch("quadratic", object({
|
|
45899
|
-
type: constant("quadratic"),
|
|
45900
|
-
coords: nullable(pairOfPoints)
|
|
45901
|
-
})).withBranch("sinusoid", object({
|
|
45902
|
-
type: constant("sinusoid"),
|
|
45903
|
-
coords: nullable(pairOfPoints)
|
|
45904
|
-
})).withBranch("tangent", object({
|
|
45905
|
-
type: constant("tangent"),
|
|
45906
|
-
coords: nullable(pairOfPoints)
|
|
45907
|
-
})).parser,
|
|
45908
|
-
graph: object({
|
|
45909
|
-
backgroundImage: object({
|
|
45910
|
-
bottom: optional(number),
|
|
45911
|
-
height: optional(number),
|
|
45912
|
-
left: optional(number),
|
|
45913
|
-
scale: optional(number),
|
|
45914
|
-
url: optional(nullable(string)),
|
|
45915
|
-
width: optional(number)
|
|
45916
|
-
}),
|
|
45917
|
-
box: optional(pairOfNumbers$3),
|
|
45918
|
-
editableSettings: optional(array(enumeration("graph", "snap", "image", "measure"))),
|
|
45919
|
-
gridStep: optional(pairOfNumbers$3),
|
|
45920
|
-
labels: pair(string, string),
|
|
45921
|
-
markings: enumeration("graph", "none", "grid"),
|
|
45922
|
-
range: pair(pairOfNumbers$3, pairOfNumbers$3),
|
|
45923
|
-
rulerLabel: constant(""),
|
|
45924
|
-
rulerTicks: number,
|
|
45925
|
-
showProtractor: optional(boolean),
|
|
45926
|
-
showRuler: optional(boolean),
|
|
45927
|
-
showTooltips: optional(boolean),
|
|
45928
|
-
snapStep: optional(pairOfNumbers$3),
|
|
45929
|
-
step: pairOfNumbers$3,
|
|
45930
|
-
valid: optional(union(boolean).or(string).parser)
|
|
45931
|
-
})
|
|
45932
|
-
}));
|
|
45933
|
-
|
|
45934
|
-
const parseGroupWidget = parseWidget(constant("group"),
|
|
45935
|
-
// This module has an import cycle with parsePerseusRenderer.
|
|
45936
|
-
// The anonymous function below ensures that we don't try to access
|
|
45937
|
-
// parsePerseusRenderer before it's defined.
|
|
45938
|
-
(rawVal, ctx) => parsePerseusRenderer(rawVal, ctx));
|
|
45939
|
-
|
|
45940
|
-
const parseIframeWidget = parseWidget(constant("iframe"), object({
|
|
45941
|
-
url: string,
|
|
45942
|
-
settings: optional(array(object({
|
|
45943
|
-
name: string,
|
|
45944
|
-
value: string
|
|
45945
|
-
}))),
|
|
45946
|
-
width: union(number).or(string).parser,
|
|
45947
|
-
height: union(number).or(string).parser,
|
|
45948
|
-
allowFullScreen: defaulted(boolean, () => false),
|
|
45949
|
-
allowTopNavigation: optional(boolean),
|
|
45950
|
-
static: defaulted(boolean, () => false)
|
|
45951
|
-
}));
|
|
45952
|
-
|
|
45953
|
-
const stringToNumber = (rawValue, ctx) => {
|
|
45954
|
-
if (typeof rawValue === "number") {
|
|
45955
|
-
return ctx.success(rawValue);
|
|
45956
|
-
}
|
|
45957
|
-
const parsedNumber = +rawValue;
|
|
45958
|
-
if (rawValue === "" || isNaN(parsedNumber)) {
|
|
45959
|
-
return ctx.failure("a number or numeric string", rawValue);
|
|
45960
|
-
}
|
|
45961
|
-
return ctx.success(parsedNumber);
|
|
45962
|
-
};
|
|
45963
|
-
|
|
45964
|
-
function emptyToZero(x) {
|
|
45965
|
-
return x === "" ? 0 : x;
|
|
45966
|
-
}
|
|
45967
|
-
const imageDimensionToNumber = pipeParsers(union(number).or(string).parser)
|
|
45968
|
-
// In this specific case, empty string is equivalent to zero. An empty
|
|
45969
|
-
// string parses to either NaN (using parseInt) or 0 (using unary +) and
|
|
45970
|
-
// CSS will treat NaN as invalid and default to 0 instead.
|
|
45971
|
-
.then(convert(emptyToZero)).then(stringToNumber).parser;
|
|
45972
|
-
const parsePerseusImageBackground = object({
|
|
45973
|
-
url: optional(nullable(string)),
|
|
45974
|
-
width: optional(imageDimensionToNumber),
|
|
45975
|
-
height: optional(imageDimensionToNumber),
|
|
45976
|
-
top: optional(imageDimensionToNumber),
|
|
45977
|
-
left: optional(imageDimensionToNumber),
|
|
45978
|
-
bottom: optional(imageDimensionToNumber),
|
|
45979
|
-
scale: optional(imageDimensionToNumber)
|
|
45980
|
-
});
|
|
45981
|
-
|
|
45982
|
-
const pairOfNumbers$2 = pair(number, number);
|
|
45983
|
-
const parseImageWidget = parseWidget(constant("image"), object({
|
|
45984
|
-
title: optional(string),
|
|
45985
|
-
caption: optional(string),
|
|
45986
|
-
alt: optional(string),
|
|
45987
|
-
backgroundImage: parsePerseusImageBackground,
|
|
45988
|
-
static: optional(boolean),
|
|
45989
|
-
labels: optional(array(object({
|
|
45990
|
-
content: string,
|
|
45991
|
-
alignment: string,
|
|
45992
|
-
coordinates: array(number)
|
|
45993
|
-
}))),
|
|
45994
|
-
range: optional(pair(pairOfNumbers$2, pairOfNumbers$2)),
|
|
45995
|
-
box: optional(pairOfNumbers$2)
|
|
45996
|
-
}));
|
|
45997
|
-
|
|
45998
|
-
const booleanToString = (rawValue, ctx) => {
|
|
45999
|
-
if (typeof rawValue === "boolean") {
|
|
46000
|
-
return ctx.success(String(rawValue));
|
|
46001
|
-
}
|
|
46002
|
-
return ctx.failure("boolean", rawValue);
|
|
46003
|
-
};
|
|
46004
|
-
const parseInputNumberWidget = parseWidget(constant("input-number"), object({
|
|
46005
|
-
answerType: optional(enumeration("number", "decimal", "integer", "rational", "improper", "mixed", "percent", "pi")),
|
|
46006
|
-
inexact: optional(boolean),
|
|
46007
|
-
maxError: optional(union(number).or(string).parser),
|
|
46008
|
-
rightAlign: optional(boolean),
|
|
46009
|
-
simplify: enumeration("required", "optional", "enforced"),
|
|
46010
|
-
size: enumeration("normal", "small"),
|
|
46011
|
-
// TODO(benchristel): there are some content items where value is a
|
|
46012
|
-
// boolean, even though that makes no sense. We should figure out if
|
|
46013
|
-
// those content items are actually published anywhere, and consider
|
|
46014
|
-
// updating them.
|
|
46015
|
-
value: union(number).or(string).or(booleanToString).parser,
|
|
46016
|
-
customKeypad: optional(boolean)
|
|
46017
|
-
}));
|
|
46018
|
-
|
|
46019
|
-
const pairOfNumbers$1 = pair(number, number);
|
|
46020
|
-
const stringOrEmpty = defaulted(string, () => "");
|
|
46021
|
-
const parseKey = pipeParsers(optional(string)).then(convert(String)).parser;
|
|
46022
|
-
const parseFunctionElement = object({
|
|
46023
|
-
type: constant("function"),
|
|
46024
|
-
key: parseKey,
|
|
46025
|
-
options: object({
|
|
46026
|
-
value: string,
|
|
46027
|
-
funcName: string,
|
|
46028
|
-
rangeMin: string,
|
|
46029
|
-
rangeMax: string,
|
|
46030
|
-
color: string,
|
|
46031
|
-
strokeDasharray: string,
|
|
46032
|
-
strokeWidth: number
|
|
46033
|
-
})
|
|
46034
|
-
});
|
|
46035
|
-
const parseLabelElement = object({
|
|
46036
|
-
type: constant("label"),
|
|
46037
|
-
key: parseKey,
|
|
46038
|
-
options: object({
|
|
46039
|
-
label: string,
|
|
46040
|
-
color: string,
|
|
46041
|
-
coordX: string,
|
|
46042
|
-
coordY: string
|
|
46043
|
-
})
|
|
46044
|
-
});
|
|
46045
|
-
const parseLineElement = object({
|
|
46046
|
-
type: constant("line"),
|
|
46047
|
-
key: parseKey,
|
|
46048
|
-
options: object({
|
|
46049
|
-
color: string,
|
|
46050
|
-
startX: string,
|
|
46051
|
-
startY: string,
|
|
46052
|
-
endX: string,
|
|
46053
|
-
endY: string,
|
|
46054
|
-
strokeDasharray: string,
|
|
46055
|
-
strokeWidth: number,
|
|
46056
|
-
arrows: string
|
|
46057
|
-
})
|
|
46058
|
-
});
|
|
46059
|
-
const parseMovableLineElement = object({
|
|
46060
|
-
type: constant("movable-line"),
|
|
46061
|
-
key: parseKey,
|
|
46062
|
-
options: object({
|
|
46063
|
-
startX: string,
|
|
46064
|
-
startY: string,
|
|
46065
|
-
startSubscript: number,
|
|
46066
|
-
endX: string,
|
|
46067
|
-
endY: string,
|
|
46068
|
-
endSubscript: number,
|
|
46069
|
-
constraint: string,
|
|
46070
|
-
snap: number,
|
|
46071
|
-
constraintFn: string,
|
|
46072
|
-
constraintXMin: string,
|
|
46073
|
-
constraintXMax: string,
|
|
46074
|
-
constraintYMin: string,
|
|
46075
|
-
constraintYMax: string
|
|
46076
|
-
})
|
|
46077
|
-
});
|
|
46078
|
-
const parseMovablePointElement = object({
|
|
46079
|
-
type: constant("movable-point"),
|
|
46080
|
-
key: parseKey,
|
|
46081
|
-
options: object({
|
|
46082
|
-
startX: string,
|
|
46083
|
-
startY: string,
|
|
46084
|
-
varSubscript: number,
|
|
46085
|
-
constraint: string,
|
|
46086
|
-
snap: number,
|
|
46087
|
-
constraintFn: string,
|
|
46088
|
-
constraintXMin: stringOrEmpty,
|
|
46089
|
-
constraintXMax: stringOrEmpty,
|
|
46090
|
-
constraintYMin: stringOrEmpty,
|
|
46091
|
-
constraintYMax: stringOrEmpty
|
|
46092
|
-
})
|
|
46093
|
-
});
|
|
46094
|
-
const parseParametricElement = object({
|
|
46095
|
-
type: constant("parametric"),
|
|
46096
|
-
key: parseKey,
|
|
46097
|
-
options: object({
|
|
46098
|
-
x: string,
|
|
46099
|
-
y: string,
|
|
46100
|
-
rangeMin: string,
|
|
46101
|
-
rangeMax: string,
|
|
46102
|
-
color: string,
|
|
46103
|
-
strokeDasharray: string,
|
|
46104
|
-
strokeWidth: number
|
|
46105
|
-
})
|
|
46106
|
-
});
|
|
46107
|
-
const parsePointElement = object({
|
|
46108
|
-
type: constant("point"),
|
|
46109
|
-
key: parseKey,
|
|
46110
|
-
options: object({
|
|
46111
|
-
color: string,
|
|
46112
|
-
coordX: string,
|
|
46113
|
-
coordY: string
|
|
46114
|
-
})
|
|
46115
|
-
});
|
|
46116
|
-
const parseRectangleElement = object({
|
|
46117
|
-
type: constant("rectangle"),
|
|
46118
|
-
key: parseKey,
|
|
46119
|
-
options: object({
|
|
46120
|
-
color: string,
|
|
46121
|
-
coordX: string,
|
|
46122
|
-
coordY: string,
|
|
46123
|
-
width: string,
|
|
46124
|
-
height: string
|
|
46125
|
-
})
|
|
46126
|
-
});
|
|
46127
|
-
const parseInteractionWidget = parseWidget(constant("interaction"), object({
|
|
46128
|
-
static: defaulted(boolean, () => false),
|
|
46129
|
-
graph: object({
|
|
46130
|
-
editableSettings: optional(array(enumeration("canvas", "graph"))),
|
|
46131
|
-
box: pairOfNumbers$1,
|
|
46132
|
-
labels: array(string),
|
|
46133
|
-
range: pair(pairOfNumbers$1, pairOfNumbers$1),
|
|
46134
|
-
gridStep: pairOfNumbers$1,
|
|
46135
|
-
markings: enumeration("graph", "grid", "none"),
|
|
46136
|
-
snapStep: optional(pairOfNumbers$1),
|
|
46137
|
-
valid: optional(union(boolean).or(string).parser),
|
|
46138
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
46139
|
-
showProtractor: optional(boolean),
|
|
46140
|
-
showRuler: optional(boolean),
|
|
46141
|
-
rulerLabel: optional(string),
|
|
46142
|
-
rulerTicks: optional(number),
|
|
46143
|
-
tickStep: pairOfNumbers$1
|
|
46144
|
-
}),
|
|
46145
|
-
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)
|
|
46146
|
-
}));
|
|
46147
|
-
|
|
46148
|
-
// Used to represent 2-D points and ranges
|
|
46149
|
-
const pairOfNumbers = pair(number, number);
|
|
46150
|
-
const parsePerseusGraphTypeAngle = object({
|
|
46151
|
-
type: constant("angle"),
|
|
46152
|
-
showAngles: optional(boolean),
|
|
46153
|
-
allowReflexAngles: optional(boolean),
|
|
46154
|
-
angleOffsetDeg: optional(number),
|
|
46155
|
-
snapDegrees: optional(number),
|
|
46156
|
-
match: optional(constant("congruent")),
|
|
46157
|
-
coords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
46158
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))
|
|
46159
|
-
});
|
|
46160
|
-
const parsePerseusGraphTypeCircle = object({
|
|
46161
|
-
type: constant("circle"),
|
|
46162
|
-
center: optional(pairOfNumbers),
|
|
46163
|
-
radius: optional(number),
|
|
46164
|
-
startCoords: optional(object({
|
|
46165
|
-
center: pairOfNumbers,
|
|
46166
|
-
radius: number
|
|
46167
|
-
})),
|
|
46168
|
-
// TODO: remove coord? it's legacy.
|
|
46169
|
-
coord: optional(pairOfNumbers)
|
|
46170
|
-
});
|
|
46171
|
-
const parsePerseusGraphTypeLinear = object({
|
|
46172
|
-
type: constant("linear"),
|
|
46173
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
46174
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
46175
|
-
// TODO: remove coord? it's legacy.
|
|
46176
|
-
coord: optional(pairOfNumbers)
|
|
46177
|
-
});
|
|
46178
|
-
const parsePerseusGraphTypeLinearSystem = object({
|
|
46179
|
-
type: constant("linear-system"),
|
|
46180
|
-
// TODO(benchristel): default coords to empty array?
|
|
46181
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
46182
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
46183
|
-
// TODO: remove coord? it's legacy.
|
|
46184
|
-
coord: optional(pairOfNumbers)
|
|
46185
|
-
});
|
|
46186
|
-
const parsePerseusGraphTypeNone = object({
|
|
46187
|
-
type: constant("none")
|
|
46188
|
-
});
|
|
46189
|
-
const parsePerseusGraphTypePoint = object({
|
|
46190
|
-
type: constant("point"),
|
|
46191
|
-
numPoints: optional(union(number).or(constant("unlimited")).parser),
|
|
46192
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
46193
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46194
|
-
// TODO: remove coord? it's legacy.
|
|
46195
|
-
coord: optional(pairOfNumbers)
|
|
46196
|
-
});
|
|
46197
|
-
const parsePerseusGraphTypePolygon = object({
|
|
46198
|
-
type: constant("polygon"),
|
|
46199
|
-
numSides: optional(union(number).or(constant("unlimited")).parser),
|
|
46200
|
-
showAngles: optional(boolean),
|
|
46201
|
-
showSides: optional(boolean),
|
|
46202
|
-
snapTo: optional(enumeration("grid", "angles", "sides")),
|
|
46203
|
-
match: optional(enumeration("similar", "congruent", "approx", "exact")),
|
|
46204
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46205
|
-
// TODO: remove coord? it's legacy.
|
|
46206
|
-
coord: optional(pairOfNumbers)
|
|
46207
|
-
});
|
|
46208
|
-
const parsePerseusGraphTypeQuadratic = object({
|
|
46209
|
-
type: constant("quadratic"),
|
|
46210
|
-
coords: optional(nullable(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers))),
|
|
46211
|
-
startCoords: optional(trio(pairOfNumbers, pairOfNumbers, pairOfNumbers)),
|
|
46212
|
-
// TODO: remove coord? it's legacy.
|
|
46213
|
-
coord: optional(pairOfNumbers)
|
|
46214
|
-
});
|
|
46215
|
-
const parsePerseusGraphTypeRay = object({
|
|
46216
|
-
type: constant("ray"),
|
|
46217
|
-
coords: optional(nullable(pair(pairOfNumbers, pairOfNumbers))),
|
|
46218
|
-
startCoords: optional(pair(pairOfNumbers, pairOfNumbers)),
|
|
46219
|
-
// TODO: remove coord? it's legacy.
|
|
46220
|
-
coord: optional(pairOfNumbers)
|
|
46221
|
-
});
|
|
46222
|
-
const parsePerseusGraphTypeSegment = object({
|
|
46223
|
-
type: constant("segment"),
|
|
46224
|
-
// TODO(benchristel): default numSegments?
|
|
46225
|
-
numSegments: optional(number),
|
|
46226
|
-
coords: optional(nullable(array(pair(pairOfNumbers, pairOfNumbers)))),
|
|
46227
|
-
startCoords: optional(array(pair(pairOfNumbers, pairOfNumbers))),
|
|
46228
|
-
// TODO: remove coord? it's legacy.
|
|
46229
|
-
coord: optional(pairOfNumbers)
|
|
46230
|
-
});
|
|
46231
|
-
const parsePerseusGraphTypeSinusoid = object({
|
|
46232
|
-
type: constant("sinusoid"),
|
|
46233
|
-
coords: optional(nullable(array(pairOfNumbers))),
|
|
46234
|
-
startCoords: optional(array(pairOfNumbers)),
|
|
46235
|
-
// TODO: remove coord? it's legacy.
|
|
46236
|
-
coord: optional(pairOfNumbers)
|
|
46237
|
-
});
|
|
46238
|
-
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;
|
|
46239
|
-
const parseLockedFigureColor = enumeration(...perseusCore.lockedFigureColorNames);
|
|
46240
|
-
const parseLockedFigureFillType = enumeration("none", "white", "translucent", "solid");
|
|
46241
|
-
const parseLockedLineStyle = enumeration("solid", "dashed");
|
|
46242
|
-
const parseLockedLabelType = object({
|
|
46243
|
-
type: constant("label"),
|
|
46244
|
-
coord: pairOfNumbers,
|
|
46245
|
-
text: string,
|
|
46246
|
-
color: parseLockedFigureColor,
|
|
46247
|
-
size: enumeration("small", "medium", "large")
|
|
46248
|
-
});
|
|
46249
|
-
const parseLockedPointType = object({
|
|
46250
|
-
type: constant("point"),
|
|
46251
|
-
coord: pairOfNumbers,
|
|
46252
|
-
color: parseLockedFigureColor,
|
|
46253
|
-
filled: boolean,
|
|
46254
|
-
// TODO(benchristel): default labels to empty array?
|
|
46255
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46256
|
-
ariaLabel: optional(string)
|
|
46257
|
-
});
|
|
46258
|
-
const parseLockedLineType = object({
|
|
46259
|
-
type: constant("line"),
|
|
46260
|
-
kind: enumeration("line", "ray", "segment"),
|
|
46261
|
-
points: pair(parseLockedPointType, parseLockedPointType),
|
|
46262
|
-
color: parseLockedFigureColor,
|
|
46263
|
-
lineStyle: parseLockedLineStyle,
|
|
46264
|
-
showPoint1: defaulted(boolean, () => false),
|
|
46265
|
-
showPoint2: defaulted(boolean, () => false),
|
|
46266
|
-
// TODO(benchristel): default labels to empty array?
|
|
46267
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46268
|
-
ariaLabel: optional(string)
|
|
46269
|
-
});
|
|
46270
|
-
const parseLockedVectorType = object({
|
|
46271
|
-
type: constant("vector"),
|
|
46272
|
-
points: pair(pairOfNumbers, pairOfNumbers),
|
|
46273
|
-
color: parseLockedFigureColor,
|
|
46274
|
-
// TODO(benchristel): default labels to empty array?
|
|
46275
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46276
|
-
ariaLabel: optional(string)
|
|
46277
|
-
});
|
|
46278
|
-
const parseLockedEllipseType = object({
|
|
46279
|
-
type: constant("ellipse"),
|
|
46280
|
-
center: pairOfNumbers,
|
|
46281
|
-
radius: pairOfNumbers,
|
|
46282
|
-
angle: number,
|
|
46283
|
-
color: parseLockedFigureColor,
|
|
46284
|
-
fillStyle: parseLockedFigureFillType,
|
|
46285
|
-
strokeStyle: parseLockedLineStyle,
|
|
46286
|
-
// TODO(benchristel): default labels to empty array?
|
|
46287
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46288
|
-
ariaLabel: optional(string)
|
|
46289
|
-
});
|
|
46290
|
-
const parseLockedPolygonType = object({
|
|
46291
|
-
type: constant("polygon"),
|
|
46292
|
-
points: array(pairOfNumbers),
|
|
46293
|
-
color: parseLockedFigureColor,
|
|
46294
|
-
showVertices: boolean,
|
|
46295
|
-
fillStyle: parseLockedFigureFillType,
|
|
46296
|
-
strokeStyle: parseLockedLineStyle,
|
|
46297
|
-
// TODO(benchristel): default labels to empty array?
|
|
46298
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46299
|
-
ariaLabel: optional(string)
|
|
46300
|
-
});
|
|
46301
|
-
const parseLockedFunctionType = object({
|
|
46302
|
-
type: constant("function"),
|
|
46303
|
-
color: parseLockedFigureColor,
|
|
46304
|
-
strokeStyle: parseLockedLineStyle,
|
|
46305
|
-
equation: string,
|
|
46306
|
-
directionalAxis: enumeration("x", "y"),
|
|
46307
|
-
domain: optional(pairOfNumbers),
|
|
46308
|
-
// TODO(benchristel): default labels to empty array?
|
|
46309
|
-
labels: optional(array(parseLockedLabelType)),
|
|
46310
|
-
ariaLabel: optional(string)
|
|
46311
|
-
});
|
|
46312
|
-
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;
|
|
46313
|
-
const parseInteractiveGraphWidget = parseWidget(constant("interactive-graph"), object({
|
|
46314
|
-
step: pairOfNumbers,
|
|
46315
|
-
// TODO(benchristel): rather than making gridStep and snapStep
|
|
46316
|
-
// optional, we should duplicate the defaulting logic from the
|
|
46317
|
-
// InteractiveGraph component. See parse-perseus-json/README.md for
|
|
46318
|
-
// why.
|
|
46319
|
-
gridStep: optional(pairOfNumbers),
|
|
46320
|
-
snapStep: optional(pairOfNumbers),
|
|
46321
|
-
backgroundImage: optional(parsePerseusImageBackground),
|
|
46322
|
-
markings: enumeration("graph", "grid", "none"),
|
|
46323
|
-
labels: optional(array(string)),
|
|
46324
|
-
showProtractor: boolean,
|
|
46325
|
-
showRuler: optional(boolean),
|
|
46326
|
-
showTooltips: optional(boolean),
|
|
46327
|
-
rulerLabel: optional(string),
|
|
46328
|
-
rulerTicks: optional(number),
|
|
46329
|
-
range: pair(pairOfNumbers, pairOfNumbers),
|
|
46330
|
-
// NOTE(benchristel): I copied the default graph from
|
|
46331
|
-
// interactive-graph.tsx. See the parse-perseus-json/README.md for
|
|
46332
|
-
// an explanation of why we want to duplicate the default here.
|
|
46333
|
-
graph: defaulted(parsePerseusGraphType, () => ({
|
|
46334
|
-
type: "linear"
|
|
46335
|
-
})),
|
|
46336
|
-
correct: parsePerseusGraphType,
|
|
46337
|
-
// TODO(benchristel): default lockedFigures to empty array
|
|
46338
|
-
lockedFigures: optional(array(parseLockedFigure)),
|
|
46339
|
-
fullGraphLabel: optional(string),
|
|
46340
|
-
fullGraphAriaDescription: optional(string)
|
|
46341
|
-
}));
|
|
46342
|
-
|
|
46343
|
-
const parseLabelImageWidget = parseWidget(constant("label-image"), object({
|
|
46344
|
-
choices: array(string),
|
|
46345
|
-
imageUrl: string,
|
|
46346
|
-
imageAlt: string,
|
|
46347
|
-
imageHeight: number,
|
|
46348
|
-
imageWidth: number,
|
|
46349
|
-
markers: array(object({
|
|
46350
|
-
answers: array(string),
|
|
46351
|
-
label: string,
|
|
46352
|
-
x: number,
|
|
46353
|
-
y: number
|
|
46354
|
-
})),
|
|
46355
|
-
hideChoicesFromInstructions: boolean,
|
|
46356
|
-
multipleAnswers: boolean,
|
|
46357
|
-
static: defaulted(boolean, () => false)
|
|
46358
|
-
}));
|
|
46359
|
-
|
|
46360
|
-
const parseMatcherWidget = parseWidget(constant("matcher"), object({
|
|
46361
|
-
labels: array(string),
|
|
46362
|
-
left: array(string),
|
|
46363
|
-
right: array(string),
|
|
46364
|
-
orderMatters: boolean,
|
|
46365
|
-
padding: boolean
|
|
46366
|
-
}));
|
|
46367
|
-
|
|
46368
|
-
const numberOrString = union(number).or(string).parser;
|
|
46369
|
-
const numeric = pipeParsers(defaulted(numberOrString, () => NaN)).then(stringToNumber).parser;
|
|
46370
|
-
const parseMatrixWidget = parseWidget(defaulted(constant("matrix"), () => "matrix"), object({
|
|
46371
|
-
prefix: optional(string),
|
|
46372
|
-
suffix: optional(string),
|
|
46373
|
-
answers: array(array(numeric)),
|
|
46374
|
-
cursorPosition: optional(array(number)),
|
|
46375
|
-
matrixBoardSize: array(number),
|
|
46376
|
-
static: optional(boolean)
|
|
46377
|
-
}));
|
|
46378
|
-
|
|
46379
|
-
const parseMeasurerWidget = parseWidget(constant("measurer"), object({
|
|
46380
|
-
// The default value for image comes from measurer.tsx.
|
|
46381
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46382
|
-
// defaults here.
|
|
46383
|
-
image: defaulted(parsePerseusImageBackground, () => ({
|
|
46384
|
-
url: null,
|
|
46385
|
-
top: 0,
|
|
46386
|
-
left: 0
|
|
46387
|
-
})),
|
|
46388
|
-
showProtractor: boolean,
|
|
46389
|
-
showRuler: boolean,
|
|
46390
|
-
rulerLabel: string,
|
|
46391
|
-
rulerTicks: number,
|
|
46392
|
-
rulerPixels: number,
|
|
46393
|
-
rulerLength: number,
|
|
46394
|
-
box: pair(number, number),
|
|
46395
|
-
// TODO(benchristel): static is not used. Remove it?
|
|
46396
|
-
static: defaulted(boolean, () => false)
|
|
46397
|
-
}));
|
|
46398
|
-
|
|
46399
|
-
const parseMoleculeRendererWidget = parseWidget(constant("molecule-renderer"), object({
|
|
46400
|
-
widgetId: string,
|
|
46401
|
-
rotationAngle: optional(number),
|
|
46402
|
-
smiles: optional(string)
|
|
46403
|
-
}));
|
|
46404
|
-
|
|
46405
|
-
const emptyStringToNull = pipeParsers(constant("")).then(convert(() => null)).parser;
|
|
46406
|
-
const parseNumberLineWidget = parseWidget(constant("number-line"), object({
|
|
46407
|
-
range: array(number),
|
|
46408
|
-
labelRange: array(nullable(union(number).or(emptyStringToNull).parser)),
|
|
46409
|
-
labelStyle: string,
|
|
46410
|
-
labelTicks: boolean,
|
|
46411
|
-
isTickCtrl: optional(nullable(boolean)),
|
|
46412
|
-
divisionRange: array(number),
|
|
46413
|
-
numDivisions: optional(nullable(number)),
|
|
46414
|
-
// NOTE(benchristel): I copied the default snapDivisions from
|
|
46415
|
-
// number-line.tsx. See the parse-perseus-json/README.md for
|
|
46416
|
-
// an explanation of why we want to duplicate the default here.
|
|
46417
|
-
snapDivisions: defaulted(number, () => 2),
|
|
46418
|
-
tickStep: optional(nullable(number)),
|
|
46419
|
-
correctRel: optional(nullable(string)),
|
|
46420
|
-
correctX: nullable(number),
|
|
46421
|
-
initialX: optional(nullable(number)),
|
|
46422
|
-
showTooltip: optional(boolean),
|
|
46423
|
-
static: defaulted(boolean, () => false)
|
|
46424
|
-
}));
|
|
46425
|
-
|
|
46426
|
-
const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
|
|
46427
|
-
const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
|
|
46428
|
-
answers: array(object({
|
|
46429
|
-
message: string,
|
|
46430
|
-
// TODO(benchristel): value should never be null or undefined,
|
|
46431
|
-
// but we have some content where it is anyway. If we backfill
|
|
46432
|
-
// the data, simplify this.
|
|
46433
|
-
value: optional(nullable(number)),
|
|
46434
|
-
status: string,
|
|
46435
|
-
answerForms: optional(array(parseMathFormat)),
|
|
46436
|
-
strict: boolean,
|
|
46437
|
-
maxError: optional(nullable(number)),
|
|
46438
|
-
// TODO(benchristel): simplify should never be a boolean, but we
|
|
46439
|
-
// have some content where it is anyway. If we ever backfill
|
|
46440
|
-
// the data, we should simplify `simplify`.
|
|
46441
|
-
simplify: optional(nullable(union(string).or(pipeParsers(boolean).then(convert(String)).parser).parser))
|
|
46442
|
-
})),
|
|
46443
|
-
labelText: optional(string),
|
|
46444
|
-
size: string,
|
|
46445
|
-
coefficient: defaulted(boolean, () => false),
|
|
46446
|
-
rightAlign: optional(boolean),
|
|
46447
|
-
static: defaulted(boolean, () => false),
|
|
46448
|
-
answerForms: optional(array(object({
|
|
46449
|
-
name: parseMathFormat,
|
|
46450
|
-
simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
|
|
46451
|
-
})))
|
|
46452
|
-
}));
|
|
46453
|
-
|
|
46454
|
-
// There is an import cycle between orderer-widget.ts and perseus-renderer.ts.
|
|
46455
|
-
// This wrapper ensures that we don't refer to parsePerseusRenderer before
|
|
46456
|
-
// it's defined.
|
|
46457
|
-
function parseRenderer(rawValue, ctx) {
|
|
46458
|
-
return parsePerseusRenderer(rawValue, ctx);
|
|
46459
|
-
}
|
|
46460
|
-
const largeToAuto = (height, ctx) => {
|
|
46461
|
-
if (height === "large") {
|
|
46462
|
-
return ctx.success("auto");
|
|
46463
|
-
}
|
|
46464
|
-
return ctx.success(height);
|
|
46465
|
-
};
|
|
46466
|
-
const parseOrdererWidget = parseWidget(constant("orderer"), object({
|
|
46467
|
-
options: defaulted(array(parseRenderer), () => []),
|
|
46468
|
-
correctOptions: array(parseRenderer),
|
|
46469
|
-
otherOptions: array(parseRenderer),
|
|
46470
|
-
height: pipeParsers(enumeration("normal", "auto", "large")).then(largeToAuto).parser,
|
|
46471
|
-
layout: defaulted(enumeration("horizontal", "vertical"), () => "horizontal")
|
|
46472
|
-
}));
|
|
46473
|
-
|
|
46474
|
-
const parsePassageRefWidget = parseWidget(constant("passage-ref"), object({
|
|
46475
|
-
passageNumber: number,
|
|
46476
|
-
referenceNumber: number,
|
|
46477
|
-
summaryText: optional(string)
|
|
46478
|
-
}));
|
|
46479
|
-
|
|
46480
|
-
const parsePassageWidget = parseWidget(constant("passage"), object({
|
|
46481
|
-
footnotes: defaulted(string, () => ""),
|
|
46482
|
-
passageText: string,
|
|
46483
|
-
passageTitle: defaulted(string, () => ""),
|
|
46484
|
-
showLineNumbers: boolean,
|
|
46485
|
-
static: defaulted(boolean, () => false)
|
|
46486
|
-
}));
|
|
46487
|
-
|
|
46488
|
-
const parsePhetSimulationWidget = parseWidget(constant("phet-simulation"), object({
|
|
46489
|
-
url: string,
|
|
46490
|
-
description: string
|
|
46491
|
-
}));
|
|
46492
|
-
|
|
46493
|
-
const parsePlotterWidget = parseWidget(constant("plotter"), object({
|
|
46494
|
-
labels: array(string),
|
|
46495
|
-
categories: array(string),
|
|
46496
|
-
type: enumeration(...perseusCore.plotterPlotTypes),
|
|
46497
|
-
maxY: number,
|
|
46498
|
-
// The default value for scaleY comes from plotter.tsx.
|
|
46499
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46500
|
-
// defaults here.
|
|
46501
|
-
scaleY: defaulted(number, () => 1),
|
|
46502
|
-
labelInterval: optional(nullable(number)),
|
|
46503
|
-
// The default value for snapsPerLine comes from plotter.tsx.
|
|
46504
|
-
// See parse-perseus-json/README.md for why we want to duplicate the
|
|
46505
|
-
// defaults here.
|
|
46506
|
-
snapsPerLine: defaulted(number, () => 2),
|
|
46507
|
-
starting: array(number),
|
|
46508
|
-
correct: array(number),
|
|
46509
|
-
picUrl: optional(nullable(string)),
|
|
46510
|
-
picSize: optional(nullable(number)),
|
|
46511
|
-
picBoxHeight: optional(nullable(number)),
|
|
46512
|
-
// NOTE(benchristel): I copied the default plotDimensions from
|
|
46513
|
-
// plotter.tsx. See the parse-perseus-json/README.md for an explanation
|
|
46514
|
-
// of why we want to duplicate the defaults here.
|
|
46515
|
-
plotDimensions: defaulted(array(number), () => [380, 300])
|
|
46516
|
-
}));
|
|
46517
|
-
|
|
46518
|
-
const parsePythonProgramWidget = parseWidget(constant("python-program"), object({
|
|
46519
|
-
programID: string,
|
|
46520
|
-
height: number
|
|
46521
|
-
}));
|
|
46522
|
-
|
|
46523
|
-
const parseRadioWidget = parseWidget(constant("radio"), object({
|
|
46524
|
-
choices: array(object({
|
|
46525
|
-
content: defaulted(string, () => ""),
|
|
46526
|
-
clue: optional(string),
|
|
46527
|
-
correct: optional(boolean),
|
|
46528
|
-
isNoneOfTheAbove: optional(boolean),
|
|
46529
|
-
// deprecated
|
|
46530
|
-
// There is an import cycle between radio-widget.ts and
|
|
46531
|
-
// widgets-map.ts. The anonymous function below ensures that we
|
|
46532
|
-
// don't refer to parseWidgetsMap before it's defined.
|
|
46533
|
-
widgets: optional((rawVal, ctx) => parseWidgetsMap(rawVal, ctx))
|
|
46534
|
-
})),
|
|
46535
|
-
hasNoneOfTheAbove: optional(boolean),
|
|
46536
|
-
countChoices: optional(boolean),
|
|
46537
|
-
randomize: optional(boolean),
|
|
46538
|
-
multipleSelect: optional(boolean),
|
|
46539
|
-
deselectEnabled: optional(boolean),
|
|
46540
|
-
// deprecated
|
|
46541
|
-
onePerLine: optional(boolean),
|
|
46542
|
-
// deprecated
|
|
46543
|
-
displayCount: optional(any),
|
|
46544
|
-
// v0 props
|
|
46545
|
-
// `noneOfTheAbove` is still in use (but only set to `false`).
|
|
46546
|
-
noneOfTheAbove: optional(constant(false))
|
|
46547
|
-
}));
|
|
46548
|
-
|
|
46549
|
-
const parseSorterWidget = parseWidget(constant("sorter"), object({
|
|
46550
|
-
correct: array(string),
|
|
46551
|
-
padding: boolean,
|
|
46552
|
-
layout: enumeration("horizontal", "vertical")
|
|
46553
|
-
}));
|
|
46554
|
-
|
|
46555
|
-
const parseTableWidget = parseWidget(constant("table"), object({
|
|
46556
|
-
headers: array(string),
|
|
46557
|
-
rows: number,
|
|
46558
|
-
columns: number,
|
|
46559
|
-
answers: array(array(string))
|
|
46560
|
-
}));
|
|
46561
|
-
|
|
46562
|
-
const parseVideoWidget = parseWidget(constant("video"), object({
|
|
46563
|
-
location: string,
|
|
46564
|
-
static: optional(boolean)
|
|
46565
|
-
}));
|
|
46566
|
-
|
|
46567
|
-
const parseWidgetsMap = (rawValue, ctx) => {
|
|
46568
|
-
if (!isObject(rawValue)) {
|
|
46569
|
-
return ctx.failure("PerseusWidgetsMap", rawValue);
|
|
46570
|
-
}
|
|
46571
|
-
const widgetsMap = {};
|
|
46572
|
-
for (const key of Object.keys(rawValue)) {
|
|
46573
|
-
// parseWidgetsMapEntry modifies the widgetsMap. This is kind of gross,
|
|
46574
|
-
// but it's the only way I could find to make TypeScript check the key
|
|
46575
|
-
// against the widget type.
|
|
46576
|
-
const entryResult = parseWidgetsMapEntry([key, rawValue[key]], widgetsMap, ctx.forSubtree(key));
|
|
46577
|
-
if (isFailure(entryResult)) {
|
|
46578
|
-
return entryResult;
|
|
46579
|
-
}
|
|
46580
|
-
}
|
|
46581
|
-
return ctx.success(widgetsMap);
|
|
46582
|
-
};
|
|
46583
|
-
const parseWidgetsMapEntry = (_ref, widgetMap, ctx) => {
|
|
46584
|
-
let [id, widget] = _ref;
|
|
46585
|
-
const idComponentsResult = parseWidgetIdComponents(id.split(" "), ctx.forSubtree("(widget ID)"));
|
|
46586
|
-
if (isFailure(idComponentsResult)) {
|
|
46587
|
-
return idComponentsResult;
|
|
46588
|
-
}
|
|
46589
|
-
const [type, n] = idComponentsResult.value;
|
|
46590
|
-
function parseAndAssign(key, parse) {
|
|
46591
|
-
const widgetResult = parse(widget, ctx);
|
|
46592
|
-
if (isFailure(widgetResult)) {
|
|
46593
|
-
return widgetResult;
|
|
46594
|
-
}
|
|
46595
|
-
widgetMap[key] = widgetResult.value;
|
|
46596
|
-
return ctx.success(undefined);
|
|
46597
|
-
}
|
|
46598
|
-
switch (type) {
|
|
46599
|
-
case "categorizer":
|
|
46600
|
-
return parseAndAssign(`categorizer ${n}`, parseCategorizerWidget);
|
|
46601
|
-
case "cs-program":
|
|
46602
|
-
return parseAndAssign(`cs-program ${n}`, parseCSProgramWidget);
|
|
46603
|
-
case "definition":
|
|
46604
|
-
return parseAndAssign(`definition ${n}`, parseDefinitionWidget);
|
|
46605
|
-
case "dropdown":
|
|
46606
|
-
return parseAndAssign(`dropdown ${n}`, parseDropdownWidget);
|
|
46607
|
-
case "explanation":
|
|
46608
|
-
return parseAndAssign(`explanation ${n}`, parseExplanationWidget);
|
|
46609
|
-
case "expression":
|
|
46610
|
-
return parseAndAssign(`expression ${n}`, parseExpressionWidget);
|
|
46611
|
-
case "grapher":
|
|
46612
|
-
return parseAndAssign(`grapher ${n}`, parseGrapherWidget);
|
|
46613
|
-
case "group":
|
|
46614
|
-
return parseAndAssign(`group ${n}`, parseGroupWidget);
|
|
46615
|
-
case "graded-group":
|
|
46616
|
-
return parseAndAssign(`graded-group ${n}`, parseGradedGroupWidget);
|
|
46617
|
-
case "graded-group-set":
|
|
46618
|
-
return parseAndAssign(`graded-group-set ${n}`, parseGradedGroupSetWidget);
|
|
46619
|
-
case "iframe":
|
|
46620
|
-
return parseAndAssign(`iframe ${n}`, parseIframeWidget);
|
|
46621
|
-
case "image":
|
|
46622
|
-
return parseAndAssign(`image ${n}`, parseImageWidget);
|
|
46623
|
-
case "input-number":
|
|
46624
|
-
return parseAndAssign(`input-number ${n}`, parseInputNumberWidget);
|
|
46625
|
-
case "interaction":
|
|
46626
|
-
return parseAndAssign(`interaction ${n}`, parseInteractionWidget);
|
|
46627
|
-
case "interactive-graph":
|
|
46628
|
-
return parseAndAssign(`interactive-graph ${n}`, parseInteractiveGraphWidget);
|
|
46629
|
-
case "label-image":
|
|
46630
|
-
return parseAndAssign(`label-image ${n}`, parseLabelImageWidget);
|
|
46631
|
-
case "matcher":
|
|
46632
|
-
return parseAndAssign(`matcher ${n}`, parseMatcherWidget);
|
|
46633
|
-
case "matrix":
|
|
46634
|
-
return parseAndAssign(`matrix ${n}`, parseMatrixWidget);
|
|
46635
|
-
case "measurer":
|
|
46636
|
-
return parseAndAssign(`measurer ${n}`, parseMeasurerWidget);
|
|
46637
|
-
case "molecule-renderer":
|
|
46638
|
-
return parseAndAssign(`molecule-renderer ${n}`, parseMoleculeRendererWidget);
|
|
46639
|
-
case "number-line":
|
|
46640
|
-
return parseAndAssign(`number-line ${n}`, parseNumberLineWidget);
|
|
46641
|
-
case "numeric-input":
|
|
46642
|
-
return parseAndAssign(`numeric-input ${n}`, parseNumericInputWidget);
|
|
46643
|
-
case "orderer":
|
|
46644
|
-
return parseAndAssign(`orderer ${n}`, parseOrdererWidget);
|
|
46645
|
-
case "passage":
|
|
46646
|
-
return parseAndAssign(`passage ${n}`, parsePassageWidget);
|
|
46647
|
-
case "passage-ref":
|
|
46648
|
-
return parseAndAssign(`passage-ref ${n}`, parsePassageRefWidget);
|
|
46649
|
-
case "passage-ref-target":
|
|
46650
|
-
// NOTE(benchristel): as of 2024-11-12, passage-ref-target is only
|
|
46651
|
-
// used in test content. See:
|
|
46652
|
-
// https://www.khanacademy.org/devadmin/content/search?query=widget:passage-ref-target
|
|
46653
|
-
return parseAndAssign(`passage-ref-target ${n}`, any);
|
|
46654
|
-
case "phet-simulation":
|
|
46655
|
-
return parseAndAssign(`phet-simulation ${n}`, parsePhetSimulationWidget);
|
|
46656
|
-
case "plotter":
|
|
46657
|
-
return parseAndAssign(`plotter ${n}`, parsePlotterWidget);
|
|
46658
|
-
case "python-program":
|
|
46659
|
-
return parseAndAssign(`python-program ${n}`, parsePythonProgramWidget);
|
|
46660
|
-
case "radio":
|
|
46661
|
-
return parseAndAssign(`radio ${n}`, parseRadioWidget);
|
|
46662
|
-
case "sorter":
|
|
46663
|
-
return parseAndAssign(`sorter ${n}`, parseSorterWidget);
|
|
46664
|
-
case "table":
|
|
46665
|
-
return parseAndAssign(`table ${n}`, parseTableWidget);
|
|
46666
|
-
case "video":
|
|
46667
|
-
return parseAndAssign(`video ${n}`, parseVideoWidget);
|
|
46668
|
-
case "sequence":
|
|
46669
|
-
// sequence is a deprecated widget type, and the corresponding
|
|
46670
|
-
// widget component no longer exists.
|
|
46671
|
-
return parseAndAssign(`sequence ${n}`, parseDeprecatedWidget);
|
|
46672
|
-
case "lights-puzzle":
|
|
46673
|
-
return parseAndAssign(`lights-puzzle ${n}`, parseDeprecatedWidget);
|
|
46674
|
-
case "simulator":
|
|
46675
|
-
return parseAndAssign(`simulator ${n}`, parseDeprecatedWidget);
|
|
46676
|
-
case "transformer":
|
|
46677
|
-
return parseAndAssign(`transformer ${n}`, parseDeprecatedWidget);
|
|
46678
|
-
default:
|
|
46679
|
-
if (getWidget(type)) {
|
|
46680
|
-
// @ts-expect-error - 'type' is not a valid widget type
|
|
46681
|
-
return parseAndAssign(`${type} ${n}`, any);
|
|
46682
|
-
}
|
|
46683
|
-
return ctx.failure("a valid widget type", type);
|
|
46684
|
-
}
|
|
46685
|
-
};
|
|
46686
|
-
const parseDeprecatedWidget = parseWidget(
|
|
46687
|
-
// Ignore the incoming widget type and hardcode "deprecated-standin"
|
|
46688
|
-
(_, ctx) => ctx.success("deprecated-standin"),
|
|
46689
|
-
// Allow any widget options
|
|
46690
|
-
object({}));
|
|
46691
|
-
const parseStringToPositiveInt = (rawValue, ctx) => {
|
|
46692
|
-
if (typeof rawValue !== "string" || !/^[1-9][0-9]*$/.test(rawValue)) {
|
|
46693
|
-
return ctx.failure("a string representing a positive integer", rawValue);
|
|
46694
|
-
}
|
|
46695
|
-
return ctx.success(+rawValue);
|
|
46696
|
-
};
|
|
46697
|
-
const parseWidgetIdComponents = pair(string, parseStringToPositiveInt);
|
|
46698
|
-
|
|
46699
|
-
const parsePerseusRenderer = defaulted(object({
|
|
46700
|
-
// TODO(benchristel): content is also defaulted to empty string in
|
|
46701
|
-
// renderer.tsx. See if we can remove one default or the other.
|
|
46702
|
-
content: defaulted(string, () => ""),
|
|
46703
|
-
// This module has an import cycle with parseWidgetsMap, because the
|
|
46704
|
-
// `group` widget can contain another renderer.
|
|
46705
|
-
// The anonymous function below ensures that we don't try to access
|
|
46706
|
-
// parseWidgetsMap before it's defined.
|
|
46707
|
-
widgets: defaulted((rawVal, ctx) => parseWidgetsMap(rawVal, ctx), () => ({})),
|
|
46708
|
-
metadata: optional(array(string)),
|
|
46709
|
-
images: parseImages
|
|
46710
|
-
}),
|
|
46711
|
-
// Default value
|
|
46712
|
-
() => ({
|
|
46713
|
-
content: "",
|
|
46714
|
-
widgets: {},
|
|
46715
|
-
images: {}
|
|
46716
|
-
}));
|
|
46717
|
-
|
|
46718
|
-
const parsePerseusArticle = union(parsePerseusRenderer).or(array(parsePerseusRenderer)).parser;
|
|
46719
|
-
|
|
46720
|
-
const parseHint = object({
|
|
46721
|
-
replace: optional(boolean),
|
|
46722
|
-
content: string,
|
|
46723
|
-
widgets: defaulted(parseWidgetsMap, () => ({})),
|
|
46724
|
-
metadata: optional(array(string)),
|
|
46725
|
-
images: parseImages
|
|
46726
|
-
});
|
|
46727
|
-
|
|
46728
|
-
const parsePerseusItem$1 = object({
|
|
46729
|
-
question: parsePerseusRenderer,
|
|
46730
|
-
hints: defaulted(array(parseHint), () => []),
|
|
46731
|
-
answerArea: pipeParsers(defaulted(object({}), () => ({}))).then(migrateAnswerArea).then(record(enumeration(...perseusCore.ItemExtras), boolean)).parser,
|
|
46732
|
-
itemDataVersion: optional(object({
|
|
46733
|
-
major: number,
|
|
46734
|
-
minor: number
|
|
46735
|
-
})),
|
|
46736
|
-
// Deprecated field
|
|
46737
|
-
answer: any
|
|
46738
|
-
});
|
|
46739
|
-
|
|
46740
|
-
// Some answerAreas have extra fields, like:
|
|
46741
|
-
//
|
|
46742
|
-
// "answerArea": {
|
|
46743
|
-
// "type": "multiple",
|
|
46744
|
-
// "options": {
|
|
46745
|
-
// "content": "",
|
|
46746
|
-
// "images": {},
|
|
46747
|
-
// "widgets": {}
|
|
46748
|
-
// }
|
|
46749
|
-
// }
|
|
46750
|
-
//
|
|
46751
|
-
// The "type" and "options" fields don't seem to be used anywhere. This
|
|
46752
|
-
// migration function removes them.
|
|
46753
|
-
function migrateAnswerArea(rawValue, ctx) {
|
|
46754
|
-
const {
|
|
46755
|
-
type: _,
|
|
46756
|
-
options: __,
|
|
46757
|
-
...rest
|
|
46758
|
-
} = rawValue;
|
|
46759
|
-
return ctx.success(rest);
|
|
46760
|
-
}
|
|
46761
|
-
|
|
46762
|
-
/**
|
|
46763
|
-
* Helper to parse PerseusItem JSON
|
|
46764
|
-
* Why not just use JSON.parse? We want:
|
|
46765
|
-
* - To make sure types are correct
|
|
46766
|
-
* - To give us a central place to validate/transform output if needed
|
|
46767
|
-
* @deprecated - use parseAndMigratePerseusItem instead
|
|
46768
|
-
* @param {string} json - the stringified PerseusItem JSON
|
|
46769
|
-
* @returns {PerseusItem} the parsed PerseusItem object
|
|
46770
|
-
*/
|
|
46771
|
-
function parsePerseusItem(json) {
|
|
46772
|
-
// Try to block a cheating vector which relies on monkey-patching
|
|
46773
|
-
// JSON.parse
|
|
46774
|
-
if (isRealJSONParse(JSON.parse)) {
|
|
46775
|
-
return JSON.parse(json);
|
|
46776
|
-
}
|
|
46777
|
-
throw new Error("Something went wrong.");
|
|
46778
|
-
}
|
|
46779
|
-
/**
|
|
46780
|
-
* Parses a PerseusItem from a JSON string, migrates old formats to the latest
|
|
46781
|
-
* schema, and runtime-typechecks the result. Use this to parse assessmentItem
|
|
46782
|
-
* data.
|
|
46783
|
-
*
|
|
46784
|
-
* @returns a {@link Result} of the parsed PerseusItem. If the result is a
|
|
46785
|
-
* failure, it will contain an error message describing where in the tree
|
|
46786
|
-
* parsing failed.
|
|
46787
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46788
|
-
*/
|
|
46789
|
-
function parseAndMigratePerseusItem(json) {
|
|
46790
|
-
throwErrorIfCheatingDetected();
|
|
46791
|
-
const object = JSON.parse(json);
|
|
46792
|
-
const result = parse(object, parsePerseusItem$1);
|
|
46793
|
-
if (isFailure(result)) {
|
|
46794
|
-
return failure({
|
|
46795
|
-
message: result.detail,
|
|
46796
|
-
invalidObject: object
|
|
46797
|
-
});
|
|
46798
|
-
}
|
|
46799
|
-
return result;
|
|
46800
|
-
}
|
|
46801
|
-
|
|
46802
|
-
/**
|
|
46803
|
-
* Parses a PerseusArticle from a JSON string, migrates old formats to the
|
|
46804
|
-
* latest schema, and runtime-typechecks the result.
|
|
46805
|
-
*
|
|
46806
|
-
* @returns a {@link Result} of the parsed PerseusArticle. If the result is a
|
|
46807
|
-
* failure, it will contain an error message describing where in the tree
|
|
46808
|
-
* parsing failed.
|
|
46809
|
-
* @throws SyntaxError if the argument is not well-formed JSON.
|
|
46810
|
-
*/
|
|
46811
|
-
function parseAndMigratePerseusArticle(json) {
|
|
46812
|
-
throwErrorIfCheatingDetected();
|
|
46813
|
-
const object = JSON.parse(json);
|
|
46814
|
-
const result = parse(object, parsePerseusArticle);
|
|
46815
|
-
if (isFailure(result)) {
|
|
46816
|
-
return failure({
|
|
46817
|
-
message: result.detail,
|
|
46818
|
-
invalidObject: object
|
|
46819
|
-
});
|
|
46820
|
-
}
|
|
46821
|
-
return result;
|
|
46822
|
-
}
|
|
46823
|
-
|
|
46824
|
-
/**
|
|
46825
|
-
* Tries to block a cheating vector that relies on monkey-patching JSON.parse.
|
|
46826
|
-
*/
|
|
46827
|
-
// TODO(LEMS-2331): delete this function once server-side scoring is done.
|
|
46828
|
-
function throwErrorIfCheatingDetected() {
|
|
46829
|
-
if (!isRealJSONParse(JSON.parse)) {
|
|
46830
|
-
throw new Error("Something went wrong.");
|
|
46831
|
-
}
|
|
46832
|
-
}
|
|
46833
|
-
|
|
46834
45555
|
const genericPerseusItemData = {
|
|
46835
45556
|
question: {
|
|
46836
45557
|
content: "",
|
|
@@ -47052,6 +45773,26 @@ const ScoringUtil = {
|
|
|
47052
45773
|
* Types
|
|
47053
45774
|
*/
|
|
47054
45775
|
|
|
45776
|
+
Object.defineProperty(exports, 'isFailure', {
|
|
45777
|
+
enumerable: true,
|
|
45778
|
+
get: function () { return perseusCore.isFailure; }
|
|
45779
|
+
});
|
|
45780
|
+
Object.defineProperty(exports, 'isSuccess', {
|
|
45781
|
+
enumerable: true,
|
|
45782
|
+
get: function () { return perseusCore.isSuccess; }
|
|
45783
|
+
});
|
|
45784
|
+
Object.defineProperty(exports, 'parseAndMigratePerseusArticle', {
|
|
45785
|
+
enumerable: true,
|
|
45786
|
+
get: function () { return perseusCore.parseAndMigratePerseusArticle; }
|
|
45787
|
+
});
|
|
45788
|
+
Object.defineProperty(exports, 'parseAndMigratePerseusItem', {
|
|
45789
|
+
enumerable: true,
|
|
45790
|
+
get: function () { return perseusCore.parseAndMigratePerseusItem; }
|
|
45791
|
+
});
|
|
45792
|
+
Object.defineProperty(exports, 'parsePerseusItem', {
|
|
45793
|
+
enumerable: true,
|
|
45794
|
+
get: function () { return perseusCore.parsePerseusItem; }
|
|
45795
|
+
});
|
|
47055
45796
|
exports.ApiOptions = ApiOptions;
|
|
47056
45797
|
exports.ArticleRenderer = ArticleRenderer;
|
|
47057
45798
|
exports.BaseRadio = BaseRadio;
|
|
@@ -47077,7 +45818,6 @@ exports.PerseusI18nContext = PerseusI18nContext;
|
|
|
47077
45818
|
exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
|
|
47078
45819
|
exports.PerseusMarkdown = PerseusMarkdown;
|
|
47079
45820
|
exports.PlotterWidget = Plotter$1;
|
|
47080
|
-
exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
|
|
47081
45821
|
exports.Radio = Radio;
|
|
47082
45822
|
exports.Renderer = Renderer;
|
|
47083
45823
|
exports.ScoringUtil = ScoringUtil;
|
|
@@ -47086,7 +45826,6 @@ exports.TableWidget = Table$1;
|
|
|
47086
45826
|
exports.Util = Util;
|
|
47087
45827
|
exports.WIDGET_PROP_DENYLIST = denylist;
|
|
47088
45828
|
exports.Widgets = widgets$1;
|
|
47089
|
-
exports.addWidget = addWidget;
|
|
47090
45829
|
exports.apiVersion = apiVersion;
|
|
47091
45830
|
exports.bodyXsmallBold = bodyXsmallBold;
|
|
47092
45831
|
exports.components = components;
|
|
@@ -47117,8 +45856,6 @@ exports.getSegmentCoords = getSegmentCoords;
|
|
|
47117
45856
|
exports.getSinusoidCoords = getSinusoidCoords;
|
|
47118
45857
|
exports.getValidWidgetIds = getValidWidgetIds;
|
|
47119
45858
|
exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
|
|
47120
|
-
exports.getWidgetIdsFromContent = getWidgetIdsFromContent;
|
|
47121
|
-
exports.getWidgetIdsFromContentByType = getWidgetIdsFromContentByType;
|
|
47122
45859
|
exports.getWidgetTypeByWidgetId = getWidgetTypeByWidgetId;
|
|
47123
45860
|
exports.getWidgetsFromWidgetMap = getWidgetsFromWidgetMap;
|
|
47124
45861
|
exports.getWidgetsMapFromItemData = getWidgetsMapFromItemData;
|
|
@@ -47127,9 +45864,7 @@ exports.iconTrash = iconTrash;
|
|
|
47127
45864
|
exports.init = init;
|
|
47128
45865
|
exports.injectWidgets = injectWidgets;
|
|
47129
45866
|
exports.interactiveSizes = interactiveSizes$1;
|
|
47130
|
-
exports.isFailure = isFailure;
|
|
47131
45867
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
47132
|
-
exports.isSuccess = isSuccess;
|
|
47133
45868
|
exports.isWidgetIdInContent = isWidgetIdInContent;
|
|
47134
45869
|
exports.isWrongAnswerSupported = isWrongAnswerSupported;
|
|
47135
45870
|
exports.itemDataVersion = itemDataVersion;
|
|
@@ -47138,10 +45873,7 @@ exports.libVersion = libVersion;
|
|
|
47138
45873
|
exports.makeSafeUrl = makeSafeUrl;
|
|
47139
45874
|
exports.mathOnlyParser = mathOnlyParser;
|
|
47140
45875
|
exports.mediaQueries = mediaQueries;
|
|
47141
|
-
exports.parseAndMigratePerseusArticle = parseAndMigratePerseusArticle;
|
|
47142
|
-
exports.parseAndMigratePerseusItem = parseAndMigratePerseusItem;
|
|
47143
45876
|
exports.parseDataFromJSONP = parseDataFromJSONP;
|
|
47144
|
-
exports.parsePerseusItem = parsePerseusItem;
|
|
47145
45877
|
exports.preprocessTex = preprocessTex;
|
|
47146
45878
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|
|
47147
45879
|
exports.scorePerseusItem = scorePerseusItem;
|