@khanacademy/perseus 13.2.0 → 15.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/article-renderer.d.ts +1 -2
  3. package/dist/components/svg-image.d.ts +10 -9
  4. package/dist/es/index.js +51 -498
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.js +72 -511
  8. package/dist/index.js.map +1 -1
  9. package/dist/multi-items/item-types.d.ts +1 -1
  10. package/dist/multi-items/multi-renderer.d.ts +2 -2
  11. package/dist/perseus-api.d.ts +0 -3
  12. package/dist/server-item-renderer.d.ts +2 -15
  13. package/dist/strings.js +1 -0
  14. package/dist/styles/shared.d.ts +1 -0
  15. package/dist/types.d.ts +0 -2
  16. package/dist/util/a11y.d.ts +1 -0
  17. package/dist/widgets/expression.d.ts +0 -12
  18. package/dist/widgets/graded-group.d.ts +1 -1
  19. package/dist/widgets/label-image.d.ts +1 -1
  20. package/dist/widgets/matcher.d.ts +0 -1
  21. package/dist/widgets/passage.d.ts +1 -1
  22. package/dist/widgets/radio/widget.d.ts +1 -1
  23. package/dist/widgets/simulator.d.ts +0 -1
  24. package/dist/widgets/sorter.d.ts +0 -1
  25. package/package.json +8 -8
  26. package/src/__stories__/server-item-renderer.stories.tsx +1 -1
  27. package/src/__testdata__/server-item-renderer.testdata.ts +160 -0
  28. package/src/__tests__/article-renderer.test.tsx +2 -3
  29. package/src/__tests__/server-item-renderer.test.tsx +2 -2
  30. package/src/article-renderer.tsx +13 -15
  31. package/src/components/graph.tsx +14 -8
  32. package/src/components/svg-image.tsx +14 -12
  33. package/src/hints-renderer.tsx +1 -0
  34. package/src/index.ts +0 -1
  35. package/src/multi-items/item-types.ts +1 -1
  36. package/src/multi-items/multi-renderer.tsx +4 -4
  37. package/src/perseus-api.tsx +0 -6
  38. package/src/perseus-types.ts +8 -3
  39. package/src/server-item-renderer.tsx +12 -13
  40. package/src/types.ts +0 -5
  41. package/src/util.ts +1 -1
  42. package/src/widgets/__stories__/expression.stories.tsx +0 -1
  43. package/src/widgets/__stories__/random-widgets.stories.tsx +59 -0
  44. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -1
  45. package/src/widgets/__testdata__/categorizer.testdata.ts +40 -0
  46. package/src/widgets/__testdata__/dropdown.testdata.ts +38 -0
  47. package/src/widgets/__testdata__/explanation.testdata.ts +30 -0
  48. package/src/widgets/__testdata__/expression.testdata.ts +82 -0
  49. package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +269 -0
  50. package/src/widgets/__testdata__/radio.testdata.ts +63 -0
  51. package/src/widgets/__testdata__/randomizers.ts +51 -0
  52. package/src/widgets/__tests__/expression-mobile.test.tsx +0 -2
  53. package/src/widgets/__tests__/expression.test.tsx +4 -15
  54. package/src/widgets/__tests__/randomizers.test.ts +53 -0
  55. package/src/widgets/expression.tsx +1 -5
  56. package/src/widgets/graded-group.tsx +1 -1
  57. package/src/widgets/image.tsx +27 -20
  58. package/src/widgets/label-image/strings.ts +3 -1
  59. package/src/widgets/label-image.tsx +2 -2
  60. package/src/widgets/passage-ref.tsx +1 -1
  61. package/src/widgets/passage.tsx +1 -1
  62. package/src/widgets/radio/widget.tsx +1 -1
  63. package/src/widgets/radio.ts +1 -1
  64. package/tsconfig-build.tsbuildinfo +1 -1
  65. package/dist/item-renderer.d.ts +0 -125
  66. package/dist/mixins/provide-keypad.d.ts +0 -39
  67. package/src/__stories__/item-renderer.stories.tsx +0 -30
  68. package/src/__testdata__/item-renderer.testdata.ts +0 -299
  69. package/src/__tests__/__snapshots__/item-renderer.test.tsx.snap +0 -224
  70. package/src/__tests__/item-renderer.test.tsx +0 -930
  71. package/src/item-renderer.tsx +0 -529
  72. package/src/mixins/provide-keypad.tsx +0 -138
@@ -0,0 +1,269 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomElement,
5
+ randomInteger,
6
+ randomLetter,
7
+ randomWord,
8
+ } from "./randomizers";
9
+
10
+ import type {Coord} from "../../interactive2/types";
11
+ import type {
12
+ PerseusGraphType,
13
+ PerseusGraphTypeAngle,
14
+ PerseusGraphTypeCircle,
15
+ PerseusGraphTypeLinear,
16
+ PerseusGraphTypeLinearSystem,
17
+ PerseusGraphTypePoint,
18
+ PerseusGraphTypePolygon,
19
+ PerseusGraphTypeQuadratic,
20
+ PerseusGraphTypeRay,
21
+ PerseusGraphTypeSegment,
22
+ PerseusGraphTypeSinusoid,
23
+ PerseusImageBackground,
24
+ PerseusRenderer,
25
+ } from "../../perseus-types";
26
+
27
+ const randomCoord = (): Coord => {
28
+ return [randomInteger(1, 100), randomInteger(1, 100)];
29
+ };
30
+
31
+ const randomGraphTypeAngle = (): PerseusGraphTypeAngle => {
32
+ return {
33
+ type: "angle",
34
+ showAngles: randomBoolean(),
35
+ allowReflexAngles: randomBoolean(),
36
+ angleOffsetDeg: randomInteger(1, 90),
37
+ snapDegrees: randomInteger(1, 90),
38
+ match: "congruent",
39
+ // must be 3 coords for an angle graph
40
+ coords: arrayOfLength(3).map(randomCoord),
41
+ };
42
+ };
43
+
44
+ const randomGraphTypeCircle = (): PerseusGraphTypeCircle => {
45
+ return {
46
+ type: "circle",
47
+ center: randomCoord(),
48
+ radius: randomInteger(1, 20),
49
+ };
50
+ };
51
+
52
+ const randomGraphTypeLinear = (): PerseusGraphTypeLinear => {
53
+ return {
54
+ type: "linear",
55
+ coords: arrayOfLength(2).map(randomCoord),
56
+ };
57
+ };
58
+
59
+ const randomGraphTypeLinearSystem = (): PerseusGraphTypeLinearSystem => {
60
+ return {
61
+ type: "linear-system",
62
+ coords: arrayOfLength(2).map(() => arrayOfLength(2).map(randomCoord)),
63
+ };
64
+ };
65
+
66
+ const randomGraphTypePoint = (): PerseusGraphTypePoint => {
67
+ let minCoords = 1;
68
+ const numPoints = randomElement(["unlimited", randomInteger(1, 10)]);
69
+ if (typeof numPoints === "number") {
70
+ minCoords = numPoints;
71
+ }
72
+ return {
73
+ type: "point",
74
+ numPoints: typeof numPoints === "number" ? numPoints : "unlimited",
75
+ coords: arrayOfLength(randomInteger(minCoords, 10)).map(randomCoord),
76
+ };
77
+ };
78
+
79
+ const randomGraphTypePolygon = (): PerseusGraphTypePolygon => {
80
+ // Note Jhead - I saw this create at least one broken graph but didn't figure out why
81
+ return {
82
+ type: "polygon",
83
+ numSides: randomElement([
84
+ "unlimited",
85
+ undefined,
86
+ randomInteger(3, 30),
87
+ randomInteger(3, 30),
88
+ ]),
89
+ showAngles: randomBoolean(),
90
+ showSides: randomBoolean(),
91
+ snapTo: randomElement(["grid", "angles", "sides"]),
92
+ match: randomElement(["similar", "congruent", "approx", undefined]),
93
+ coords: arrayOfLength(randomInteger(3, 20)).map(randomCoord),
94
+ };
95
+ };
96
+
97
+ const randomGraphTypeQuadratic = (): PerseusGraphTypeQuadratic => {
98
+ return {
99
+ type: "quadratic",
100
+ coords: arrayOfLength(3).map(randomCoord),
101
+ };
102
+ };
103
+
104
+ const randomGraphTypeRay = (): PerseusGraphTypeRay => {
105
+ return {
106
+ type: "ray",
107
+ coords: arrayOfLength(2).map(randomCoord),
108
+ };
109
+ };
110
+
111
+ const randomGraphTypeSegment = (): PerseusGraphTypeSegment => {
112
+ const numSegments = randomInteger(1, 6);
113
+ return {
114
+ type: "segment",
115
+ numSegments,
116
+ coords: arrayOfLength(numSegments).map(() => [
117
+ randomCoord(),
118
+ randomCoord(),
119
+ ]),
120
+ };
121
+ };
122
+
123
+ const randomGraphTypeSinusoid = (): PerseusGraphTypeSinusoid => {
124
+ return {
125
+ type: "sinusoid",
126
+ coords: arrayOfLength(2).map(randomCoord),
127
+ };
128
+ };
129
+
130
+ export const randomBackgroundImage = (): PerseusImageBackground => {
131
+ const height = randomInteger(200, 500);
132
+ const width = randomInteger(200, 500);
133
+ const graphieUrl =
134
+ "web+graphie://ka-perseus-graphie.s3.amazonaws.com/a1b7a05c177742523250b64a3995c9b37aac3399";
135
+ const placeKittenUrl = `https://placekitten.com/g/${width}/${height}`;
136
+
137
+ return {
138
+ scale: randomInteger(8, 12) / 10,
139
+ bottom: randomInteger(0, 10) - 5,
140
+ url: randomElement([graphieUrl, placeKittenUrl, null]),
141
+ height,
142
+ width,
143
+ left: randomInteger(0, 10) - 5,
144
+ };
145
+ };
146
+
147
+ export const randomInteractiveGraphGenerator = (
148
+ type?: string,
149
+ ): PerseusRenderer => {
150
+ const randomRange = (): [number, number] => {
151
+ const val1 = randomInteger(0, 400) - 200;
152
+ const val2 = randomInteger(0, 400) - 200;
153
+ if (val1 < val2) {
154
+ return [val1, val2];
155
+ }
156
+ return [val2, val1];
157
+ };
158
+
159
+ const graphType =
160
+ type ||
161
+ randomElement([
162
+ "angle",
163
+ "circle",
164
+ "linear",
165
+ "linear-system",
166
+ "point",
167
+ "polygon",
168
+ "quadratic",
169
+ "ray",
170
+ "segment",
171
+ "sinusoid",
172
+ ]);
173
+
174
+ let graph: PerseusGraphType;
175
+ let correct: PerseusGraphType;
176
+
177
+ switch (graphType) {
178
+ case "angle": {
179
+ graph = randomGraphTypeAngle();
180
+ correct = randomGraphTypeAngle();
181
+ break;
182
+ }
183
+ case "circle": {
184
+ graph = randomGraphTypeCircle();
185
+ correct = randomGraphTypeCircle();
186
+ break;
187
+ }
188
+ case "linear": {
189
+ graph = randomGraphTypeLinear();
190
+ correct = randomGraphTypeLinear();
191
+ break;
192
+ }
193
+ case "linear-system": {
194
+ graph = randomGraphTypeLinearSystem();
195
+ correct = randomGraphTypeLinearSystem();
196
+ break;
197
+ }
198
+ case "point": {
199
+ graph = randomGraphTypePoint();
200
+ correct = randomGraphTypePoint();
201
+ break;
202
+ }
203
+ case "polygon": {
204
+ graph = randomGraphTypePolygon();
205
+ correct = randomGraphTypePolygon();
206
+ break;
207
+ }
208
+ case "quadratic": {
209
+ graph = randomGraphTypeQuadratic();
210
+ correct = randomGraphTypeQuadratic();
211
+ break;
212
+ }
213
+ case "ray": {
214
+ graph = randomGraphTypeRay();
215
+ correct = randomGraphTypeRay();
216
+ break;
217
+ }
218
+ case "segment": {
219
+ graph = randomGraphTypeSegment();
220
+ correct = randomGraphTypeSegment();
221
+ break;
222
+ }
223
+ case "sinusoid": {
224
+ graph = randomGraphTypeSinusoid();
225
+ correct = randomGraphTypeSinusoid();
226
+ break;
227
+ }
228
+ default: {
229
+ graph = randomGraphTypeAngle();
230
+ correct = randomGraphTypeAngle();
231
+ }
232
+ }
233
+
234
+ return {
235
+ content: `[[\u2603 interactive-graph 1]]`,
236
+ images: {},
237
+ widgets: {
238
+ "interactive-graph 1": {
239
+ graded: randomBoolean(),
240
+ version: {
241
+ major: 0,
242
+ minor: 0,
243
+ },
244
+ static: false, //always false for this widget
245
+ type: "interactive-graph",
246
+ options: {
247
+ rulerTicks: randomInteger(1, 100),
248
+ showProtractor: randomBoolean(0.2),
249
+ graph,
250
+ snapStep: [
251
+ randomInteger(1, 10) / 10,
252
+ randomInteger(1, 10) / 10,
253
+ ],
254
+ labels: [randomLetter() || "x", randomLetter() || "y"],
255
+ step: [randomInteger(1, 10), randomInteger(1, 10)],
256
+ gridStep: [randomInteger(1, 10), randomInteger(1, 10)],
257
+ backgroundImage: randomBackgroundImage(),
258
+ range: [randomRange(), randomRange()],
259
+ showRuler: randomBoolean(0.2),
260
+ markings:
261
+ randomElement(["none", "graph", "grid"]) || "none",
262
+ rulerLabel: randomElement(["", randomWord()]) || "",
263
+ correct,
264
+ },
265
+ alignment: "default",
266
+ },
267
+ },
268
+ };
269
+ };
@@ -1,7 +1,15 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomInteger,
5
+ randomSentence,
6
+ } from "./randomizers";
7
+
1
8
  import type {
2
9
  PerseusRenderer,
3
10
  RadioWidget,
4
11
  PassageWidget,
12
+ PerseusRadioChoice,
5
13
  } from "../../perseus-types";
6
14
 
7
15
  export const question: PerseusRenderer = {
@@ -259,3 +267,58 @@ export const multiChoiceQuestionAndAnswer: [
259
267
  [0, 1, 2, 3],
260
268
  ],
261
269
  ];
270
+
271
+ export const randomRadioGenerator = (): PerseusRenderer => {
272
+ const randomChoice = (
273
+ isMultiSelect: boolean,
274
+ isCorrect: boolean,
275
+ isNoneOfTheAbove: boolean,
276
+ ): PerseusRadioChoice => {
277
+ return {
278
+ content: randomSentence(12),
279
+ isNoneOfTheAbove: isNoneOfTheAbove,
280
+ correct: isMultiSelect ? randomBoolean() : isCorrect,
281
+ };
282
+ };
283
+ const numberOfChoices = randomInteger(2, 6);
284
+
285
+ const isMultiSelect = randomBoolean();
286
+ // only used if not multi select
287
+ const correctIndex = randomInteger(0, numberOfChoices);
288
+
289
+ const containsNoneOfTheAbove = randomBoolean();
290
+ // only used if containsNoneOfTheAbove
291
+ const noneOfTheAboveIndex = randomInteger(0, numberOfChoices);
292
+
293
+ return {
294
+ content: `${randomSentence(30)}\n\n[[\u2603 radio 1]]`,
295
+ images: {},
296
+ widgets: {
297
+ "radio 1": {
298
+ graded: randomBoolean(),
299
+ version: {
300
+ major: 1,
301
+ minor: 0,
302
+ },
303
+ static: randomBoolean(0.05),
304
+ type: "radio",
305
+ options: {
306
+ onePerLine: randomBoolean(),
307
+ displayCount: null,
308
+ choices: arrayOfLength(numberOfChoices).map((_, i) =>
309
+ randomChoice(
310
+ isMultiSelect,
311
+ correctIndex === i,
312
+ containsNoneOfTheAbove && noneOfTheAboveIndex === i,
313
+ ),
314
+ ),
315
+ hasNoneOfTheAbove: randomBoolean(),
316
+ multipleSelect: isMultiSelect,
317
+ randomize: randomBoolean(),
318
+ deselectEnabled: randomBoolean(),
319
+ },
320
+ alignment: "default",
321
+ } as RadioWidget,
322
+ },
323
+ };
324
+ };
@@ -0,0 +1,51 @@
1
+ // Helpers to generate random test data for widgets
2
+
3
+ // Returns a random letter inclusive of emoji and japanese characters
4
+ export const randomLetter = (): string => {
5
+ const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
6
+ const emojis = ["🍇", "🍈", "🍌"];
7
+ const japaneseCharacters = ["ツ", "デ", "ト", "ド", "ナ", "ぁ", "あ"];
8
+ const characters = alphabet.concat(emojis).concat(japaneseCharacters);
9
+ const letter = randomInteger(0, characters.length - 1);
10
+ return characters[letter];
11
+ };
12
+
13
+ // Returns a random word 3-20 characters long composed of the random letter function
14
+ export const randomWord = (): string => {
15
+ // words are 3 - 20 characters
16
+ const numLetters = randomInteger(3, 11);
17
+ return arrayOfLength(numLetters).map(randomLetter).join("");
18
+ };
19
+
20
+ // Returns a random sentence of a specified number of words, using the random word function
21
+ export const randomSentence = (maxWords: number): string => {
22
+ const numWords = randomInteger(5, Math.max(maxWords, 5));
23
+ return arrayOfLength(numWords).map(randomWord).join(" ");
24
+ };
25
+
26
+ // generates a boolean. 50/50 unless rateTrue is specified
27
+ export const randomBoolean = (rateTrue?: number): boolean => {
28
+ let threshold = rateTrue;
29
+ if (threshold == null) {
30
+ threshold = 0.5;
31
+ }
32
+ return Math.random() < threshold;
33
+ };
34
+
35
+ // generates an integer inclusive of the specified min and max
36
+ export const randomInteger = (min: number, max: number): number => {
37
+ return Math.floor(Math.random() * (max + 1 - min)) + min;
38
+ };
39
+
40
+ // A helper to create an array of a certain length.
41
+ export const arrayOfLength = (length: number): number[] => {
42
+ return Array.from(Array(length).keys());
43
+ };
44
+
45
+ // Returns a random element from a provided list
46
+ export function randomElement<T>(items: Array<T>): T | undefined {
47
+ if (items.length > 0) {
48
+ return items[randomInteger(0, items.length - 1)];
49
+ }
50
+ return undefined;
51
+ }
@@ -37,7 +37,6 @@ function RendererWithContext({item}) {
37
37
  apiOptions={{
38
38
  isMobile: true,
39
39
  customKeypad: true,
40
- useV2Keypad: true,
41
40
  }}
42
41
  item={item}
43
42
  problemNum={0}
@@ -60,7 +59,6 @@ function KeypadWithContext() {
60
59
  onElementMounted={setKeypadElement}
61
60
  onDismiss={() => {}}
62
61
  onAnalyticsEvent={async () => {}}
63
- useV2Keypad
64
62
  />
65
63
  );
66
64
  }}
@@ -42,7 +42,7 @@ const assertCorrect = (itemData: PerseusItem, input: string) => {
42
42
  type: "perseus:expression-evaluated",
43
43
  payload: {
44
44
  result: "correct",
45
- virtualKeypadVersion: "PERSEUS_MATH_INPUT",
45
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
46
46
  },
47
47
  });
48
48
  };
@@ -54,7 +54,7 @@ const assertIncorrect = (itemData: PerseusItem, input: string) => {
54
54
  type: "perseus:expression-evaluated",
55
55
  payload: {
56
56
  result: "incorrect",
57
- virtualKeypadVersion: "PERSEUS_MATH_INPUT",
57
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V2",
58
58
  },
59
59
  });
60
60
  };
@@ -224,19 +224,8 @@ describe("Expression Widget", function () {
224
224
  );
225
225
  });
226
226
 
227
- it("should set the virtual keypad version to MATH_INPUT_KEYPAD_V1 when customKeypad is set and useV2Keypad is unset", () => {
228
- assertKeypadVersion({customKeypad: true}, "MATH_INPUT_KEYPAD_V1");
229
- });
230
-
231
- it("should set the virtual keypad version to MATH_INPUT_KEYPAD_V2 when customKeypad is set and useV2Keypad is true", () => {
232
- assertKeypadVersion(
233
- {customKeypad: true, useV2Keypad: true},
234
- "MATH_INPUT_KEYPAD_V2",
235
- );
236
- });
237
-
238
- it("should default the virtual keypad version to PERSEUS_MATH_INPUT", () => {
239
- assertKeypadVersion(Object.freeze({}), "PERSEUS_MATH_INPUT");
227
+ it("should default the virtual keypad version to MATH_INPUT_KEYPAD_V2", () => {
228
+ assertKeypadVersion(Object.freeze({}), "MATH_INPUT_KEYPAD_V2");
240
229
  });
241
230
  });
242
231
  });
@@ -0,0 +1,53 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomElement,
5
+ randomLetter,
6
+ randomSentence,
7
+ randomWord,
8
+ } from "../__testdata__/randomizers";
9
+
10
+ describe("randomizers", () => {
11
+ describe("randomLetter", () => {
12
+ it("returns one letter", () => {
13
+ // can't check for just one character because some non english chars are longer than 1 char
14
+ expect(typeof randomLetter()).toBe("string");
15
+ });
16
+ });
17
+
18
+ describe("randomWord", () => {
19
+ it("returns a word of at least 3 letters", () => {
20
+ expect(randomWord().length).toBeGreaterThanOrEqual(3);
21
+ });
22
+ });
23
+
24
+ describe("randomSentence", () => {
25
+ it("returns a sentence of at least 5 words", () => {
26
+ expect(randomSentence(10).split(" ").length).toBeGreaterThanOrEqual(
27
+ 5,
28
+ );
29
+ });
30
+ });
31
+
32
+ describe("randomBoolean", () => {
33
+ it("is true when rateTrue is 1", () => {
34
+ expect(randomBoolean(1)).toEqual(true);
35
+ });
36
+ it("is false when rateTrue is 0", () => {
37
+ expect(randomBoolean(0)).toEqual(false);
38
+ });
39
+ });
40
+
41
+ describe("arrayOfLength", () => {
42
+ it("returns an array of the specified length", () => {
43
+ expect(arrayOfLength(3).length).toEqual(3);
44
+ expect(arrayOfLength(12).length).toEqual(12);
45
+ });
46
+ });
47
+
48
+ describe("randomElement", () => {
49
+ it("returns one of the input array elements", () => {
50
+ expect([1, 2, 3]).toContain(randomElement([1, 2, 3]));
51
+ });
52
+ });
53
+ });
@@ -64,11 +64,7 @@ const deriveKeypadVersion = (apiOptions: APIOptions) => {
64
64
  // can remove this `virtualKeypadVersion` field entirely.
65
65
  return apiOptions.nativeKeypadProxy != null
66
66
  ? "REACT_NATIVE_KEYPAD"
67
- : apiOptions.customKeypad === true
68
- ? apiOptions.useV2Keypad === true
69
- ? "MATH_INPUT_KEYPAD_V2"
70
- : "MATH_INPUT_KEYPAD_V1"
71
- : "PERSEUS_MATH_INPUT";
67
+ : "MATH_INPUT_KEYPAD_V2";
72
68
  };
73
69
 
74
70
  type Rubric = PerseusExpressionWidgetOptions;
@@ -24,6 +24,7 @@ import a11y from "../util/a11y";
24
24
 
25
25
  import GradedGroupAnswerBar from "./graded-group-answer-bar";
26
26
 
27
+ import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
27
28
  import type {PerseusGradedGroupWidgetOptions} from "../perseus-types";
28
29
  import type {
29
30
  PerseusScore,
@@ -31,7 +32,6 @@ import type {
31
32
  WidgetExports,
32
33
  WidgetProps,
33
34
  } from "../types";
34
- import type {ANSWER_BAR_STATES} from "./graded-group-answer-bar";
35
35
 
36
36
  const GRADING_STATUSES = {
37
37
  ungraded: "ungraded" as const,
@@ -4,6 +4,7 @@ import classNames from "classnames";
4
4
  import * as React from "react";
5
5
  import _ from "underscore";
6
6
 
7
+ import AssetContext from "../asset-context";
7
8
  import SvgImage from "../components/svg-image";
8
9
  import * as Changeable from "../mixins/changeable";
9
10
  import Renderer from "../renderer";
@@ -93,11 +94,14 @@ class ImageWidget extends React.Component<Props> {
93
94
  const backgroundImage = this.props.backgroundImage;
94
95
 
95
96
  if (backgroundImage.url) {
97
+ const url = backgroundImage.url;
96
98
  image = (
97
- <SvgImage
98
- src={backgroundImage.url}
99
- alt={
100
- /* alt text is formatted in a sr-only
99
+ <AssetContext.Consumer>
100
+ {({setAssetStatus}) => (
101
+ <SvgImage
102
+ src={url}
103
+ alt={
104
+ /* alt text is formatted in a sr-only
101
105
  div next to the image in addition to
102
106
  the alt attribute.
103
107
  If there is no alt text at all,
@@ -117,22 +121,25 @@ class ImageWidget extends React.Component<Props> {
117
121
  in practice right now, although
118
122
  it will exhibit weird behaviour
119
123
  while editing. */
120
- this.props.alt
121
- }
122
- overrideAriaHidden={true}
123
- width={backgroundImage.width}
124
- height={backgroundImage.height}
125
- preloader={apiOptions.imagePreloader}
126
- extraGraphie={{
127
- box: this.props.box,
128
- range: this.props.range,
129
- labels: this.props.labels,
130
- }}
131
- trackInteraction={this.props.trackInteraction}
132
- zoomToFullSizeOnMobile={apiOptions.isMobile}
133
- constrainHeight={apiOptions.isMobile}
134
- allowFullBleed={apiOptions.isMobile}
135
- />
124
+ this.props.alt
125
+ }
126
+ overrideAriaHidden={true}
127
+ width={backgroundImage.width}
128
+ height={backgroundImage.height}
129
+ preloader={apiOptions.imagePreloader}
130
+ extraGraphie={{
131
+ box: this.props.box,
132
+ range: this.props.range,
133
+ labels: this.props.labels,
134
+ }}
135
+ trackInteraction={this.props.trackInteraction}
136
+ zoomToFullSizeOnMobile={apiOptions.isMobile}
137
+ constrainHeight={apiOptions.isMobile}
138
+ allowFullBleed={apiOptions.isMobile}
139
+ setAssetStatus={setAssetStatus}
140
+ />
141
+ )}
142
+ </AssetContext.Consumer>
136
143
  );
137
144
  }
138
145
 
@@ -1,3 +1,5 @@
1
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
2
+
1
3
  export const strings = {
2
- hideAnswersToggleLabel: "Hide answer choices",
4
+ hideAnswersToggleLabel: i18n._("Hide answer choices"),
3
5
  };
@@ -22,12 +22,12 @@ import mediaQueries from "../styles/media-queries";
22
22
  import AnswerChoices from "./label-image/answer-choices";
23
23
  import Marker from "./label-image/marker";
24
24
 
25
- import type {ChangeableProps} from "../mixins/changeable";
26
- import type {APIOptions, PerseusScore, WidgetExports} from "../types";
27
25
  import type {
28
26
  InteractiveMarkerType,
29
27
  InteractiveMarkerScore,
30
28
  } from "./label-image/types";
29
+ import type {ChangeableProps} from "../mixins/changeable";
30
+ import type {APIOptions, PerseusScore, WidgetExports} from "../types";
31
31
 
32
32
  type MarkersState = {
33
33
  markers: ReadonlyArray<InteractiveMarkerType>;
@@ -7,6 +7,7 @@ import * as Changeable from "../mixins/changeable";
7
7
  import {removeDenylistProps} from "../mixins/widget-prop-denylist";
8
8
  import PerseusMarkdown from "../perseus-markdown";
9
9
 
10
+ import type {Passage, Reference} from "./passage";
10
11
  import type {PerseusPassageRefWidgetOptions} from "../perseus-types";
11
12
  import type {
12
13
  ChangeFn,
@@ -14,7 +15,6 @@ import type {
14
15
  WidgetExports,
15
16
  WidgetProps,
16
17
  } from "../types";
17
- import type {Passage, Reference} from "./passage";
18
18
  import type {LinterContextProps} from "@khanacademy/perseus-linter";
19
19
 
20
20
  const EN_DASH = "\u2013";
@@ -12,6 +12,7 @@ import Renderer from "../renderer";
12
12
 
13
13
  import PassageMarkdown from "./passage/passage-markdown";
14
14
 
15
+ import type {ParseState} from "./passage/passage-markdown";
15
16
  import type {SerializedHighlightSet} from "../components/highlighting/types";
16
17
  import type {ChangeableProps} from "../mixins/changeable";
17
18
  import type {PerseusPassageWidgetOptions} from "../perseus-types";
@@ -21,7 +22,6 @@ import type {
21
22
  WidgetInfo,
22
23
  WidgetProps,
23
24
  } from "../types";
24
- import type {ParseState} from "./passage/passage-markdown";
25
25
  import type {SingleASTNode} from "@khanacademy/simple-markdown";
26
26
 
27
27
  // A fake paragraph to measure the line height of the passage,
@@ -8,12 +8,12 @@ import PassageRef from "../passage-ref";
8
8
 
9
9
  import BaseRadio from "./base-radio";
10
10
 
11
+ import type {FocusFunction, ChoiceType} from "./base-radio";
11
12
  import type {
12
13
  PerseusRadioChoice,
13
14
  PerseusRadioWidgetOptions,
14
15
  } from "../../perseus-types";
15
16
  import type {PerseusScore, WidgetProps, ChoiceState} from "../../types";
16
- import type {FocusFunction, ChoiceType} from "./base-radio";
17
17
 
18
18
  // RenderProps is the return type for radio.jsx#transform
19
19
  export type RenderProps = {