@khanacademy/perseus 19.0.0 → 19.1.1
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 +37 -0
- package/dist/es/index.css +209 -104
- package/dist/es/index.js +806 -877
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +209 -104
- package/dist/index.d.ts +3 -2
- package/dist/index.js +849 -920
- 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/renderer.d.ts +2 -4
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +4 -0
- 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 +6 -0
- package/dist/widgets/label-image.d.ts +2 -0
- package/package.json +3 -4
- package/src/__testdata__/renderer.testdata.ts +152 -0
- package/src/__tests__/renderer.test.tsx +17 -0
- package/src/components/math-input.tsx +4 -6
- package/src/index.ts +8 -3
- package/src/mixins/changeable.ts +1 -1
- package/src/renderer.tsx +14 -8
- package/src/types.ts +9 -0
- 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/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/expression.tsx +1 -23
- package/src/widgets/label-image.tsx +0 -1
- package/tsconfig-build.tsbuildinfo +1 -1
package/src/util/interactive.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {Errors} from "../logging/log";
|
|
|
31
31
|
import {PerseusError} from "../perseus-error";
|
|
32
32
|
|
|
33
33
|
import KhanColors from "./colors";
|
|
34
|
+
import {clockwise, reverseVector} from "./geometry";
|
|
34
35
|
import GraphUtils, {polar} from "./graphie";
|
|
35
36
|
import KhanMath from "./math";
|
|
36
37
|
|
|
@@ -40,38 +41,6 @@ export type MouseHandler = (position: Coord) => void;
|
|
|
40
41
|
|
|
41
42
|
const {getCanUse3dTransform} = InteractiveUtil;
|
|
42
43
|
|
|
43
|
-
function sum(array: any) {
|
|
44
|
-
return _.reduce(
|
|
45
|
-
array,
|
|
46
|
-
function (memo, arg) {
|
|
47
|
-
return memo + arg;
|
|
48
|
-
},
|
|
49
|
-
0,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function clockwise(points: any) {
|
|
54
|
-
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
55
|
-
const areas = _.map(segments, function (segment) {
|
|
56
|
-
const p1 = segment[0];
|
|
57
|
-
const p2 = segment[1];
|
|
58
|
-
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
59
|
-
});
|
|
60
|
-
return sum(areas) > 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* vector-add multiple [x, y] coords/vectors */
|
|
64
|
-
function addPoints(...points) {
|
|
65
|
-
const zipped = _.zip(...points);
|
|
66
|
-
return _.map(zipped, sum);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function reverseVector(vector: Array<number>) {
|
|
70
|
-
return _.map(vector, function (coord) {
|
|
71
|
-
return coord * -1;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
44
|
function scaledDistanceFromAngle(angle: number) {
|
|
76
45
|
const a = 3.51470560176242 * 20;
|
|
77
46
|
const b = 0.5687298702748785 * 20;
|
|
@@ -79,14 +48,14 @@ function scaledDistanceFromAngle(angle: number) {
|
|
|
79
48
|
return (a - b) * Math.exp(c * angle) + b;
|
|
80
49
|
}
|
|
81
50
|
|
|
82
|
-
function scaledPolarRad(radius: number, radians: number) {
|
|
51
|
+
function scaledPolarRad(radius: number, radians: number): Coord {
|
|
83
52
|
return [
|
|
84
53
|
radius * Math.cos(radians),
|
|
85
54
|
radius * Math.sin(radians) * -1, // SVG flips y axis
|
|
86
55
|
];
|
|
87
56
|
}
|
|
88
57
|
|
|
89
|
-
function scaledPolarDeg(radius: number, degrees) {
|
|
58
|
+
function scaledPolarDeg(radius: number, degrees): Coord {
|
|
90
59
|
const radians = (degrees * Math.PI) / 180;
|
|
91
60
|
return scaledPolarRad(radius, radians);
|
|
92
61
|
}
|
|
@@ -245,7 +214,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
245
214
|
/**
|
|
246
215
|
* Get mouse coordinates in graph coordinates
|
|
247
216
|
*/
|
|
248
|
-
getMouseCoord: function (event) {
|
|
217
|
+
getMouseCoord: function (event): Coord {
|
|
249
218
|
return this.unscalePoint(this.getMousePx(event));
|
|
250
219
|
},
|
|
251
220
|
|
|
@@ -291,11 +260,14 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
291
260
|
const temp: Array<never> = [];
|
|
292
261
|
|
|
293
262
|
if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
|
|
294
|
-
const v1 =
|
|
295
|
-
|
|
263
|
+
const v1 = kvector.add(
|
|
264
|
+
sVertex,
|
|
265
|
+
scaledPolarDeg(sRadius, startAngle),
|
|
266
|
+
);
|
|
267
|
+
const v2 = kvector.add(sVertex, scaledPolarDeg(sRadius, endAngle));
|
|
296
268
|
|
|
297
269
|
sRadius *= Math.SQRT2;
|
|
298
|
-
const v3 =
|
|
270
|
+
const v3 = kvector.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
|
|
299
271
|
|
|
300
272
|
_.each([v1, v2], function (v) {
|
|
301
273
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
@@ -329,7 +301,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
329
301
|
}
|
|
330
302
|
|
|
331
303
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
332
|
-
const sPosition =
|
|
304
|
+
const sPosition = kvector.add(sVertex, sOffset);
|
|
333
305
|
const position = graphie.unscalePoint(sPosition);
|
|
334
306
|
|
|
335
307
|
// Reuse label if possible
|
|
@@ -395,8 +367,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
395
367
|
);
|
|
396
368
|
|
|
397
369
|
const sPath = [
|
|
398
|
-
|
|
399
|
-
|
|
370
|
+
kvector.add(sMidpoint, sOffsetVector, sHeightVector),
|
|
371
|
+
kvector.add(
|
|
400
372
|
sMidpoint,
|
|
401
373
|
sOffsetVector,
|
|
402
374
|
reverseVector(sHeightVector),
|
|
@@ -436,7 +408,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
436
408
|
sOffsetVector = reverseVector(sOffsetVector);
|
|
437
409
|
}
|
|
438
410
|
|
|
439
|
-
const sEnd =
|
|
411
|
+
const sEnd = kvector.add(sMidpoint, sOffsetVector);
|
|
440
412
|
|
|
441
413
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
442
414
|
temp.push(graphie.scaledPath([sStart, sEnd], style));
|
|
@@ -457,7 +429,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
457
429
|
|
|
458
430
|
const sOffset = 20;
|
|
459
431
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
460
|
-
const sPosition =
|
|
432
|
+
const sPosition = kvector.add(sMidpoint, sOffsetVector);
|
|
461
433
|
const position = graphie.unscalePoint(sPosition);
|
|
462
434
|
|
|
463
435
|
// Reuse label if possible
|
|
@@ -522,7 +494,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
522
494
|
|
|
523
495
|
const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
|
|
524
496
|
const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
|
|
525
|
-
const sPosition =
|
|
497
|
+
const sPosition = kvector.add(sVertex, sOffsetVector);
|
|
526
498
|
const position = graphie.unscalePoint(sPosition);
|
|
527
499
|
|
|
528
500
|
// Reuse label if possible
|
|
@@ -3225,7 +3197,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
3225
3197
|
return new Ruler(this, options || {});
|
|
3226
3198
|
},
|
|
3227
3199
|
|
|
3228
|
-
addPoints:
|
|
3200
|
+
addPoints: kvector.add,
|
|
3229
3201
|
});
|
|
3230
3202
|
|
|
3231
3203
|
function Protractor(graph: any, center: any) {
|
|
@@ -3679,7 +3651,7 @@ function Ruler(graphie: any, options: any) {
|
|
|
3679
3651
|
});
|
|
3680
3652
|
|
|
3681
3653
|
const mouseTarget = graphie.mouselayer.path(
|
|
3682
|
-
|
|
3654
|
+
graphie.svgPath([
|
|
3683
3655
|
leftBottom,
|
|
3684
3656
|
[left, top],
|
|
3685
3657
|
rightTop,
|
|
@@ -3970,16 +3942,16 @@ _.extend(MovableAngle.prototype, {
|
|
|
3970
3942
|
const points = this.points;
|
|
3971
3943
|
|
|
3972
3944
|
// Drag the vertex to move the entire angle
|
|
3973
|
-
points[1].onMove = function (x, y:
|
|
3945
|
+
points[1].onMove = function (x: number, y: number) {
|
|
3974
3946
|
const oldVertex = points[1].coord;
|
|
3975
|
-
const newVertex = [x, y];
|
|
3976
|
-
const delta =
|
|
3947
|
+
const newVertex: Coord = [x, y];
|
|
3948
|
+
const delta = kvector.add(newVertex, reverseVector(oldVertex));
|
|
3977
3949
|
|
|
3978
3950
|
let valid = true;
|
|
3979
3951
|
const newPoints: Record<string, any> = {};
|
|
3980
3952
|
_.each([0, 2], function (i) {
|
|
3981
3953
|
const oldPoint = points[i].coord;
|
|
3982
|
-
let newPoint =
|
|
3954
|
+
let newPoint = kvector.add(oldPoint, delta);
|
|
3983
3955
|
|
|
3984
3956
|
let angle = GraphUtils.findAngle(newVertex, newPoint);
|
|
3985
3957
|
angle *= Math.PI / 180;
|
|
@@ -4024,7 +3996,7 @@ _.extend(MovableAngle.prototype, {
|
|
|
4024
3996
|
Math.round((angle - snapOffset) / snap) * snap +
|
|
4025
3997
|
snapOffset;
|
|
4026
3998
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
4027
|
-
return
|
|
3999
|
+
return kvector.add(vertex, polar(distance, angle));
|
|
4028
4000
|
}
|
|
4029
4001
|
return true;
|
|
4030
4002
|
};
|
package/src/util/math.ts
CHANGED
|
@@ -283,4 +283,12 @@ const KhanMath = {
|
|
|
283
283
|
},
|
|
284
284
|
} as const;
|
|
285
285
|
|
|
286
|
+
export function sum(array: number[]): number {
|
|
287
|
+
return array.reduce(add, 0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function add(a: number, b: number): number {
|
|
291
|
+
return a + b;
|
|
292
|
+
}
|
|
293
|
+
|
|
286
294
|
export default KhanMath;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {parsePerseusItem} from "./parse-perseus-json";
|
|
2
2
|
|
|
3
|
-
describe("
|
|
3
|
+
describe("parsePerseusItem", () => {
|
|
4
4
|
it("should parse JSON", () => {
|
|
5
|
-
const result =
|
|
5
|
+
const result = parsePerseusItem(
|
|
6
6
|
`{ "question": { "content": "idk why I'm testing this" }}`,
|
|
7
7
|
);
|
|
8
8
|
expect(result.question.content).toBe("idk why I'm testing this");
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type {PerseusItem} from "../perseus-types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Helper to parse
|
|
4
|
+
* Helper to parse PerseusItem JSON
|
|
5
5
|
* Why not just use JSON.parse? We want:
|
|
6
6
|
* - To make sure types are correct
|
|
7
7
|
* - To give us a central place to validate/transform output if needed
|
|
8
|
-
* @param {string}
|
|
8
|
+
* @param {string} json - the stringified PerseusItem JSON
|
|
9
9
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
10
10
|
*/
|
|
11
|
-
export
|
|
12
|
-
return JSON.parse(
|
|
11
|
+
export function parsePerseusItem(json: string): PerseusItem {
|
|
12
|
+
return JSON.parse(json);
|
|
13
13
|
}
|
|
@@ -181,11 +181,13 @@ exports[`rendering supports mobile rendering: mobile render 1`] = `
|
|
|
181
181
|
<span
|
|
182
182
|
class="mq-textarea"
|
|
183
183
|
>
|
|
184
|
-
<span
|
|
184
|
+
<span
|
|
185
|
+
aria-label="Math Input:"
|
|
186
|
+
/>
|
|
185
187
|
</span>
|
|
186
188
|
<span
|
|
189
|
+
aria-hidden="true"
|
|
187
190
|
class="mq-root-block mq-empty"
|
|
188
|
-
mathquill-block-id="1"
|
|
189
191
|
style="padding: 10px 11px 8px 11px; font-size: 18pt;"
|
|
190
192
|
/>
|
|
191
193
|
</div>
|
|
@@ -43,11 +43,13 @@ exports[`Numeric input widget styles differently on mobile: mobile render 1`] =
|
|
|
43
43
|
<span
|
|
44
44
|
class="mq-textarea"
|
|
45
45
|
>
|
|
46
|
-
<span
|
|
46
|
+
<span
|
|
47
|
+
aria-label="Math Input:"
|
|
48
|
+
/>
|
|
47
49
|
</span>
|
|
48
50
|
<span
|
|
51
|
+
aria-hidden="true"
|
|
49
52
|
class="mq-root-block mq-empty"
|
|
50
|
-
mathquill-block-id="1"
|
|
51
53
|
style="padding: 10px 11px 8px 11px; font-size: 18pt;"
|
|
52
54
|
/>
|
|
53
55
|
</div>
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
KeypadContext,
|
|
4
4
|
StatefulKeypadContextProvider,
|
|
5
5
|
MobileKeypad,
|
|
6
|
+
mathQuillInstance,
|
|
6
7
|
} from "@khanacademy/math-input";
|
|
7
8
|
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
|
|
8
9
|
import {
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
within,
|
|
14
15
|
} from "@testing-library/react";
|
|
15
16
|
import userEvent from "@testing-library/user-event";
|
|
16
|
-
import MathQuill from "mathquill";
|
|
17
17
|
import * as React from "react";
|
|
18
18
|
|
|
19
19
|
import {
|
|
@@ -26,7 +26,7 @@ import {registerWidget} from "../../widgets";
|
|
|
26
26
|
import {expressionItem2} from "../__testdata__/expression.testdata";
|
|
27
27
|
import ExpressionExport from "../expression";
|
|
28
28
|
|
|
29
|
-
const MQ =
|
|
29
|
+
const MQ = mathQuillInstance;
|
|
30
30
|
|
|
31
31
|
function RendererWithContext({item}) {
|
|
32
32
|
return (
|
|
@@ -351,10 +351,13 @@ describe("expression mobile", () => {
|
|
|
351
351
|
|
|
352
352
|
// MathQuill is problematic,
|
|
353
353
|
// this is how to get the value of the input directly from MQ
|
|
354
|
-
const mathquillInstance =
|
|
354
|
+
const mathquillInstance = MQ(
|
|
355
355
|
// eslint-disable-next-line testing-library/no-node-access
|
|
356
|
-
|
|
356
|
+
document.getElementsByClassName(
|
|
357
|
+
"mq-editable-field",
|
|
358
|
+
)[0] as HTMLElement,
|
|
359
|
+
);
|
|
357
360
|
|
|
358
|
-
expect(mathquillInstance
|
|
361
|
+
expect(mathquillInstance?.latex()).toBe("x=\\sin\\left(9\\right)");
|
|
359
362
|
});
|
|
360
363
|
});
|
|
@@ -35,28 +35,6 @@ type InputPath = ReadonlyArray<string>;
|
|
|
35
35
|
const ERROR_TITLE = i18n._("Oops!");
|
|
36
36
|
const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
|
|
37
37
|
|
|
38
|
-
const insertBraces = (value) => {
|
|
39
|
-
// HACK(alex): Make sure that all LaTeX super/subscripts are wrapped
|
|
40
|
-
// in curly braces to avoid the mismatch between KAS and LaTeX sup/sub
|
|
41
|
-
// parsing.
|
|
42
|
-
//
|
|
43
|
-
// What exactly is this mismatch? Due to its heritage of parsing plain
|
|
44
|
-
// text math from <OldExpression />, KAS parses "x^12" as x^(12).
|
|
45
|
-
// This is both generally what the user expects to happen, and is
|
|
46
|
-
// consistent with other computer algebra systems. It is NOT
|
|
47
|
-
// consistent with LaTeX however, where x^12 is equivalent to x^{1}2.
|
|
48
|
-
//
|
|
49
|
-
// Since the only LaTeX we parse comes from MathQuill, this wouldn't
|
|
50
|
-
// be a problem if MathQuill just always gave us the latter version
|
|
51
|
-
// (with explicit braces). However, instead it always gives the former.
|
|
52
|
-
// This behavior is baked in pretty deep; my naive attempts at changing
|
|
53
|
-
// it triggered all sorts of confusing errors. So instead we just make
|
|
54
|
-
// sure to add in any missing braces before grading MathQuill input.
|
|
55
|
-
//
|
|
56
|
-
// TODO(alex): Properly hack MathQuill to always use explicit braces.
|
|
57
|
-
return value.replace(/([_^])([^{])/g, "$1{$2}");
|
|
58
|
-
};
|
|
59
|
-
|
|
60
38
|
const anglicizeOperators = (tex: string): string => {
|
|
61
39
|
// sen is used instead of sin in some languages, e.g. Portuguese.
|
|
62
40
|
// To ensure that answers in various languages are graded correctly, we
|
|
@@ -65,7 +43,7 @@ const anglicizeOperators = (tex: string): string => {
|
|
|
65
43
|
};
|
|
66
44
|
|
|
67
45
|
const normalizeTex = (tex: string): string => {
|
|
68
|
-
return anglicizeOperators(
|
|
46
|
+
return anglicizeOperators(tex);
|
|
69
47
|
};
|
|
70
48
|
|
|
71
49
|
const deriveKeypadVersion = (apiOptions: APIOptions) => {
|
|
@@ -401,7 +401,6 @@ export class LabelImage extends React.Component<
|
|
|
401
401
|
|
|
402
402
|
// Update Perseus widget state with user selected answers without
|
|
403
403
|
// triggering interaction events for listeners.
|
|
404
|
-
// @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '(() => unknown) | undefined'.
|
|
405
404
|
onChange({markers: updatedMarkers}, null, true);
|
|
406
405
|
}
|
|
407
406
|
|