@khanacademy/perseus 7.1.1 → 7.2.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/es/index.css +61 -61
  3. package/dist/es/index.js +100 -168
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +61 -61
  6. package/dist/index.js +89 -126
  7. package/dist/index.js.map +1 -1
  8. package/dist/util.d.ts +7 -12
  9. package/dist/util.graphie.d.ts +6 -0
  10. package/dist/util.graphie.js.flow +14 -0
  11. package/dist/util.js.flow +7 -12
  12. package/dist/widgets/plotter.d.ts +47 -13
  13. package/dist/widgets/plotter.js.flow +47 -13
  14. package/package.json +2 -2
  15. package/src/__tests__/item-renderer.test.tsx +1 -1
  16. package/src/components/__stories__/image-loader.stories.tsx +1 -0
  17. package/src/components/graph.tsx +1 -0
  18. package/src/components/icon.tsx +1 -0
  19. package/src/components/image-loader.tsx +1 -0
  20. package/src/components/number-input.tsx +1 -0
  21. package/src/components/sortable.tsx +1 -0
  22. package/src/components/svg-image.tsx +1 -0
  23. package/src/components/tex-buttons.tsx +1 -0
  24. package/src/components/text-input.tsx +1 -0
  25. package/src/components/text-list-editor.tsx +1 -0
  26. package/src/components/zoomable.tsx +1 -0
  27. package/src/hints-renderer.tsx +1 -0
  28. package/src/index.ts +1 -0
  29. package/src/item-renderer.tsx +1 -0
  30. package/src/mixins/provide-keypad.tsx +1 -0
  31. package/src/multi-items/multi-renderer.tsx +1 -0
  32. package/src/renderer.tsx +1 -0
  33. package/src/server-item-renderer.tsx +1 -0
  34. package/src/styles/articles.less +8 -4
  35. package/src/styles/perseus-renderer.less +47 -38
  36. package/src/styles/util.less +1 -1
  37. package/src/styles/variables.boostrap.less +158 -182
  38. package/src/styles/variables.less +20 -20
  39. package/src/styles/variables.shared.less +162 -168
  40. package/src/styles/widgets/categorizer.less +1 -2
  41. package/src/styles/widgets/expression.less +7 -4
  42. package/src/styles/widgets/grapher.less +2 -1
  43. package/src/styles/widgets/image.less +3 -2
  44. package/src/styles/widgets/interactive-graph.less +4 -2
  45. package/src/styles/widgets/label-image.less +2 -1
  46. package/src/styles/widgets/matrix.less +11 -7
  47. package/src/styles/widgets/orderer.less +2 -1
  48. package/src/styles/widgets/passage.less +2 -1
  49. package/src/styles/widgets/plotter.less +4 -3
  50. package/src/styles/widgets/radio.less +4 -2
  51. package/src/styles/widgets/sortable.less +1 -1
  52. package/src/styles/widgets/table.less +2 -1
  53. package/src/styles/widgets/transformer.less +2 -1
  54. package/src/styles/widgets/unit.less +2 -1
  55. package/src/util.graphie.test.ts +45 -0
  56. package/src/util.graphie.ts +59 -0
  57. package/src/util.ts +8 -63
  58. package/src/widgets/__stories__/plotter.stories.tsx +27 -0
  59. package/src/widgets/__testdata__/plotter.testdata.ts +36 -0
  60. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +863 -0
  61. package/src/widgets/__tests__/plotter.test.tsx +65 -0
  62. package/src/widgets/categorizer.tsx +1 -0
  63. package/src/widgets/graded-group-set.tsx +1 -0
  64. package/src/widgets/graded-group.tsx +1 -0
  65. package/src/widgets/interaction.tsx +1 -0
  66. package/src/widgets/label-image/marker.tsx +1 -0
  67. package/src/widgets/matcher.tsx +1 -0
  68. package/src/widgets/orderer.tsx +1 -0
  69. package/src/widgets/passage-ref-target.tsx +1 -0
  70. package/src/widgets/plotter.tsx +108 -98
  71. package/src/widgets/radio/base-radio.tsx +1 -0
  72. package/src/widgets/radio/choice-icon/choice-icon.tsx +1 -0
  73. package/src/widgets/sorter.tsx +1 -0
  74. package/src/widgets/transformer.tsx +1 -0
  75. package/tsconfig-build.tsbuildinfo +1 -1
@@ -32,7 +32,7 @@
32
32
  touch-action: none;
33
33
 
34
34
  &.placeholder {
35
- background:#ddd;
35
+ background: #ddd;
36
36
  border: 1px solid #ccc;
37
37
  }
38
38
 
@@ -8,7 +8,8 @@
8
8
  height: 23px;
9
9
  }
10
10
 
11
- th, td {
11
+ th,
12
+ td {
12
13
  border: 2px solid black;
13
14
  border-width: 0 2px;
14
15
 
@@ -7,7 +7,8 @@
7
7
  position: relative;
8
8
  overflow: hidden;
9
9
 
10
- > img, > .unresponsive-svg-image {
10
+ > img,
11
+ > .unresponsive-svg-image {
11
12
  position: absolute;
12
13
  // Preserves background image alignment (if image is a graph,
13
14
  // it can extend up and to the right to allow for axis labels)
@@ -1,4 +1,5 @@
1
- .old-unit-input input, .unit-editor-canonical {
1
+ .old-unit-input input,
2
+ .unit-editor-canonical {
2
3
  border: 1px solid #a4a4a4;
3
4
  border-radius: 5px;
4
5
  // box-sizing: border-box;
@@ -0,0 +1,45 @@
1
+ import {describe, it} from "@jest/globals";
2
+
3
+ import {getRealImageUrl, isLabeledSVG} from "./util.graphie";
4
+
5
+ describe(".getRealImageUrl", () => {
6
+ it("when passed a graphie URL, it transforms the URL to an http url", () => {
7
+ const input =
8
+ "web+graphie://ka-perseus-graphie.s3.amazonaws.com/b4a76d480e49f89fddb973c88c53fc301ba0d927";
9
+ const result =
10
+ "https://ka-perseus-graphie.s3.amazonaws.com/b4a76d480e49f89fddb973c88c53fc301ba0d927.svg";
11
+ expect(getRealImageUrl(input)).toBe(result);
12
+ });
13
+
14
+ it("when it's passed an HTTP URL, it returns the URL", () => {
15
+ const input =
16
+ "https://cdn.kastatic.org/ka-perseus-images/c633a1d04970211586b413985a7959876ca6c909.png";
17
+ expect(getRealImageUrl(input)).toBe(input);
18
+ });
19
+ });
20
+
21
+ describe(".isLabeledSVG", () => {
22
+ it("returns true for web+graphie", () => {
23
+ expect(
24
+ isLabeledSVG(
25
+ "web+graphie://ka-perseus-graphie.s3.amazonaws.com/b4a76d480e49f89fddb973c88c53fc301ba0d927",
26
+ ),
27
+ ).toBeTruthy();
28
+ });
29
+
30
+ it("returns true for file+graphie", () => {
31
+ expect(
32
+ isLabeledSVG(
33
+ "file+graphie://b4a76d480e49f89fddb973c88c53fc301ba0d927",
34
+ ),
35
+ ).toBeTruthy();
36
+ });
37
+
38
+ it("returns false for all other protocols", () => {
39
+ expect(
40
+ isLabeledSVG(
41
+ "https://cdn.kastatic.org/ka-perseus-images/c633a1d04970211586b413985a7959876ca6c909.png",
42
+ ),
43
+ ).toBeFalsy();
44
+ });
45
+ });
@@ -0,0 +1,59 @@
1
+ const svgLabelsRegex = /^web\+graphie:/;
2
+ // For offline exercises in the mobile app, we download the graphie data
3
+ // (svgs and localized data files) and serve them from the local file
4
+ // system (with file://). We replace urls that start with `web+graphie`
5
+ // in the perseus json with this `file+graphie` prefix to indicate that
6
+ // they should have the `file://` protocol instead of `https://`.
7
+ const svgLocalLabelsRegex = /^file\+graphie:/;
8
+
9
+ // Sometimes other components want to download the actual image e.g. to
10
+ // determine its size. Here, we transform an .svg-labels url into the
11
+ // correct image url, and leave normal image urls alone
12
+ export function getRealImageUrl(graphieUrl: string): string {
13
+ if (isLabeledSVG(graphieUrl)) {
14
+ return getSvgUrl(graphieUrl);
15
+ }
16
+ return graphieUrl;
17
+ }
18
+
19
+ export function isLabeledSVG(graphieUrl: string): boolean {
20
+ return (
21
+ svgLabelsRegex.test(graphieUrl) || svgLocalLabelsRegex.test(graphieUrl)
22
+ );
23
+ }
24
+
25
+ // For each graphie, there are two urls we need to download from. This gets
26
+ // the base url without the suffix, and `getSvgUrl` and `getDataUrl` apply
27
+ // appropriate suffixes to get the image and other data
28
+ export function getBaseUrl(graphieUrl: string): string {
29
+ return graphieUrl
30
+ .replace(svgLabelsRegex, "https:")
31
+ .replace(svgLocalLabelsRegex, "file:");
32
+ }
33
+
34
+ export function getSvgUrl(graphieUrl: string): string {
35
+ return getBaseUrl(graphieUrl) + ".svg";
36
+ }
37
+
38
+ export function getDataUrl(graphieUrl: string): string {
39
+ return getBaseUrl(graphieUrl) + "-data.json";
40
+ }
41
+
42
+ export async function getImageSizeModern(
43
+ url: string,
44
+ ): Promise<[number, number]> {
45
+ const image = new Image();
46
+
47
+ return new Promise((resolve, reject) => {
48
+ // Handle the success case
49
+ image.onload = () => {
50
+ resolve([image.naturalWidth, image.naturalHeight]);
51
+ };
52
+
53
+ // Handle the error case
54
+ image.onerror = reject;
55
+
56
+ // Kick off the loading
57
+ image.src = getRealImageUrl(url);
58
+ });
59
+ }
package/src/util.ts CHANGED
@@ -3,6 +3,7 @@ import _ from "underscore";
3
3
 
4
4
  import {Errors} from "./logging/log";
5
5
  import {PerseusError} from "./perseus-error";
6
+ import * as GraphieUtil from "./util.graphie";
6
7
  import KhanAnswerTypes from "./util/answer-types";
7
8
 
8
9
  import type {Range} from "./perseus-types";
@@ -51,14 +52,6 @@ type TouchHandlers = {
51
52
 
52
53
  let supportsPassive = false;
53
54
 
54
- const svgLabelsRegex = /^web\+graphie:/;
55
- // For offline exercises in the mobile app, we download the graphie data
56
- // (svgs and localized data files) and serve them from the local file
57
- // system (with file://). We replace urls that start with `web+graphie`
58
- // in the perseus json with this `file+graphie` prefix to indicate that
59
- // they should have the `file://` protocol instead of `https://`.
60
- const svgLocalLabelsRegex = /^file\+graphie:/;
61
-
62
55
  const nestedMap = function <T, M>(
63
56
  children: T | ReadonlyArray<T>,
64
57
  func: (arg1: T) => M,
@@ -839,23 +832,6 @@ function captureScratchpadTouchStart(e: TouchEvent) {
839
832
  e.stopPropagation();
840
833
  }
841
834
 
842
- async function getImageSizeModern(url: string): Promise<[number, number]> {
843
- const image = new Image();
844
-
845
- return new Promise((resolve, reject) => {
846
- // Handle the success case
847
- image.onload = () => {
848
- resolve([image.naturalWidth, image.naturalHeight]);
849
- };
850
-
851
- // Handle the error case
852
- image.onerror = reject;
853
-
854
- // Kick off the loading
855
- image.src = url;
856
- });
857
- }
858
-
859
835
  function getImageSize(
860
836
  url: string,
861
837
  callback: (width: number, height: number) => void,
@@ -884,38 +860,7 @@ function getImageSize(
884
860
  }
885
861
  };
886
862
 
887
- img.src = getRealImageUrl(url);
888
- }
889
-
890
- // Sometimes other components want to download the actual image e.g. to
891
- // determine its size. Here, we transform an .svg-labels url into the
892
- // correct image url, and leave normal image urls alone
893
- function getRealImageUrl(url: string): string {
894
- if (isLabeledSVG(url)) {
895
- return getSvgUrl(url);
896
- }
897
- return url;
898
- }
899
-
900
- function isLabeledSVG(url: string): boolean {
901
- return svgLabelsRegex.test(url) || svgLocalLabelsRegex.test(url);
902
- }
903
-
904
- // For each svg+labels, there are two urls we need to download from. This gets
905
- // the base url without the suffix, and `getSvgUrl` and `getDataUrl` apply
906
- // appropriate suffixes to get the image and other data
907
- function getBaseUrl(url: string): string {
908
- return url
909
- .replace(svgLabelsRegex, "https:")
910
- .replace(svgLocalLabelsRegex, "file:");
911
- }
912
-
913
- function getSvgUrl(url: string): string {
914
- return getBaseUrl(url) + ".svg";
915
- }
916
-
917
- function getDataUrl(url: string): string {
918
- return getBaseUrl(url) + "-data.json";
863
+ img.src = GraphieUtil.getRealImageUrl(url);
919
864
  }
920
865
 
921
866
  /**
@@ -1007,12 +952,12 @@ const Util = {
1007
952
  supportsPassiveEvents,
1008
953
  captureScratchpadTouchStart,
1009
954
  getImageSize,
1010
- getImageSizeModern,
1011
- getRealImageUrl,
1012
- isLabeledSVG,
1013
- getBaseUrl,
1014
- getSvgUrl,
1015
- getDataUrl,
955
+ getImageSizeModern: GraphieUtil.getImageSizeModern,
956
+ getRealImageUrl: GraphieUtil.getRealImageUrl,
957
+ isLabeledSVG: GraphieUtil.isLabeledSVG,
958
+ getBaseUrl: GraphieUtil.getBaseUrl,
959
+ getSvgUrl: GraphieUtil.getSvgUrl,
960
+ getDataUrl: GraphieUtil.getDataUrl,
1016
961
  textarea,
1017
962
  unescapeMathMode,
1018
963
  random,
@@ -0,0 +1,27 @@
1
+ import {View} from "@khanacademy/wonder-blocks-core";
2
+ import * as React from "react";
3
+
4
+ import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui";
5
+ import {question1} from "../__testdata__/plotter.testdata";
6
+
7
+ import type {PerseusRenderer} from "../../perseus-types";
8
+
9
+ export default {
10
+ title: "Perseus/Widgets/Plotter",
11
+ };
12
+
13
+ type StoryArgs = Record<any, any>;
14
+
15
+ const Template = (props: {question: PerseusRenderer}): React.ReactElement => (
16
+ <View
17
+ style={{
18
+ paddingLeft: 20,
19
+ }}
20
+ >
21
+ <RendererWithDebugUI question={props.question} />
22
+ </View>
23
+ );
24
+
25
+ export const Basic = (args: StoryArgs): React.ReactElement => {
26
+ return <Template question={question1} />;
27
+ };
@@ -0,0 +1,36 @@
1
+ import type {PerseusRenderer} from "../../perseus-types";
2
+
3
+ export const question1: PerseusRenderer = {
4
+ content:
5
+ "The elementary school principal asked teachers to report the number of students absent in each grade during the past week.\n\n**Create a bar graph to show how many students were absent in each grade.**\n\nSchool grade | Number of absent students \n:- | :-: \n$1^{\\text{st}} \\text{ grade}$ | $15$ \n$2^{\\text{nd}} \\text{ grade}$ |$25$ \n$3^{\\text{rd}} \\text{ grade}$ | $5$ \n$4^{\\text{th}} \\text{ grade}$ | $10$ \n$5^{\\text{th}} \\text{ grade}$ | $10$ \n\n[[☃ plotter 1]]\n\n",
6
+ images: {},
7
+ widgets: {
8
+ // This is the actual problem from the server
9
+ // I don't know why the type is incorrect
10
+ // @ts-expect-error - TS2322
11
+ "plotter 1": {
12
+ alignment: "default",
13
+ graded: true,
14
+ options: {
15
+ categories: [
16
+ "$1^{\\text{st}} \\text{}$",
17
+ "$2^{\\text{nd}} \\text{}$",
18
+ "$3^{\\text{rd}} \\text{}$",
19
+ "$4^{\\text{th}} \\text{}$",
20
+ "$5^{\\text{th}} \\text{}$",
21
+ ],
22
+ correct: [15, 25, 5, 10, 10],
23
+ labelInterval: 1,
24
+ labels: ["School grade", "Number of absent students"],
25
+ maxY: 30,
26
+ scaleY: 5,
27
+ snapsPerLine: 1,
28
+ starting: [0, 0, 0, 0, 0],
29
+ type: "bar",
30
+ },
31
+ static: false,
32
+ type: "plotter",
33
+ version: {major: 0, minor: 0},
34
+ },
35
+ },
36
+ };