@khanacademy/perseus 18.0.1 → 19.1.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 +36 -0
- package/dist/es/index.css +209 -128
- package/dist/es/index.js +1026 -3516
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +209 -128
- package/dist/index.d.ts +3 -3
- package/dist/index.js +917 -3409
- package/dist/index.js.map +1 -1
- package/dist/mixins/changeable.d.ts +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/perseus-types.d.ts +2 -109
- package/dist/renderer.d.ts +2 -4
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/strings.js +0 -29
- package/dist/types.d.ts +4 -4
- package/dist/util/geometry.d.ts +2 -0
- package/dist/util/graphie.d.ts +107 -1
- package/dist/util/math.d.ts +2 -0
- package/dist/util/parse-perseus-json.d.ts +3 -3
- package/dist/widgets/expression.d.ts +8 -2
- package/dist/widgets/label-image.d.ts +2 -0
- package/dist/widgets.d.ts +1 -1
- package/package.json +3 -4
- package/src/__testdata__/renderer.testdata.ts +152 -0
- package/src/__tests__/renderer.test.tsx +17 -0
- package/src/__tests__/widgets.test.ts +0 -1
- package/src/components/math-input.tsx +4 -6
- package/src/extra-widgets.ts +0 -2
- package/src/index.ts +8 -4
- package/src/mixins/changeable.ts +1 -1
- package/src/perseus-types.ts +1 -154
- package/src/renderer.tsx +14 -8
- package/src/styles/perseus-renderer.less +0 -1
- package/src/types.ts +9 -5
- package/src/util/geometry.test.ts +75 -0
- package/src/util/geometry.ts +19 -0
- package/src/util/graphie.test.ts +1291 -0
- package/src/util/graphie.ts +832 -784
- package/src/util/interactive.ts +23 -51
- package/src/util/math.test.ts +11 -0
- package/src/util/math.ts +8 -0
- package/src/util/parse-perseus-json.test.ts +3 -3
- package/src/util/parse-perseus-json.ts +4 -4
- package/src/util/register-all-widgets-for-testing.ts +1 -3
- package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +4 -2
- package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +4 -2
- package/src/widgets/__tests__/expression-mobile.test.tsx +8 -5
- package/src/widgets/__tests__/renderQuestion.tsx +11 -3
- package/src/widgets/expression.tsx +1 -23
- package/src/widgets/label-image.tsx +0 -1
- package/src/widgets.ts +1 -9
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/widgets/transformer.d.ts +0 -94
- package/src/styles/widgets/transformer.less +0 -29
- package/src/widgets/__stories__/transformer.stories.tsx +0 -14
- package/src/widgets/__testdata__/transformer.testdata.ts +0 -107
- package/src/widgets/transformer.tsx +0 -2936
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
InputNumberWidget,
|
|
5
5
|
PerseusRenderer,
|
|
6
6
|
} from "../perseus-types";
|
|
7
|
+
import type {RenderProps} from "../widgets/radio/widget";
|
|
7
8
|
|
|
8
9
|
export const dropdownWidget: DropdownWidget = {
|
|
9
10
|
type: "dropdown",
|
|
@@ -120,3 +121,154 @@ export const mockedItem: PerseusRenderer = {
|
|
|
120
121
|
},
|
|
121
122
|
},
|
|
122
123
|
} as PerseusRenderer;
|
|
124
|
+
|
|
125
|
+
// Note that if this item is used, you _must_ first register the MockWidget
|
|
126
|
+
export const mockedRandomItem: PerseusRenderer = {
|
|
127
|
+
content: "Mock widgets ==> [[\u2603 radio 1]] [[\u2603 radio 2]]",
|
|
128
|
+
images: {},
|
|
129
|
+
widgets: {
|
|
130
|
+
"radio 1": {
|
|
131
|
+
graded: true,
|
|
132
|
+
version: {major: 0, minor: 0},
|
|
133
|
+
static: false,
|
|
134
|
+
numCorrect: 1,
|
|
135
|
+
hasNoneOfTheAbove: false,
|
|
136
|
+
multipleSelect: false,
|
|
137
|
+
countChoices: false,
|
|
138
|
+
deselectEnabled: false,
|
|
139
|
+
type: "radio",
|
|
140
|
+
options: {
|
|
141
|
+
static: false,
|
|
142
|
+
countChoices: false,
|
|
143
|
+
deselectEnabled: false,
|
|
144
|
+
displayCount: null,
|
|
145
|
+
hasNoneOfTheAbove: false,
|
|
146
|
+
multipleSelect: false,
|
|
147
|
+
randomize: true,
|
|
148
|
+
choices: [
|
|
149
|
+
{
|
|
150
|
+
content: "Content 1",
|
|
151
|
+
correct: true,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
content: "Content 2",
|
|
155
|
+
correct: false,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
content: "Content 3",
|
|
159
|
+
correct: false,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
content: "Content 4",
|
|
163
|
+
correct: false,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
alignment: "default",
|
|
168
|
+
},
|
|
169
|
+
"radio 2": {
|
|
170
|
+
graded: true,
|
|
171
|
+
version: {major: 0, minor: 0},
|
|
172
|
+
static: false,
|
|
173
|
+
numCorrect: 1,
|
|
174
|
+
hasNoneOfTheAbove: false,
|
|
175
|
+
multipleSelect: false,
|
|
176
|
+
countChoices: false,
|
|
177
|
+
deselectEnabled: false,
|
|
178
|
+
type: "radio",
|
|
179
|
+
options: {
|
|
180
|
+
static: false,
|
|
181
|
+
countChoices: false,
|
|
182
|
+
deselectEnabled: false,
|
|
183
|
+
displayCount: null,
|
|
184
|
+
hasNoneOfTheAbove: false,
|
|
185
|
+
multipleSelect: false,
|
|
186
|
+
randomize: true,
|
|
187
|
+
choices: [
|
|
188
|
+
{
|
|
189
|
+
content: "Content 1",
|
|
190
|
+
correct: true,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
content: "Content 2",
|
|
194
|
+
correct: false,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
content: "Content 3",
|
|
198
|
+
correct: false,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
content: "Content 4",
|
|
202
|
+
correct: false,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
alignment: "default",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
} as PerseusRenderer;
|
|
210
|
+
|
|
211
|
+
export const mockedShuffledRadioProps: {[key in string]: RenderProps} = {
|
|
212
|
+
"radio 1": {
|
|
213
|
+
numCorrect: 1,
|
|
214
|
+
countChoices: false,
|
|
215
|
+
deselectEnabled: false,
|
|
216
|
+
hasNoneOfTheAbove: false,
|
|
217
|
+
multipleSelect: false,
|
|
218
|
+
choices: [
|
|
219
|
+
{
|
|
220
|
+
content: "Content 4",
|
|
221
|
+
correct: false,
|
|
222
|
+
originalIndex: 3,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
content: "Content 2",
|
|
226
|
+
correct: false,
|
|
227
|
+
originalIndex: 1,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
content: "Content 1",
|
|
231
|
+
correct: true,
|
|
232
|
+
originalIndex: 0,
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
{
|
|
236
|
+
content: "Content 3",
|
|
237
|
+
correct: false,
|
|
238
|
+
originalIndex: 2,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
selectedChoices: [false, false, true, false],
|
|
242
|
+
},
|
|
243
|
+
"radio 2": {
|
|
244
|
+
numCorrect: 1,
|
|
245
|
+
countChoices: false,
|
|
246
|
+
deselectEnabled: false,
|
|
247
|
+
hasNoneOfTheAbove: false,
|
|
248
|
+
multipleSelect: false,
|
|
249
|
+
choices: [
|
|
250
|
+
{
|
|
251
|
+
content: "Content 4",
|
|
252
|
+
correct: false,
|
|
253
|
+
originalIndex: 3,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
content: "Content 2",
|
|
257
|
+
correct: false,
|
|
258
|
+
originalIndex: 1,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
content: "Content 1",
|
|
262
|
+
correct: true,
|
|
263
|
+
originalIndex: 0,
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
{
|
|
267
|
+
content: "Content 3",
|
|
268
|
+
correct: false,
|
|
269
|
+
originalIndex: 2,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
selectedChoices: [false, false, true, false],
|
|
273
|
+
},
|
|
274
|
+
};
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
question1,
|
|
15
15
|
question2,
|
|
16
16
|
mockedItem,
|
|
17
|
+
mockedRandomItem,
|
|
18
|
+
mockedShuffledRadioProps,
|
|
17
19
|
} from "../__testdata__/renderer.testdata";
|
|
18
20
|
import * as Dependencies from "../dependencies";
|
|
19
21
|
import {Errors} from "../logging/log";
|
|
@@ -29,6 +31,7 @@ import type {
|
|
|
29
31
|
PerseusImageWidgetOptions,
|
|
30
32
|
PerseusInputNumberWidgetOptions,
|
|
31
33
|
} from "../perseus-types";
|
|
34
|
+
import type {APIOptions} from "../types";
|
|
32
35
|
|
|
33
36
|
// NOTE(jeremy): We can't use an automatic mock for the translation linter,
|
|
34
37
|
// because one of it's "instance" methods is created using `debounce` and Jest
|
|
@@ -287,6 +290,20 @@ describe("renderer", () => {
|
|
|
287
290
|
/^less than or equal to$/,
|
|
288
291
|
);
|
|
289
292
|
});
|
|
293
|
+
|
|
294
|
+
it("should call the onWidgetStartProps callback if provided in apiOptions", () => {
|
|
295
|
+
// Arrange
|
|
296
|
+
const onWidgetStartProps = jest.fn();
|
|
297
|
+
const apiOptions: APIOptions = {onWidgetStartProps};
|
|
298
|
+
|
|
299
|
+
// Act
|
|
300
|
+
renderQuestion(mockedRandomItem, apiOptions);
|
|
301
|
+
|
|
302
|
+
// Assert
|
|
303
|
+
expect(onWidgetStartProps).toHaveBeenCalledWith(
|
|
304
|
+
mockedShuffledRadioProps,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
290
307
|
});
|
|
291
308
|
|
|
292
309
|
describe("rendering", () => {
|
|
@@ -157,7 +157,7 @@ class MathInput extends React.Component<Props, State> {
|
|
|
157
157
|
(baseConfig) => ({
|
|
158
158
|
...baseConfig,
|
|
159
159
|
handlers: {
|
|
160
|
-
edit: (mathField) => {
|
|
160
|
+
edit: (mathField: MathFieldInterface) => {
|
|
161
161
|
// This handler is guaranteed to be called on change, but
|
|
162
162
|
// unlike React it sometimes generates false positives.
|
|
163
163
|
// One of these is on initialization (with an empty string
|
|
@@ -184,11 +184,9 @@ class MathInput extends React.Component<Props, State> {
|
|
|
184
184
|
// directly, instead of waiting for `.latex()` to be
|
|
185
185
|
// called in `componentDidUpdate()`.
|
|
186
186
|
const left =
|
|
187
|
-
mathField.
|
|
188
|
-
mathQuillInstance.L
|
|
189
|
-
];
|
|
187
|
+
mathField.cursor()[mathQuillInstance.L];
|
|
190
188
|
if (left && left.ctrlSeq === "\\cdot ") {
|
|
191
|
-
mathField.
|
|
189
|
+
mathField.controller().backspace();
|
|
192
190
|
mathField.cmd("\\times");
|
|
193
191
|
}
|
|
194
192
|
} else {
|
|
@@ -214,7 +212,7 @@ class MathInput extends React.Component<Props, State> {
|
|
|
214
212
|
$(this.__mathFieldWrapperRef).submit();
|
|
215
213
|
}
|
|
216
214
|
},
|
|
217
|
-
upOutOf: (mathField) => {
|
|
215
|
+
upOutOf: (mathField: MathFieldInterface) => {
|
|
218
216
|
// This handler is called when the user presses the up
|
|
219
217
|
// arrow key, but there is nowhere in the expression to go
|
|
220
218
|
// up to (no numerator or exponent). For ease of use,
|
package/src/extra-widgets.ts
CHANGED
|
@@ -33,7 +33,6 @@ import Sequence from "./widgets/sequence";
|
|
|
33
33
|
import Simulator from "./widgets/simulator";
|
|
34
34
|
import Sorter from "./widgets/sorter";
|
|
35
35
|
import Table from "./widgets/table";
|
|
36
|
-
import Transformer from "./widgets/transformer";
|
|
37
36
|
import Unit from "./widgets/unit";
|
|
38
37
|
import Video from "./widgets/video";
|
|
39
38
|
|
|
@@ -70,7 +69,6 @@ export default [
|
|
|
70
69
|
Simulator,
|
|
71
70
|
Sorter,
|
|
72
71
|
Table,
|
|
73
|
-
Transformer,
|
|
74
72
|
Unit,
|
|
75
73
|
Video,
|
|
76
74
|
DeprecatedStandin,
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,6 @@ export {default as Radio} from "./widgets/radio";
|
|
|
31
31
|
export {default as BaseRadio} from "./widgets/radio/base-radio";
|
|
32
32
|
export {default as Categorizer} from "./widgets/categorizer";
|
|
33
33
|
export {default as InteractiveGraphWidget} from "./widgets/interactive-graph";
|
|
34
|
-
export {default as TransformerWidget} from "./widgets/transformer";
|
|
35
34
|
export {default as MatrixWidget} from "./widgets/matrix";
|
|
36
35
|
export {default as TableWidget} from "./widgets/table";
|
|
37
36
|
export {default as PlotterWidget} from "./widgets/plotter";
|
|
@@ -84,7 +83,7 @@ export {
|
|
|
84
83
|
getAnswersFromWidgets,
|
|
85
84
|
injectWidgets,
|
|
86
85
|
} from "./util/extract-perseus-data";
|
|
87
|
-
export {
|
|
86
|
+
export {parsePerseusItem} from "./util/parse-perseus-json";
|
|
88
87
|
|
|
89
88
|
/**
|
|
90
89
|
* Mixins
|
|
@@ -124,11 +123,16 @@ export type {
|
|
|
124
123
|
} from "./types";
|
|
125
124
|
export type {ParsedValue} from "./util";
|
|
126
125
|
export type {
|
|
127
|
-
InputNumberWidget,
|
|
128
|
-
MathFormat,
|
|
129
126
|
PerseusAnswerArea,
|
|
130
127
|
PerseusExpressionWidgetOptions,
|
|
128
|
+
// Utility types
|
|
129
|
+
Range,
|
|
130
|
+
Size,
|
|
131
|
+
MathFormat,
|
|
132
|
+
InputNumberWidget, // TODO(jeremy): remove?
|
|
133
|
+
// Widget configuration types
|
|
131
134
|
PerseusImageBackground,
|
|
135
|
+
PerseusInputNumberWidgetOptions,
|
|
132
136
|
PerseusInteractiveGraphWidgetOptions,
|
|
133
137
|
PerseusItem,
|
|
134
138
|
PerseusPlotterWidgetOptions,
|
package/src/mixins/changeable.ts
CHANGED
package/src/perseus-types.ts
CHANGED
|
@@ -90,7 +90,7 @@ type Widget<Type extends string, Options> = {
|
|
|
90
90
|
// Whether this widget is displayed with the values and is immutable. For display only
|
|
91
91
|
// NOTE: perseus_data.go says this is required even though it isn't necessary.
|
|
92
92
|
static?: boolean;
|
|
93
|
-
// Whether a widget is scored. Usually true except for IFrame widgets (deprecated)
|
|
93
|
+
// Whether a widget is scored. Usually true except for IFrame widgets (deprecated)
|
|
94
94
|
// Default: true
|
|
95
95
|
graded?: boolean;
|
|
96
96
|
// The HTML alignment of the widget. "default" or "block"
|
|
@@ -164,9 +164,6 @@ export type SimulatorWidget = Widget<'simulator', PerseusSimulatorWidgetOptions>
|
|
|
164
164
|
export type SorterWidget = Widget<'sorter', PerseusSorterWidgetOptions>;
|
|
165
165
|
// prettier-ignore
|
|
166
166
|
export type TableWidget = Widget<'table', PerseusTableWidgetOptions>;
|
|
167
|
-
// prettier-ignore
|
|
168
|
-
export type TransformerWidget = Widget<'transformer', PerseusTransformerWidgetOptions>;
|
|
169
|
-
|
|
170
167
|
// prettier-ignore
|
|
171
168
|
export type ExampleGraphieWidget = Widget<'example-graphie-widget', PerseusExampleGraphieWidgetOptions>;
|
|
172
169
|
// prettier-ignore
|
|
@@ -228,7 +225,6 @@ export type PerseusWidget =
|
|
|
228
225
|
| SimulatorWidget
|
|
229
226
|
| SorterWidget
|
|
230
227
|
| TableWidget
|
|
231
|
-
| TransformerWidget
|
|
232
228
|
| UnitInputWidget
|
|
233
229
|
| VideoWidget
|
|
234
230
|
| AutoCorrectWidget;
|
|
@@ -456,7 +452,6 @@ export type PerseusGrapherWidgetOptions = {
|
|
|
456
452
|
// Two points on the same slope in the tangent wave line.
|
|
457
453
|
coords: [Coord, Coord];
|
|
458
454
|
};
|
|
459
|
-
// TODO(jeremy): Unify with PerseusTransformerWidgetOptions's .graph
|
|
460
455
|
graph: {
|
|
461
456
|
backgroundImage: {
|
|
462
457
|
bottom?: number;
|
|
@@ -990,115 +985,6 @@ export type PerseusTableWidgetOptions = {
|
|
|
990
985
|
answers: ReadonlyArray<ReadonlyArray<string>>;
|
|
991
986
|
};
|
|
992
987
|
|
|
993
|
-
export type PerseusTransformerWidgetOptions = {
|
|
994
|
-
correct: {
|
|
995
|
-
shape: {
|
|
996
|
-
type: ReadonlyArray<
|
|
997
|
-
| "polygon-5"
|
|
998
|
-
| "polygon-3"
|
|
999
|
-
| "polygon-4"
|
|
1000
|
-
| "lineSegment"
|
|
1001
|
-
| "polygon-6"
|
|
1002
|
-
| "angle"
|
|
1003
|
-
| "line"
|
|
1004
|
-
| "circle"
|
|
1005
|
-
>;
|
|
1006
|
-
coords: ReadonlyArray<Coord>;
|
|
1007
|
-
options?: ReadonlyArray<{
|
|
1008
|
-
reflex?: boolean;
|
|
1009
|
-
}>;
|
|
1010
|
-
};
|
|
1011
|
-
transformations: ReadonlyArray<PerseusTransformerTransformation>;
|
|
1012
|
-
};
|
|
1013
|
-
drawSolutionShape: boolean;
|
|
1014
|
-
gradeEmpty: boolean;
|
|
1015
|
-
// TODO(jeremy): Unify with PerseusGrapherWidgetOptions's .graph
|
|
1016
|
-
graph: {
|
|
1017
|
-
backgroundImage: {
|
|
1018
|
-
bottom?: number;
|
|
1019
|
-
height?: number;
|
|
1020
|
-
left?: number;
|
|
1021
|
-
scale?: number;
|
|
1022
|
-
url?: string;
|
|
1023
|
-
width?: number;
|
|
1024
|
-
};
|
|
1025
|
-
gridStep?: [number, number];
|
|
1026
|
-
labels?: ["x", "y"];
|
|
1027
|
-
markings: "none" | "graph" | "grid";
|
|
1028
|
-
range: [[number, number], [number, number]];
|
|
1029
|
-
rulerLabel?: string;
|
|
1030
|
-
rulerTicks?: number;
|
|
1031
|
-
showProtractor: boolean;
|
|
1032
|
-
showRuler?: boolean;
|
|
1033
|
-
snapStep?: [number, number];
|
|
1034
|
-
step: [number, number];
|
|
1035
|
-
};
|
|
1036
|
-
graphMode: "dynamic" | "interactive" | "static";
|
|
1037
|
-
listMode: "interactive" | "dynamic" | "static";
|
|
1038
|
-
starting: {
|
|
1039
|
-
shape:
|
|
1040
|
-
| {
|
|
1041
|
-
type: ReadonlyArray<
|
|
1042
|
-
| "polygon-5"
|
|
1043
|
-
| "polygon-3"
|
|
1044
|
-
| "polygon-4"
|
|
1045
|
-
| "lineSegment"
|
|
1046
|
-
| "polygon-6"
|
|
1047
|
-
>;
|
|
1048
|
-
coords: ReadonlyArray<Coord>;
|
|
1049
|
-
options?: ReadonlyArray<Empty>;
|
|
1050
|
-
}
|
|
1051
|
-
| {
|
|
1052
|
-
type: "polygon-3";
|
|
1053
|
-
coords: [Coord, Coord, Coord];
|
|
1054
|
-
}
|
|
1055
|
-
| {
|
|
1056
|
-
type: [
|
|
1057
|
-
| "polygon-3"
|
|
1058
|
-
| "lineSegment"
|
|
1059
|
-
| "polygon-4"
|
|
1060
|
-
| "polygon-5"
|
|
1061
|
-
| "polygon-6"
|
|
1062
|
-
| "line"
|
|
1063
|
-
| "circle",
|
|
1064
|
-
];
|
|
1065
|
-
coords: ReadonlyArray<Coord>;
|
|
1066
|
-
options: [Empty];
|
|
1067
|
-
}
|
|
1068
|
-
| {
|
|
1069
|
-
type: ["polygon-3"];
|
|
1070
|
-
coords: [Coord, Coord, Coord];
|
|
1071
|
-
options?: [Empty];
|
|
1072
|
-
}
|
|
1073
|
-
| {
|
|
1074
|
-
type: ["angle"];
|
|
1075
|
-
coords: [Coord, Coord, Coord];
|
|
1076
|
-
options: [
|
|
1077
|
-
{
|
|
1078
|
-
reflex: boolean;
|
|
1079
|
-
},
|
|
1080
|
-
];
|
|
1081
|
-
}
|
|
1082
|
-
| {
|
|
1083
|
-
type: ["line" | "lineSegment"];
|
|
1084
|
-
coords: [Coord, Coord];
|
|
1085
|
-
options?: [Empty];
|
|
1086
|
-
}
|
|
1087
|
-
| {
|
|
1088
|
-
type: "polygon";
|
|
1089
|
-
coords: ReadonlyArray<Coord>;
|
|
1090
|
-
};
|
|
1091
|
-
transformations: ReadonlyArray<PerseusTransformerTransformation>;
|
|
1092
|
-
};
|
|
1093
|
-
tools: {
|
|
1094
|
-
dilation: DilationTool;
|
|
1095
|
-
reflection: ReflectionTool;
|
|
1096
|
-
rotation: RotationTool;
|
|
1097
|
-
translation: TranslationTool;
|
|
1098
|
-
};
|
|
1099
|
-
version: number;
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
988
|
export type DilationTransformation = {
|
|
1103
989
|
type: "dilation";
|
|
1104
990
|
center: Coord;
|
|
@@ -1131,44 +1017,6 @@ export type TranslationTransformation = {
|
|
|
1131
1017
|
contraints: Empty;
|
|
1132
1018
|
};
|
|
1133
1019
|
|
|
1134
|
-
export type PerseusTransformerTransformation =
|
|
1135
|
-
| DilationTransformation
|
|
1136
|
-
| ReflectionTransformation
|
|
1137
|
-
| RotationTransformation
|
|
1138
|
-
| TranslationTransformation;
|
|
1139
|
-
|
|
1140
|
-
type DilationTool = {
|
|
1141
|
-
constraints: PerseusTransformerToolConstraints;
|
|
1142
|
-
coord: Coord;
|
|
1143
|
-
enabled: boolean;
|
|
1144
|
-
required?: boolean;
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
type ReflectionTool = {
|
|
1148
|
-
constraints: PerseusTransformerToolConstraints;
|
|
1149
|
-
coords: [Coord, Coord];
|
|
1150
|
-
enabled: boolean;
|
|
1151
|
-
required?: boolean;
|
|
1152
|
-
};
|
|
1153
|
-
|
|
1154
|
-
type RotationTool = {
|
|
1155
|
-
constraints: PerseusTransformerToolConstraints;
|
|
1156
|
-
coord: Coord;
|
|
1157
|
-
enabled: boolean;
|
|
1158
|
-
required?: boolean;
|
|
1159
|
-
};
|
|
1160
|
-
|
|
1161
|
-
type TranslationTool = {
|
|
1162
|
-
constraints: Empty;
|
|
1163
|
-
enabled: boolean;
|
|
1164
|
-
required?: boolean;
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
export type PerseusTransformerToolConstraints = {
|
|
1168
|
-
// Whether we should prevent the student from repositioning the tool
|
|
1169
|
-
fixed: boolean;
|
|
1170
|
-
};
|
|
1171
|
-
|
|
1172
1020
|
export type PerseusInteractionWidgetOptions = {
|
|
1173
1021
|
// The definition of the graph
|
|
1174
1022
|
graph: PerseusInteractionGraph;
|
|
@@ -1514,6 +1362,5 @@ export type PerseusWidgetOptions =
|
|
|
1514
1362
|
| PerseusSimulatorWidgetOptions
|
|
1515
1363
|
| PerseusSorterWidgetOptions
|
|
1516
1364
|
| PerseusTableWidgetOptions
|
|
1517
|
-
| PerseusTransformerWidgetOptions
|
|
1518
1365
|
| PerseusUnitInputWidgetOptions
|
|
1519
1366
|
| PerseusVideoWidgetOptions;
|
package/src/renderer.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
2
|
/* eslint-disable react/no-unsafe */
|
|
3
3
|
import * as PerseusLinter from "@khanacademy/perseus-linter";
|
|
4
|
+
import {entries} from "@khanacademy/wonder-stuff-core";
|
|
4
5
|
import classNames from "classnames";
|
|
5
6
|
import $ from "jquery";
|
|
6
7
|
import * as React from "react";
|
|
@@ -41,6 +42,7 @@ import type {
|
|
|
41
42
|
FilterCriterion,
|
|
42
43
|
FocusPath,
|
|
43
44
|
PerseusScore,
|
|
45
|
+
PerseusWidgetMap,
|
|
44
46
|
WidgetProps,
|
|
45
47
|
} from "./types";
|
|
46
48
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
@@ -485,18 +487,22 @@ class Renderer extends React.Component<Props, State> {
|
|
|
485
487
|
};
|
|
486
488
|
|
|
487
489
|
_getAllWidgetsStartProps: (
|
|
488
|
-
allWidgetInfo:
|
|
489
|
-
[key: string]: PerseusWidget;
|
|
490
|
-
},
|
|
490
|
+
allWidgetInfo: PerseusWidgetMap,
|
|
491
491
|
props: Props,
|
|
492
|
-
) =>
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
) => PerseusWidgetMap = (allWidgetInfo, props) => {
|
|
493
|
+
const {apiOptions, problemNum} = props;
|
|
494
|
+
const widgetsStartProps: PerseusWidgetMap = {};
|
|
495
|
+
entries(allWidgetInfo).forEach(([key, widgetInfo]) => {
|
|
496
|
+
widgetsStartProps[key] = Widgets.getRendererPropsForWidgetInfo(
|
|
496
497
|
widgetInfo,
|
|
497
|
-
|
|
498
|
+
problemNum,
|
|
498
499
|
);
|
|
499
500
|
});
|
|
501
|
+
|
|
502
|
+
// Call the onWidgetStartProps callback if it exists
|
|
503
|
+
apiOptions?.onWidgetStartProps?.(widgetsStartProps);
|
|
504
|
+
|
|
505
|
+
return widgetsStartProps;
|
|
500
506
|
};
|
|
501
507
|
|
|
502
508
|
// This is only used in _getWidgetInfo as a fallback if widgetId
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {SerializedHighlightSet} from "./components/highlighting/types";
|
|
2
1
|
import type {ILogger} from "./logging/log";
|
|
3
2
|
import type {Item} from "./multi-items/item-types";
|
|
4
3
|
import type {
|
|
@@ -92,10 +91,6 @@ export type ChangeHandler = (
|
|
|
92
91
|
currentValue?: string;
|
|
93
92
|
// perseus-all-package/widgets/dropdown.jsx
|
|
94
93
|
selected?: number;
|
|
95
|
-
// perseus-all-package/widgets/transformer.jsx
|
|
96
|
-
tools?: ReadonlyArray<any>;
|
|
97
|
-
transformations?: ReadonlyArray<any>;
|
|
98
|
-
highlights?: SerializedHighlightSet;
|
|
99
94
|
// perseus-all-package/widgets/grapher.jsx
|
|
100
95
|
plot?: any;
|
|
101
96
|
},
|
|
@@ -228,6 +223,11 @@ export type APIOptions = Readonly<{
|
|
|
228
223
|
// keystroke caused text typed in the text area to appear in it
|
|
229
224
|
// only after a good few seconds.
|
|
230
225
|
editorChangeDelay?: number;
|
|
226
|
+
// This is a callback function that returns all of the Widget props
|
|
227
|
+
// after they have been transformed by the widget's transform function.
|
|
228
|
+
// This is useful for when we need to know how a widget has shuffled its
|
|
229
|
+
// the available choices.
|
|
230
|
+
onWidgetStartProps?: (widgets: PerseusWidgetMap) => PerseusWidgetMap;
|
|
231
231
|
}>;
|
|
232
232
|
|
|
233
233
|
type TeXProps = {
|
|
@@ -453,6 +453,10 @@ export type WidgetExports<
|
|
|
453
453
|
widget: T;
|
|
454
454
|
}>;
|
|
455
455
|
|
|
456
|
+
export type PerseusWidgetMap = {
|
|
457
|
+
[key: string]: PerseusWidget;
|
|
458
|
+
};
|
|
459
|
+
|
|
456
460
|
export type FilterCriterion =
|
|
457
461
|
| string
|
|
458
462
|
| ((
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {clockwise, reverseVector} from "./geometry";
|
|
2
|
+
|
|
3
|
+
describe("reverseVector", () => {
|
|
4
|
+
it("flips the sign of zero", () => {
|
|
5
|
+
// This is a characterization test for legacy behavior. It might not
|
|
6
|
+
// actually be desired behavior.
|
|
7
|
+
expect(reverseVector([0, 0])).toEqual([-0, -0]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("flips the sign of each component", () => {
|
|
11
|
+
expect(reverseVector([1, -5])).toEqual([-1, 5]);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("clockwise", () => {
|
|
16
|
+
it("returns false given no points", () => {
|
|
17
|
+
expect(clockwise([])).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns false given a single point at the origin", () => {
|
|
21
|
+
expect(clockwise([[0, 0]])).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns false given a single point not at the origin", () => {
|
|
25
|
+
expect(clockwise([[7, 7]])).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns false given two identical points", () => {
|
|
29
|
+
expect(
|
|
30
|
+
clockwise([
|
|
31
|
+
[7, 7],
|
|
32
|
+
[7, 7],
|
|
33
|
+
]),
|
|
34
|
+
).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns false given two different points", () => {
|
|
38
|
+
expect(
|
|
39
|
+
clockwise([
|
|
40
|
+
[8, 8],
|
|
41
|
+
[7, 7],
|
|
42
|
+
]),
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns true given a clockwise triangle in the first quadrant", () => {
|
|
47
|
+
expect(
|
|
48
|
+
clockwise([
|
|
49
|
+
[0, 1],
|
|
50
|
+
[1, 0],
|
|
51
|
+
[0, 0],
|
|
52
|
+
]),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns false given a counter-clockwise triangle in the first quadrant", () => {
|
|
57
|
+
expect(
|
|
58
|
+
clockwise([
|
|
59
|
+
[1, 0],
|
|
60
|
+
[0, 1],
|
|
61
|
+
[0, 0],
|
|
62
|
+
]),
|
|
63
|
+
).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns true given a clockwise triangle in the second quadrant", () => {
|
|
67
|
+
expect(
|
|
68
|
+
clockwise([
|
|
69
|
+
[0, 1],
|
|
70
|
+
[0, 0],
|
|
71
|
+
[-1, 0],
|
|
72
|
+
]),
|
|
73
|
+
).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|