@khanacademy/perseus-editor 2.2.2 → 2.3.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 (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/all-widgets.d.ts +2 -2
  3. package/dist/all-widgets.js.flow +2 -2
  4. package/dist/components/graph-points-count-selector.d.ts +7 -0
  5. package/dist/components/graph-points-count-selector.js.flow +12 -0
  6. package/dist/components/graph-type-selector.d.ts +7 -0
  7. package/dist/components/graph-type-selector.js.flow +12 -0
  8. package/dist/components/segment-count-selector.d.ts +6 -0
  9. package/dist/components/segment-count-selector.js.flow +11 -0
  10. package/dist/components/widget-editor.d.ts +31 -0
  11. package/dist/components/widget-editor.js.flow +91 -0
  12. package/dist/components/widget-select.d.ts +10 -0
  13. package/dist/components/widget-select.js.flow +16 -0
  14. package/dist/es/index.js +423 -229
  15. package/dist/es/index.js.map +1 -1
  16. package/dist/index.js +431 -225
  17. package/dist/index.js.map +1 -1
  18. package/dist/testing-widgets.d.ts +2 -2
  19. package/dist/testing-widgets.js.flow +2 -2
  20. package/dist/util/points.d.ts +3 -0
  21. package/dist/util/points.js.flow +9 -0
  22. package/dist/widgets/example-graphie-widget.d.ts +2 -2
  23. package/dist/widgets/example-graphie-widget.js.flow +2 -2
  24. package/dist/widgets/example-widget.d.ts +2 -2
  25. package/dist/widgets/example-widget.js.flow +2 -2
  26. package/dist/widgets/interactive-graph-editor.d.ts +3182 -1
  27. package/dist/widgets/interactive-graph-editor.js.flow +5290 -1
  28. package/package.json +9 -3
  29. package/src/__stories__/editor.stories.tsx +105 -4
  30. package/src/article-editor.tsx +1 -1
  31. package/src/{__stories__ → components/__stories__}/section-control-button.stories.tsx +1 -1
  32. package/src/components/graph-points-count-selector.tsx +29 -0
  33. package/src/components/graph-type-selector.tsx +31 -0
  34. package/src/components/json-editor.tsx +0 -1
  35. package/src/components/segment-count-selector.tsx +27 -0
  36. package/src/components/widget-editor.tsx +232 -0
  37. package/src/components/widget-select.tsx +48 -0
  38. package/src/editor.tsx +4 -270
  39. package/src/util/points.test.ts +24 -0
  40. package/src/util/points.ts +13 -0
  41. package/src/widgets/interactive-graph-editor.tsx +294 -48
  42. package/tsconfig-build.json +0 -1
  43. package/tsconfig-build.tsbuildinfo +1 -1
  44. package/dist/diffs/__stories__/perseus-diff-wrapper.d.ts +0 -6
  45. package/dist/diffs/__stories__/perseus-diff-wrapper.js.flow +0 -12
  46. /package/dist/{section-control-button.d.ts → components/section-control-button.d.ts} +0 -0
  47. /package/dist/{section-control-button.js.flow → components/section-control-button.js.flow} +0 -0
  48. /package/src/{section-control-button.tsx → components/section-control-button.tsx} +0 -0
@@ -1,4 +1,3 @@
1
- /* eslint-disable react/prop-types */
2
1
  /* eslint-disable react/no-unsafe */
3
2
  /* eslint-disable react/sort-comp */
4
3
  import {
@@ -7,12 +6,21 @@ import {
7
6
  InteractiveGraphWidget,
8
7
  SizingUtils,
9
8
  Util,
9
+ PerseusImageBackground,
10
+ PerseusInteractiveGraphWidgetOptions,
11
+ APIOptionsWithDefaults,
10
12
  } from "@khanacademy/perseus";
11
- import createReactClass from "create-react-class";
13
+ import {StyleType, View} from "@khanacademy/wonder-blocks-core";
14
+ import Spacing from "@khanacademy/wonder-blocks-spacing";
15
+ import * as Typography from "@khanacademy/wonder-blocks-typography";
12
16
  import * as React from "react";
13
17
  import _ from "underscore";
14
18
 
19
+ import GraphPointsCountSelector from "../components/graph-points-count-selector";
15
20
  import GraphSettings from "../components/graph-settings";
21
+ import GraphTypeSelector from "../components/graph-type-selector";
22
+ import SegmentCountSelector from "../components/segment-count-selector";
23
+ import {parsePointCount} from "../util/points";
16
24
 
17
25
  const {InfoTip} = components;
18
26
  const {containerSizeClass, getInteractiveBoxFromSizeClass} = SizingUtils;
@@ -34,46 +42,98 @@ const deprecatedProps = {
34
42
  },
35
43
  } as const;
36
44
 
37
- const InteractiveGraphEditor: any = createReactClass({
38
- displayName: "InteractiveGraphEditor",
39
- className: "perseus-widget-interactive-graph",
45
+ type Range = [number, number]; // [min, max]
40
46
 
41
- statics: {
42
- widgetName: "interactive-graph",
43
- },
47
+ // Renders the given children in a View laid out horizontally.
48
+ const Row = (props: {style?: StyleType; children: React.ReactNode}) => {
49
+ const {children, style} = props;
44
50
 
45
- getDefaultProps: function () {
46
- return {
47
- labels: ["x", "y"],
48
- range: [
49
- [-10, 10],
50
- [-10, 10],
51
- ],
52
- step: [1, 1],
53
- valid: true,
54
- backgroundImage: defaultBackgroundImage,
55
- markings: "graph",
56
- showProtractor: false,
57
- showRuler: false,
58
- showTooltips: false,
59
- rulerLabel: "",
60
- rulerTicks: 10,
61
- correct: {
62
- type: "linear",
63
- coords: null,
64
- },
65
- };
66
- },
51
+ return (
52
+ <View
53
+ style={{
54
+ ...style,
55
+ flexDirection: "row",
56
+ marginTop: Spacing.xSmall_8,
57
+ }}
58
+ >
59
+ {children}
60
+ </View>
61
+ );
62
+ };
63
+
64
+ const FieldLabel = (props: {children: string}) => {
65
+ return (
66
+ <Typography.LabelSmall style={{marginRight: Spacing.xSmall_8}}>
67
+ {props.children}
68
+ </Typography.LabelSmall>
69
+ );
70
+ };
71
+
72
+ type Props = {
73
+ apiOptions: APIOptionsWithDefaults;
74
+
75
+ labels: ReadonlyArray<string>;
76
+ range: [Range, Range]; // x, y
77
+ step: [number, number]; // x, y
78
+ gridStep: [number, number]; // x, y
79
+ snapStep: [number, number]; // x, y
80
+ box: [number, number]; // x, y
81
+
82
+ valid: boolean;
83
+ backgroundImage: PerseusImageBackground;
84
+ markings: string; // STOPSHIP
85
+ showProtractor: boolean;
86
+ showRuler: boolean;
87
+ showTooltips: boolean;
88
+ rulerLabel: string;
89
+ rulerTicks: number;
90
+ correct: any; // STOPSHIP
91
+
92
+ graph: InteractiveGraphProps["graph"];
93
+ onChange: (props: Partial<Props>) => void;
94
+ };
95
+
96
+ type DefaultProps = {
97
+ labels: Props["labels"];
98
+ range: Props["range"];
99
+ step: Props["step"];
100
+ valid: Props["valid"];
101
+ backgroundImage: Props["backgroundImage"];
102
+ markings: Props["markings"];
103
+ showProtractor: Props["showProtractor"];
104
+ showRuler: Props["showRuler"];
105
+ showTooltips: Props["showTooltips"];
106
+ rulerLabel: Props["rulerLabel"];
107
+ rulerTicks: Props["rulerTicks"];
108
+ correct: Props["correct"];
109
+ };
110
+
111
+ class InteractiveGraphEditor extends React.Component<Props> {
112
+ displayName = "InteractiveGraphEditor";
113
+ className = "perseus-widget-interactive-graph";
114
+
115
+ static widgetName = "interactive-graph";
116
+
117
+ static defaultProps: DefaultProps = {
118
+ ...InteractiveGraph.defaultProps,
119
+ valid: true,
120
+ backgroundImage: defaultBackgroundImage,
121
+ showTooltips: false,
122
+ correct: {
123
+ type: InteractiveGraph.defaultProps.graph.type,
124
+ coords: null,
125
+ },
126
+ };
67
127
 
68
128
  // TODO(jack): Use versioning instead of DeprecationMixin
69
- deprecatedProps: deprecatedProps,
129
+ deprecatedProps = deprecatedProps;
70
130
 
71
131
  // TODO(jangmi, CP-3288): Remove usage of `UNSAFE_componentWillMount`
72
132
  UNSAFE_componentWillMount() {
73
133
  DeprecationMixin.UNSAFE_componentWillMount.call(this);
74
- },
134
+ }
75
135
 
76
- render: function () {
136
+ render() {
77
137
  let graph;
78
138
  let equationString;
79
139
 
@@ -141,19 +201,204 @@ const InteractiveGraphEditor: any = createReactClass({
141
201
  }
142
202
 
143
203
  return (
144
- <div className="perseus-widget-interactive-graph">
145
- <div>
146
- Correct answer{" "}
204
+ <View>
205
+ <Row>
206
+ <FieldLabel>Type of Graph:</FieldLabel>
207
+ <GraphTypeSelector
208
+ graphType={
209
+ this.props.graph?.type ??
210
+ InteractiveGraph.defaultProps.graph.type
211
+ }
212
+ onChange={(
213
+ type: Required<InteractiveGraphProps>["graph"]["type"],
214
+ ) => {
215
+ this.props.onChange({
216
+ graph: {type},
217
+ correct: {type},
218
+ });
219
+ }}
220
+ />
221
+ </Row>
222
+ {this.props.correct?.type === "point" && (
223
+ <Row>
224
+ <FieldLabel>Number of Points:</FieldLabel>
225
+ <GraphPointsCountSelector
226
+ numPoints={this.props.correct?.numPoints}
227
+ onChange={(points) => {
228
+ this.props.onChange({
229
+ correct: {
230
+ type: "point",
231
+ numPoints: points,
232
+ },
233
+ });
234
+ }}
235
+ />
236
+ </Row>
237
+ )}
238
+ {this.props.correct?.type === "polygon" && (
239
+ <>
240
+ <Row>
241
+ <FieldLabel>Number of sides:</FieldLabel>
242
+ <select
243
+ key="polygon-select"
244
+ value={this.props.correct?.numSides || 3}
245
+ onChange={(e) => {
246
+ const graph = {
247
+ ...this.props.correct,
248
+ numSides: parsePointCount(
249
+ e.target.value,
250
+ ),
251
+ coords: null,
252
+ // reset the snap for UNLIMITED, which
253
+ // only supports "grid"
254
+ // From: D6578
255
+ snapTo: "grid",
256
+ };
257
+
258
+ this.props.onChange({correct: graph});
259
+ }}
260
+ >
261
+ {_.map(_.range(3, 13), function (n) {
262
+ return (
263
+ <option key={n} value={n}>
264
+ {`${n} sides`}
265
+ </option>
266
+ );
267
+ })}
268
+ <option value={"unlimited"}>
269
+ unlimited sides
270
+ </option>
271
+ </select>
272
+ </Row>
273
+ <Row>
274
+ <FieldLabel>Snap to:</FieldLabel>
275
+ <select
276
+ key="polygon-snap"
277
+ value={this.props.correct?.snapTo}
278
+ onChange={(e) => {
279
+ const graph = {
280
+ ...this.props.correct,
281
+ snapTo: e.target.value,
282
+ coords: null,
283
+ };
284
+
285
+ this.props.onChange({correct: graph});
286
+ }}
287
+ >
288
+ <option value="grid">grid</option>
289
+ {this.props.correct?.numSides !==
290
+ "unlimited" && (
291
+ <React.Fragment>
292
+ <option value="angles">
293
+ interior angles
294
+ </option>
295
+ <option value="sides">
296
+ side measures
297
+ </option>
298
+ </React.Fragment>
299
+ )}
300
+ </select>
301
+ <InfoTip>
302
+ <p>
303
+ These options affect the movement of the
304
+ vertex points. The grid option will guide
305
+ the points to the nearest half step along
306
+ the grid.
307
+ </p>
308
+ <p>
309
+ The interior angle and side measure options
310
+ guide the points to the nearest whole angle
311
+ or side
312
+ </p>{" "}
313
+ measure respectively.
314
+ </InfoTip>
315
+ </Row>
316
+ <Row>
317
+ <Typography.LabelSmall tag="label">
318
+ Show angle measures:{" "}
319
+ <input
320
+ type="checkbox"
321
+ checked={this.props.correct?.showAngles}
322
+ onChange={() => {
323
+ this.props.onChange({
324
+ correct: {
325
+ ...this.props.correct,
326
+ showAngles:
327
+ !this.props.correct
328
+ .showAngles,
329
+ },
330
+ });
331
+ }}
332
+ />
333
+ </Typography.LabelSmall>
334
+ <InfoTip>
335
+ <p>Displays the interior angle measures.</p>
336
+ </InfoTip>
337
+ </Row>
338
+ <Row>
339
+ <Typography.LabelSmall tag="label">
340
+ Show side measures:{" "}
341
+ <input
342
+ type="checkbox"
343
+ checked={this.props.correct?.showSides}
344
+ onChange={() => {
345
+ this.props.onChange({
346
+ correct: {
347
+ ...this.props.correct,
348
+ showSides:
349
+ !this.props.correct
350
+ .showSides,
351
+ },
352
+ });
353
+ }}
354
+ />
355
+ </Typography.LabelSmall>
356
+ <InfoTip>
357
+ <p>Displays the side lengths.</p>
358
+ </InfoTip>
359
+ </Row>
360
+ </>
361
+ )}
362
+ {this.props.correct?.type === "segment" && (
363
+ <Row>
364
+ <FieldLabel>Number of segments: </FieldLabel>
365
+ <SegmentCountSelector
366
+ numSegments={this.props.correct?.numSegments}
367
+ onChange={(sides) => {
368
+ this.props.onChange({
369
+ correct: {
370
+ type: "segment",
371
+ numSegments: sides,
372
+ coords: null,
373
+ },
374
+ });
375
+ }}
376
+ />
377
+ </Row>
378
+ )}
379
+
380
+ <Row>
381
+ <FieldLabel>Correct answer:</FieldLabel>
382
+ <Typography.BodyMonospace
383
+ style={{
384
+ fontSize: 12,
385
+ backgroundColor: "#eee",
386
+ paddingInline: Spacing.xxSmall_6,
387
+ borderColor: "#ccc",
388
+ borderStyle: "solid",
389
+ borderWidth: 1,
390
+ }}
391
+ >
392
+ {equationString}
393
+ </Typography.BodyMonospace>
147
394
  <InfoTip>
148
395
  <p>
149
396
  Graph the correct answer in the graph below and
150
397
  ensure the equation or point coordinates displayed
151
398
  represent the correct answer.
152
399
  </p>
153
- </InfoTip>{" "}
154
- : {equationString}
155
- </div>
156
-
400
+ </InfoTip>
401
+ </Row>
157
402
  <GraphSettings
158
403
  box={getInteractiveBoxFromSizeClass(sizeClass)}
159
404
  range={this.props.range}
@@ -171,7 +416,6 @@ const InteractiveGraphEditor: any = createReactClass({
171
416
  rulerTicks={this.props.rulerTicks}
172
417
  onChange={this.props.onChange}
173
418
  />
174
-
175
419
  {this.props.correct.type === "polygon" && (
176
420
  <div className="type-settings">
177
421
  <label>
@@ -260,18 +504,18 @@ const InteractiveGraphEditor: any = createReactClass({
260
504
  </div>
261
505
  )}
262
506
  {graph}
263
- </div>
507
+ </View>
264
508
  );
265
- },
509
+ }
266
510
 
267
- changeMatchType: function (e) {
511
+ changeMatchType(e) {
268
512
  const correct = _.extend({}, this.props.correct, {
269
513
  match: e.target.value,
270
514
  });
271
515
  this.props.onChange({correct: correct});
272
- },
516
+ }
273
517
 
274
- serialize: function () {
518
+ serialize(): PerseusInteractiveGraphWidgetOptions {
275
519
  const json = _.pick(
276
520
  this.props,
277
521
  "step",
@@ -291,6 +535,7 @@ const InteractiveGraphEditor: any = createReactClass({
291
535
  // eslint-disable-next-line react/no-string-refs
292
536
  const graph = this.refs.graph;
293
537
  if (graph) {
538
+ // @ts-expect-error TS2339 Property 'getUserInput' does not exist on type 'ReactInstance'. Property 'getUserInput' does not exist on type 'Component<any, {}, any>'.
294
539
  const correct = graph && graph.getUserInput();
295
540
  _.extend(json, {
296
541
  graph: {type: correct.type},
@@ -317,8 +562,9 @@ const InteractiveGraphEditor: any = createReactClass({
317
562
  },
318
563
  );
319
564
  }
565
+ // @ts-expect-error TS2739 Type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "step" | "gridStep" | "snapStep" | "backgroundImage" | "markings" | "labels" | ... 5 more ... | "range">' is missing the following properties from type 'PerseusInteractiveGraphWidgetOptions': graph, correct
320
566
  return json;
321
- },
322
- });
567
+ }
568
+ }
323
569
 
324
570
  export default InteractiveGraphEditor;
@@ -1,5 +1,4 @@
1
1
  {
2
- "exclude": ["dist", "**/*.test.*", "**/*.testdata.*", "**/*.stories.*", "**/*.cypress.*"],
3
2
  "extends": "../tsconfig-shared.json",
4
3
  "compilerOptions": {
5
4
  "outDir": "./dist",