@khanacademy/perseus 8.0.0 → 9.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/CHANGELOG.md +21 -0
- package/dist/article-renderer.d.ts +2 -1
- package/dist/dependencies.d.ts +7 -1
- package/dist/es/index.js +66 -29
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +66 -29
- package/dist/index.js.map +1 -1
- package/dist/interaction-tracker.d.ts +3 -3
- package/dist/item-renderer.d.ts +2 -1
- package/dist/multi-items/multi-renderer.d.ts +2 -1
- package/dist/perseus-api.d.ts +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/server-item-renderer.d.ts +3 -1
- package/dist/types.d.ts +29 -9
- package/dist/widgets/expression.d.ts +409 -12
- package/dist/widgets/graded-group.d.ts +2 -2
- package/dist/widgets/sequence.d.ts +2 -2
- package/package.json +3 -3
- package/src/__stories__/article-renderer.stories.tsx +35 -2
- package/src/__testdata__/article-renderer.testdata.ts +71 -0
- package/src/__tests__/item-renderer.test.tsx +12 -1
- package/src/__tests__/server-item-renderer.test.tsx +9 -1
- package/src/article-renderer.tsx +38 -30
- package/src/dependencies.ts +16 -1
- package/src/interaction-tracker.ts +3 -3
- package/src/item-renderer.tsx +62 -44
- package/src/multi-items/__tests__/multi-renderer.test.tsx +6 -1
- package/src/multi-items/multi-renderer.tsx +16 -4
- package/src/renderer.tsx +1 -1
- package/src/server-item-renderer.tsx +21 -16
- package/src/types.ts +49 -13
- package/src/widgets/__stories__/definition.stories.tsx +8 -1
- package/src/widgets/__stories__/expression.stories.tsx +2 -2
- package/src/widgets/__tests__/{expression.test.ts → expression.test.tsx} +73 -8
- package/src/widgets/__tests__/renderQuestion.tsx +26 -18
- package/src/widgets/expression.tsx +82 -48
- package/src/widgets/graded-group.tsx +11 -2
- package/src/widgets/sequence.tsx +10 -2
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { PerseusGradedGroupWidgetOptions } from "../perseus-types";
|
|
3
|
-
import type { WidgetProps } from "../types";
|
|
3
|
+
import type { TrackingGradedGroupExtraArguments, WidgetProps } from "../types";
|
|
4
4
|
import type { ANSWER_BAR_STATES } from "./graded-group-answer-bar";
|
|
5
5
|
declare const GRADING_STATUSES: {
|
|
6
6
|
readonly ungraded: "ungraded";
|
|
@@ -10,7 +10,7 @@ declare const GRADING_STATUSES: {
|
|
|
10
10
|
};
|
|
11
11
|
type Rubric = PerseusGradedGroupWidgetOptions;
|
|
12
12
|
type RenderProps = PerseusGradedGroupWidgetOptions;
|
|
13
|
-
type Props = WidgetProps<RenderProps, Rubric> & {
|
|
13
|
+
type Props = WidgetProps<RenderProps, Rubric, TrackingGradedGroupExtraArguments> & {
|
|
14
14
|
inGradedGroupSet?: boolean;
|
|
15
15
|
onNextQuestion?: () => unknown;
|
|
16
16
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { PerseusSequenceWidgetOptions } from "../perseus-types";
|
|
3
|
-
import type { WidgetProps } from "../types";
|
|
3
|
+
import type { TrackingSequenceExtraArguments, WidgetProps } from "../types";
|
|
4
4
|
type Rubric = PerseusSequenceWidgetOptions;
|
|
5
|
-
type ExternalProps = WidgetProps<PerseusSequenceWidgetOptions, Rubric>;
|
|
5
|
+
type ExternalProps = WidgetProps<PerseusSequenceWidgetOptions, Rubric, TrackingSequenceExtraArguments>;
|
|
6
6
|
type Props = ExternalProps;
|
|
7
7
|
type DefaultProps = {
|
|
8
8
|
json: Props["json"];
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core Perseus API (includes renderers and widgets)",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "9.0.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@khanacademy/kas": "^0.3.1",
|
|
25
25
|
"@khanacademy/kmath": "^0.1.2",
|
|
26
|
-
"@khanacademy/math-input": "^
|
|
27
|
-
"@khanacademy/perseus-core": "0.
|
|
26
|
+
"@khanacademy/math-input": "^10.0.0",
|
|
27
|
+
"@khanacademy/perseus-core": "0.2.0",
|
|
28
28
|
"@khanacademy/perseus-linter": "^0.3.2",
|
|
29
29
|
"@khanacademy/pure-markdown": "^0.2.4",
|
|
30
30
|
"@khanacademy/simple-markdown": "^0.10.0"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {storybookDependenciesV2} from "../../../../testing/test-dependencies";
|
|
4
|
+
import {
|
|
5
|
+
singleSectionArticle,
|
|
6
|
+
multiSectionArticle,
|
|
7
|
+
passageArticle,
|
|
8
|
+
} from "../__testdata__/article-renderer.testdata";
|
|
4
9
|
import ArticleRenderer from "../article-renderer";
|
|
5
10
|
|
|
6
11
|
export default {
|
|
@@ -12,6 +17,34 @@ export default {
|
|
|
12
17
|
},
|
|
13
18
|
};
|
|
14
19
|
|
|
20
|
+
export const ASingleSectionArticle = (args: {
|
|
21
|
+
useNewStyles;
|
|
22
|
+
}): React.ReactElement => {
|
|
23
|
+
return (
|
|
24
|
+
<ArticleRenderer
|
|
25
|
+
json={singleSectionArticle}
|
|
26
|
+
dependencies={storybookDependenciesV2}
|
|
27
|
+
useNewStyles={args.useNewStyles}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const BMultiSectionArticle = (args: {
|
|
33
|
+
useNewStyles;
|
|
34
|
+
}): React.ReactElement => {
|
|
35
|
+
return (
|
|
36
|
+
<ArticleRenderer
|
|
37
|
+
json={multiSectionArticle}
|
|
38
|
+
dependencies={storybookDependenciesV2}
|
|
39
|
+
useNewStyles={args.useNewStyles}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
15
44
|
export const PassageArticle = ({useNewStyles}): any => (
|
|
16
|
-
<ArticleRenderer
|
|
45
|
+
<ArticleRenderer
|
|
46
|
+
json={passageArticle}
|
|
47
|
+
dependencies={storybookDependenciesV2}
|
|
48
|
+
useNewStyles={useNewStyles}
|
|
49
|
+
/>
|
|
17
50
|
);
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import type {PerseusRenderer} from "../perseus-types";
|
|
2
2
|
|
|
3
|
+
export const singleSectionArticle: PerseusRenderer = {
|
|
4
|
+
content:
|
|
5
|
+
"[[☃ image 2]]\n\n**Part A:** Return to the main characters from your three favorite films. \n\n- What was one important choice they had to make where the stakes were **high**?\n\n- What were the **stakes**?\n\n- Can you identify them as internal, external or philosophical?\n\n**Part B:** Think about a difficult choice you had to make in your own life. What was at stake? \n\n**Part C:** Return to one of the obstacles your character might face from the previous exercise. Now think of the **choice** this obstacle forces them to make. Answer the following:\n\n- What are the possible stakes of this choice?\n\n- Can you come up with an internal, external or philosophical stake which applies to this choice? \n",
|
|
6
|
+
images: {},
|
|
7
|
+
widgets: {
|
|
8
|
+
"image 2": {
|
|
9
|
+
type: "image",
|
|
10
|
+
alignment: "block",
|
|
11
|
+
static: false,
|
|
12
|
+
graded: true,
|
|
13
|
+
options: {
|
|
14
|
+
static: false,
|
|
15
|
+
title: "",
|
|
16
|
+
range: [
|
|
17
|
+
[0, 10],
|
|
18
|
+
[0, 10],
|
|
19
|
+
],
|
|
20
|
+
box: [1258, 703],
|
|
21
|
+
backgroundImage: {
|
|
22
|
+
url: "https://ka-perseus-images.s3.amazonaws.com/b08029bc1786fbe54468a2ddc96aaa20be7a663a.png",
|
|
23
|
+
width: 1258,
|
|
24
|
+
height: 703,
|
|
25
|
+
},
|
|
26
|
+
labels: [],
|
|
27
|
+
alt: 'A scene from Pixar\'s film "Toy Story 3" where the characters are swimming in a sea of trash and look very afraid.',
|
|
28
|
+
caption: "",
|
|
29
|
+
},
|
|
30
|
+
version: {major: 0, minor: 0},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
3
35
|
export const passageArticle: PerseusRenderer = {
|
|
4
36
|
content:
|
|
5
37
|
"###Group/Pair Activity \n\nThis passage is adapted from Ed Yong, “Turtles Use the Earth’s Magnetic Field as Global GPS.” ©2011 by Kalmbach Publishing Co.\n\n[[☃ passage 1]]\n\n**Question 9**\n\nThe passage most strongly suggests that Adelita used which of the following to navigate her 9,000-mile journey?\n\nA) The current of the North Atlantic gyre\n\nB) Cues from electromagnetic coils designed by Putman and Lohmann\n\nC) The inclination and intensity of Earth’s magnetic field\n\nD) A simulated “magnetic signature” configured by Lohmann\n\n10) Which choice provides the best evidence for the answer to the previous question?\n\nA) Lines 1–2 (“In 1996...way”)\n\nB) Lines 20–21 (“Using...surface”)\n\nC) Lines 36–37 (“In the wild...stars”)\n\nD) Lines 43–45 (“Neither...it is”)\n\n**Question 12** \n\nBased on the passage, which choice best describes the relationship between Putman’s and Lohmann’s research?\n\nA) Putman’s research contradicts Lohmann’s.\n\nB) Putman’s research builds on Lohmann’s.\n\nC) Lohmann’s research confirms Putman’s.\n\nD) Lohmann’s research corrects Putman’s.",
|
|
@@ -25,3 +57,42 @@ export const passageArticle: PerseusRenderer = {
|
|
|
25
57
|
},
|
|
26
58
|
},
|
|
27
59
|
};
|
|
60
|
+
|
|
61
|
+
export const multiSectionArticle: ReadonlyArray<PerseusRenderer> = [
|
|
62
|
+
{
|
|
63
|
+
content:
|
|
64
|
+
"## What is a matrix?\n\nA matrix is a rectangular array of numbers arranged in rows and columns.\n\nMatrices can be useful for organizing and manipulating data. They can also be used as a tool to help solve systems of equations.",
|
|
65
|
+
images: {},
|
|
66
|
+
widgets: {},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
content:
|
|
70
|
+
"## How do we multiply a matrix by a scalar?\n\nTo multiply a matrix by a scalar, we multiply each entry in the matrix by the scalar. For example, if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$\n\nand we want to multiply $A$ by $2$, we can multiply each entry by $2$ to get:\n\n$2A = \\begin{bmatrix} \n4 & 6 \\\\\n2 & 8 \\\\\n\\end{bmatrix}$",
|
|
71
|
+
images: {},
|
|
72
|
+
widgets: {},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
content:
|
|
76
|
+
"## How do we add or subtract two matrices?\n\nTo add or subtract two matrices, we add or subtract corresponding entries. So if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$ \n\nand \n\n$B = \\begin{bmatrix} \n5 & 2 \\\\\n3 & 1 \\\\\n\\end{bmatrix}$,\n\nthen \n\n$A+B = \\begin{bmatrix} \n7 & 5 \\\\\n4 & 5 \\\\\n\\end{bmatrix}$\n\nand \n\n$A-B = \\begin{bmatrix} \n-3 & 1 \\\\\n-2 & 3 \\\\\n\\end{bmatrix}$",
|
|
77
|
+
images: {},
|
|
78
|
+
widgets: {},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
content:
|
|
82
|
+
"## How do we multiply two matrices together?\n\nThis one's a bit trickier. To multiply two matrices together, we take the dot product of the rows from the first matrix with the columns from the second matrix. The result is a new matrix. For example, if \n\n$A = \\begin{bmatrix} \n2 & 3 \\\\\n1 & 4 \\\\\n\\end{bmatrix}$ \n\nand \n\n$B = \\begin{bmatrix} \n5 & 2 \\\\\n3 & 1 \\\\\n\\end{bmatrix}$,\n\nthen \n\n$AB = \\begin{bmatrix} \n2\\cdot5+3\\cdot3 & 2\\cdot2+3\\cdot1 \\\\\n1\\cdot5+4\\cdot3 & 1\\cdot2+4\\cdot1 \\\\\n\\end{bmatrix} = \\begin{bmatrix} \n19 & 7 \\\\\n17 & 6 \\\\\n\\end{bmatrix}.$",
|
|
83
|
+
images: {},
|
|
84
|
+
widgets: {},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
content:
|
|
88
|
+
"## What can we do with the inverse of a matrix?\n\nFinding the inverse of a matrix can be useful for solving linear systems of equations. If $A$ is the matrix representing the system of equations, and $b$ is the vector of solutions, then $Ax=b$. If we can find the inverse of $A$, we can multiply both sides of the equation by it to isolate $x$:\n\n$A^{-1}Ax=A^{-1}b \\Rightarrow x=A^{-1}b.$",
|
|
89
|
+
images: {},
|
|
90
|
+
widgets: {},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
content:
|
|
94
|
+
"## Where are matrices used in the real world?\n\nMatrices have tons of real-world applications! They can be used in computer graphics to perform transformations on images, in physics to model physical systems, and in statistics to analyze data, just to name a few.",
|
|
95
|
+
images: {},
|
|
96
|
+
widgets: {},
|
|
97
|
+
},
|
|
98
|
+
];
|
|
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import "@testing-library/jest-dom"; // Imports custom matchers
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
testDependencies,
|
|
9
|
+
testDependenciesV2,
|
|
10
|
+
} from "../../../../testing/test-dependencies";
|
|
8
11
|
import {
|
|
9
12
|
itemWithInput,
|
|
10
13
|
labelImageItem,
|
|
@@ -76,6 +79,7 @@ export const renderQuestion = (
|
|
|
76
79
|
reviewMode={false}
|
|
77
80
|
savedState=""
|
|
78
81
|
controlPeripherals={false}
|
|
82
|
+
dependencies={testDependenciesV2}
|
|
79
83
|
{...optionalProps}
|
|
80
84
|
/>
|
|
81
85
|
{/* The ItemRenderer _requires_ two divs: a work area and hints
|
|
@@ -132,6 +136,13 @@ describe("item renderer", () => {
|
|
|
132
136
|
});
|
|
133
137
|
|
|
134
138
|
beforeEach(() => {
|
|
139
|
+
// Mock ResizeObserver used by the mobile keypad
|
|
140
|
+
window.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
141
|
+
observe: jest.fn(),
|
|
142
|
+
unobserve: jest.fn(),
|
|
143
|
+
disconnect: jest.fn(),
|
|
144
|
+
}));
|
|
145
|
+
|
|
135
146
|
jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
|
|
136
147
|
testDependencies,
|
|
137
148
|
);
|
|
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import "@testing-library/jest-dom"; // Imports custom matchers
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
testDependencies,
|
|
9
|
+
testDependenciesV2,
|
|
10
|
+
} from "../../../../testing/test-dependencies";
|
|
8
11
|
import {
|
|
9
12
|
itemWithInput,
|
|
10
13
|
mockedItem,
|
|
@@ -51,6 +54,7 @@ const renderQuestion = (
|
|
|
51
54
|
item={question}
|
|
52
55
|
problemNum={0}
|
|
53
56
|
reviewMode={false}
|
|
57
|
+
dependencies={testDependenciesV2}
|
|
54
58
|
{...optionalProps}
|
|
55
59
|
/>
|
|
56
60
|
</RenderStateRoot>,
|
|
@@ -228,6 +232,7 @@ describe("server item renderer", () => {
|
|
|
228
232
|
item={itemWithInput}
|
|
229
233
|
problemNum={0}
|
|
230
234
|
reviewMode={false}
|
|
235
|
+
dependencies={testDependenciesV2}
|
|
231
236
|
/>
|
|
232
237
|
</RenderStateRoot>,
|
|
233
238
|
);
|
|
@@ -243,6 +248,7 @@ describe("server item renderer", () => {
|
|
|
243
248
|
item={itemWithInput}
|
|
244
249
|
problemNum={1} // to force componentDidUpdate
|
|
245
250
|
reviewMode={false}
|
|
251
|
+
dependencies={testDependenciesV2}
|
|
246
252
|
/>
|
|
247
253
|
,
|
|
248
254
|
</RenderStateRoot>,
|
|
@@ -272,6 +278,7 @@ describe("server item renderer", () => {
|
|
|
272
278
|
problemNum={0}
|
|
273
279
|
reviewMode={false}
|
|
274
280
|
onRendered={onRendered}
|
|
281
|
+
dependencies={testDependenciesV2}
|
|
275
282
|
/>
|
|
276
283
|
</RenderStateRoot>,
|
|
277
284
|
);
|
|
@@ -293,6 +300,7 @@ describe("server item renderer", () => {
|
|
|
293
300
|
problemNum={1}
|
|
294
301
|
reviewMode={false}
|
|
295
302
|
onRendered={onRendered}
|
|
303
|
+
dependencies={testDependenciesV2}
|
|
296
304
|
/>
|
|
297
305
|
</RenderStateRoot>,
|
|
298
306
|
);
|
package/src/article-renderer.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import * as PerseusLinter from "@khanacademy/perseus-linter";
|
|
|
7
7
|
import classNames from "classnames";
|
|
8
8
|
import * as React from "react";
|
|
9
9
|
|
|
10
|
+
import {DependenciesContext} from "./dependencies";
|
|
10
11
|
import ProvideKeypad from "./mixins/provide-keypad";
|
|
11
12
|
import {ClassNames as ApiClassNames, ApiOptions} from "./perseus-api";
|
|
12
13
|
import Renderer from "./renderer";
|
|
@@ -14,13 +15,12 @@ import Util from "./util";
|
|
|
14
15
|
|
|
15
16
|
import type {KeypadProps} from "./mixins/provide-keypad";
|
|
16
17
|
import type {PerseusRenderer} from "./perseus-types";
|
|
17
|
-
import type {APIOptions} from "./types";
|
|
18
|
+
import type {APIOptions, PerseusDependenciesV2} from "./types";
|
|
18
19
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
19
20
|
|
|
20
21
|
type Props = {
|
|
21
22
|
apiOptions: APIOptions;
|
|
22
23
|
json: PerseusRenderer | ReadonlyArray<PerseusRenderer>;
|
|
23
|
-
|
|
24
24
|
// Whether to use the new Bibliotron styles for articles
|
|
25
25
|
/**
|
|
26
26
|
* @deprecated Does nothing
|
|
@@ -28,6 +28,8 @@ type Props = {
|
|
|
28
28
|
useNewStyles: boolean;
|
|
29
29
|
linterContext: LinterContextProps;
|
|
30
30
|
legacyPerseusLint?: ReadonlyArray<string>;
|
|
31
|
+
|
|
32
|
+
dependencies: PerseusDependenciesV2;
|
|
31
33
|
} & KeypadProps;
|
|
32
34
|
|
|
33
35
|
type DefaultProps = {
|
|
@@ -180,34 +182,40 @@ class ArticleRenderer extends React.Component<Props, State> {
|
|
|
180
182
|
const sections = this._sections().map((section, i) => {
|
|
181
183
|
const refForSection = `section-${i}`;
|
|
182
184
|
return (
|
|
183
|
-
<
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
185
|
+
<DependenciesContext.Provider value={this.props.dependencies}>
|
|
186
|
+
<div key={i} className="clearfix">
|
|
187
|
+
<Renderer
|
|
188
|
+
{...section}
|
|
189
|
+
ref={refForSection}
|
|
190
|
+
key={i}
|
|
191
|
+
key_={i}
|
|
192
|
+
keypadElement={this.keypadElement()}
|
|
193
|
+
apiOptions={{
|
|
194
|
+
...apiOptions,
|
|
195
|
+
onFocusChange: (newFocusPath, oldFocusPath) => {
|
|
196
|
+
// Prefix the paths with the relevant section,
|
|
197
|
+
// so as to allow us to distinguish between
|
|
198
|
+
// equivalently-named inputs across Renderers.
|
|
199
|
+
this._handleFocusChange(
|
|
200
|
+
newFocusPath &&
|
|
201
|
+
[refForSection].concat(
|
|
202
|
+
newFocusPath,
|
|
203
|
+
),
|
|
204
|
+
oldFocusPath &&
|
|
205
|
+
[refForSection].concat(
|
|
206
|
+
oldFocusPath,
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
},
|
|
210
|
+
}}
|
|
211
|
+
linterContext={PerseusLinter.pushContextStack(
|
|
212
|
+
this.props.linterContext,
|
|
213
|
+
"article",
|
|
214
|
+
)}
|
|
215
|
+
legacyPerseusLint={this.props.legacyPerseusLint}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
</DependenciesContext.Provider>
|
|
211
219
|
);
|
|
212
220
|
});
|
|
213
221
|
|
package/src/dependencies.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import type {PerseusDependencies, PerseusDependenciesV2} from "./types";
|
|
2
4
|
|
|
3
5
|
let _dependencies: PerseusDependencies | null | undefined = null;
|
|
4
6
|
|
|
@@ -19,3 +21,16 @@ export const getDependencies = (): PerseusDependencies => {
|
|
|
19
21
|
].join("\n"),
|
|
20
22
|
);
|
|
21
23
|
};
|
|
24
|
+
|
|
25
|
+
export const DependenciesContext = React.createContext<PerseusDependenciesV2>({
|
|
26
|
+
analytics: {onAnalyticsEvent: async () => {}},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* useDependencies provides easy access to the current Perseus dependencies.
|
|
31
|
+
*/
|
|
32
|
+
export const useDependencies = (): PerseusDependenciesV2 => {
|
|
33
|
+
const deps = React.useContext(DependenciesContext);
|
|
34
|
+
|
|
35
|
+
return deps;
|
|
36
|
+
};
|
|
@@ -8,12 +8,12 @@ function _noop() {}
|
|
|
8
8
|
/**
|
|
9
9
|
* Wrapper for the trackInteraction apiOption.
|
|
10
10
|
*/
|
|
11
|
-
class InteractionTracker {
|
|
11
|
+
class InteractionTracker<T> {
|
|
12
12
|
// @ts-expect-error - TS2564 - Property '_tracked' has no initializer and is not definitely assigned in the constructor.
|
|
13
13
|
_tracked: boolean;
|
|
14
14
|
// @ts-expect-error - TS2564 - Property 'setting' has no initializer and is not definitely assigned in the constructor.
|
|
15
15
|
setting: Tracking;
|
|
16
|
-
track: (extraData?:
|
|
16
|
+
track: (extraData?: T) => void;
|
|
17
17
|
trackApi: any;
|
|
18
18
|
// @ts-expect-error - TS2564 - Property 'widgetID' has no initializer and is not definitely assigned in the constructor.
|
|
19
19
|
widgetID: string;
|
|
@@ -46,7 +46,7 @@ class InteractionTracker {
|
|
|
46
46
|
* @param extraData Any extra data to track about the event.
|
|
47
47
|
* @private
|
|
48
48
|
*/
|
|
49
|
-
_track: (extraData:
|
|
49
|
+
_track: (extraData: T) => void = (extraData) => {
|
|
50
50
|
if (this._tracked && !this.setting) {
|
|
51
51
|
return;
|
|
52
52
|
}
|
package/src/item-renderer.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import * as React from "react";
|
|
|
6
6
|
import * as ReactDOM from "react-dom";
|
|
7
7
|
import _ from "underscore";
|
|
8
8
|
|
|
9
|
+
import {DependenciesContext} from "./dependencies";
|
|
9
10
|
import HintsRenderer from "./hints-renderer";
|
|
10
11
|
import Objective from "./interactive2/objective_";
|
|
11
12
|
import ProvideKeypad from "./mixins/provide-keypad";
|
|
@@ -16,7 +17,12 @@ import reactRender from "./util/react-render";
|
|
|
16
17
|
|
|
17
18
|
import type {KeypadProps} from "./mixins/provide-keypad";
|
|
18
19
|
import type {PerseusItem} from "./perseus-types";
|
|
19
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
APIOptions,
|
|
22
|
+
FocusPath,
|
|
23
|
+
LinterContextProps,
|
|
24
|
+
PerseusDependenciesV2,
|
|
25
|
+
} from "./types";
|
|
20
26
|
import type {KEScore} from "@khanacademy/perseus-core";
|
|
21
27
|
|
|
22
28
|
const {mapObject} = Objective;
|
|
@@ -46,6 +52,8 @@ type Props = // These props are used by the ProvideKeypad mixin.
|
|
|
46
52
|
savedState: any;
|
|
47
53
|
linterContext: LinterContextProps;
|
|
48
54
|
legacyPerseusLint?: ReadonlyArray<string>;
|
|
55
|
+
|
|
56
|
+
dependencies: PerseusDependenciesV2;
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
type DefaultProps = {
|
|
@@ -176,53 +184,59 @@ class ItemRenderer extends React.Component<Props, State> {
|
|
|
176
184
|
// strangeness instead of relying on React's normal render() method.
|
|
177
185
|
// TODO(alpert): Figure out how to clean this up somehow
|
|
178
186
|
reactRender(
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.props.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
187
|
+
<DependenciesContext.Provider value={this.props.dependencies}>
|
|
188
|
+
<Renderer
|
|
189
|
+
ref={(node) => {
|
|
190
|
+
if (!node) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.questionRenderer = node;
|
|
194
|
+
|
|
195
|
+
// NOTE(jeremy): Why don't we just pass this into the
|
|
196
|
+
// renderer as a prop?
|
|
197
|
+
const {answerableCallback} = apiOptions;
|
|
198
|
+
if (answerableCallback) {
|
|
199
|
+
const isAnswerable =
|
|
200
|
+
this.questionRenderer.emptyWidgets().length ===
|
|
201
|
+
0;
|
|
202
|
+
answerableCallback(isAnswerable);
|
|
203
|
+
}
|
|
204
|
+
}}
|
|
205
|
+
keypadElement={this.keypadElement()}
|
|
206
|
+
problemNum={this.props.problemNum}
|
|
207
|
+
onInteractWithWidget={this.handleInteractWithWidget}
|
|
208
|
+
highlightedWidgets={this.state.questionHighlightedWidgets}
|
|
209
|
+
apiOptions={apiOptions}
|
|
210
|
+
questionCompleted={this.state.questionCompleted}
|
|
211
|
+
reviewMode={this.props.reviewMode}
|
|
212
|
+
savedState={this.props.savedState}
|
|
213
|
+
linterContext={PerseusLinter.pushContextStack(
|
|
214
|
+
this.props.linterContext,
|
|
215
|
+
"question",
|
|
216
|
+
)}
|
|
217
|
+
{...this.props.item.question}
|
|
218
|
+
legacyPerseusLint={this.props.legacyPerseusLint}
|
|
219
|
+
/>
|
|
220
|
+
</DependenciesContext.Provider>,
|
|
210
221
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
211
222
|
workArea,
|
|
212
223
|
);
|
|
213
224
|
|
|
214
225
|
reactRender(
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
<DependenciesContext.Provider value={this.props.dependencies}>
|
|
227
|
+
<HintsRenderer
|
|
228
|
+
ref={(node) => (this.hintsRenderer = node)}
|
|
229
|
+
hints={this.props.item.hints}
|
|
230
|
+
hintsVisible={this.state.hintsVisible}
|
|
231
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
232
|
+
apiOptions={apiOptions}
|
|
233
|
+
linterContext={PerseusLinter.pushContextStack(
|
|
234
|
+
this.props.linterContext,
|
|
235
|
+
"hints",
|
|
236
|
+
)}
|
|
237
|
+
/>
|
|
238
|
+
</DependenciesContext.Provider>,
|
|
239
|
+
|
|
226
240
|
// @ts-expect-error - TS2345 - Argument of type 'Element' is not assignable to parameter of type 'HTMLElement'.
|
|
227
241
|
hintsArea,
|
|
228
242
|
);
|
|
@@ -504,7 +518,11 @@ class ItemRenderer extends React.Component<Props, State> {
|
|
|
504
518
|
}
|
|
505
519
|
|
|
506
520
|
render(): React.ReactNode {
|
|
507
|
-
return
|
|
521
|
+
return (
|
|
522
|
+
<DependenciesContext.Provider value={this.props.dependencies}>
|
|
523
|
+
<div />
|
|
524
|
+
</DependenciesContext.Provider>
|
|
525
|
+
);
|
|
508
526
|
}
|
|
509
527
|
}
|
|
510
528
|
|
|
@@ -4,7 +4,10 @@ import userEvent from "@testing-library/user-event";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
testDependencies,
|
|
9
|
+
testDependenciesV2,
|
|
10
|
+
} from "../../../../../testing/test-dependencies";
|
|
8
11
|
import MockWidgetExport from "../../__tests__/mock-widget";
|
|
9
12
|
import * as Dependencies from "../../dependencies";
|
|
10
13
|
import {registerAllWidgetsForTesting} from "../../util/register-all-widgets-for-testing";
|
|
@@ -50,6 +53,7 @@ const renderSimpleQuestion = (question: Item) => {
|
|
|
50
53
|
item={question}
|
|
51
54
|
shape={simpleQuestionShape}
|
|
52
55
|
ref={(r) => (renderer = r)}
|
|
56
|
+
dependencies={testDependenciesV2}
|
|
53
57
|
>
|
|
54
58
|
{({renderers}) => <SimpleLayout renderers={renderers} />}
|
|
55
59
|
</MultiRenderer>
|
|
@@ -119,6 +123,7 @@ describe("multi-item renderer", () => {
|
|
|
119
123
|
<MultiRenderer
|
|
120
124
|
item={question1}
|
|
121
125
|
shape={shapes.shape({broken: shapes.content})}
|
|
126
|
+
dependencies={testDependenciesV2}
|
|
122
127
|
>
|
|
123
128
|
{({renderers}) => {
|
|
124
129
|
return <div />;
|
|
@@ -41,6 +41,7 @@ import {StyleSheet, css} from "aphrodite"; // eslint-disable-line import/no-extr
|
|
|
41
41
|
import lens from "hubble"; // eslint-disable-line import/no-extraneous-dependencies
|
|
42
42
|
import * as React from "react";
|
|
43
43
|
|
|
44
|
+
import {DependenciesContext} from "../dependencies";
|
|
44
45
|
import HintsRenderer from "../hints-renderer";
|
|
45
46
|
import {Errors, Log} from "../logging/log";
|
|
46
47
|
import Renderer from "../renderer";
|
|
@@ -50,7 +51,12 @@ import {itemToTree} from "./items";
|
|
|
50
51
|
import {buildMapper} from "./trees";
|
|
51
52
|
|
|
52
53
|
import type {Widget} from "../renderer";
|
|
53
|
-
import type {
|
|
54
|
+
import type {
|
|
55
|
+
APIOptions,
|
|
56
|
+
FilterCriterion,
|
|
57
|
+
PerseusDependenciesV2,
|
|
58
|
+
PerseusScore,
|
|
59
|
+
} from "../types";
|
|
54
60
|
import type {Item, ContentNode, HintNode, TagsNode} from "./item-types";
|
|
55
61
|
import type {Shape, ArrayShape} from "./shape-types";
|
|
56
62
|
import type {Tree} from "./tree-types";
|
|
@@ -109,6 +115,8 @@ type Props = {
|
|
|
109
115
|
onInteractWithWidget?: (id: string) => void;
|
|
110
116
|
apiOptions?: APIOptions;
|
|
111
117
|
reviewMode?: boolean | null | undefined;
|
|
118
|
+
|
|
119
|
+
dependencies: PerseusDependenciesV2;
|
|
112
120
|
};
|
|
113
121
|
type State = {
|
|
114
122
|
// We cache functions to generate renderers and refs in `rendererDataTree`,
|
|
@@ -547,9 +555,13 @@ class MultiRenderer extends React.Component<Props, State> {
|
|
|
547
555
|
|
|
548
556
|
// Pass the renderer tree to the `children` function, which will
|
|
549
557
|
// determine the actual content of this component.
|
|
550
|
-
return
|
|
551
|
-
|
|
552
|
-
|
|
558
|
+
return (
|
|
559
|
+
<DependenciesContext.Provider value={this.props.dependencies}>
|
|
560
|
+
{this.props.children({
|
|
561
|
+
renderers: this._getRenderers(),
|
|
562
|
+
})}
|
|
563
|
+
</DependenciesContext.Provider>
|
|
564
|
+
);
|
|
553
565
|
}
|
|
554
566
|
}
|
|
555
567
|
|
package/src/renderer.tsx
CHANGED
|
@@ -213,7 +213,7 @@ class Renderer extends React.Component<Props, State> {
|
|
|
213
213
|
_foundTextNodes: boolean;
|
|
214
214
|
// @ts-expect-error - TS2564 - Property '_interactionTrackers' has no initializer and is not definitely assigned in the constructor.
|
|
215
215
|
_interactionTrackers: {
|
|
216
|
-
[id: string]: InteractionTracker
|
|
216
|
+
[id: string]: InteractionTracker<any>;
|
|
217
217
|
};
|
|
218
218
|
// @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
|
|
219
219
|
_isMounted: boolean;
|