@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.
- package/CHANGELOG.md +22 -0
- package/dist/all-widgets.d.ts +2 -2
- package/dist/all-widgets.js.flow +2 -2
- package/dist/components/graph-points-count-selector.d.ts +7 -0
- package/dist/components/graph-points-count-selector.js.flow +12 -0
- package/dist/components/graph-type-selector.d.ts +7 -0
- package/dist/components/graph-type-selector.js.flow +12 -0
- package/dist/components/segment-count-selector.d.ts +6 -0
- package/dist/components/segment-count-selector.js.flow +11 -0
- package/dist/components/widget-editor.d.ts +31 -0
- package/dist/components/widget-editor.js.flow +91 -0
- package/dist/components/widget-select.d.ts +10 -0
- package/dist/components/widget-select.js.flow +16 -0
- package/dist/es/index.js +423 -229
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +431 -225
- package/dist/index.js.map +1 -1
- package/dist/testing-widgets.d.ts +2 -2
- package/dist/testing-widgets.js.flow +2 -2
- package/dist/util/points.d.ts +3 -0
- package/dist/util/points.js.flow +9 -0
- package/dist/widgets/example-graphie-widget.d.ts +2 -2
- package/dist/widgets/example-graphie-widget.js.flow +2 -2
- package/dist/widgets/example-widget.d.ts +2 -2
- package/dist/widgets/example-widget.js.flow +2 -2
- package/dist/widgets/interactive-graph-editor.d.ts +3182 -1
- package/dist/widgets/interactive-graph-editor.js.flow +5290 -1
- package/package.json +9 -3
- package/src/__stories__/editor.stories.tsx +105 -4
- package/src/article-editor.tsx +1 -1
- package/src/{__stories__ → components/__stories__}/section-control-button.stories.tsx +1 -1
- package/src/components/graph-points-count-selector.tsx +29 -0
- package/src/components/graph-type-selector.tsx +31 -0
- package/src/components/json-editor.tsx +0 -1
- package/src/components/segment-count-selector.tsx +27 -0
- package/src/components/widget-editor.tsx +232 -0
- package/src/components/widget-select.tsx +48 -0
- package/src/editor.tsx +4 -270
- package/src/util/points.test.ts +24 -0
- package/src/util/points.ts +13 -0
- package/src/widgets/interactive-graph-editor.tsx +294 -48
- package/tsconfig-build.json +0 -1
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/diffs/__stories__/perseus-diff-wrapper.d.ts +0 -6
- package/dist/diffs/__stories__/perseus-diff-wrapper.js.flow +0 -12
- /package/dist/{section-control-button.d.ts → components/section-control-button.d.ts} +0 -0
- /package/dist/{section-control-button.js.flow → components/section-control-button.js.flow} +0 -0
- /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
|
|
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
|
-
|
|
38
|
-
displayName: "InteractiveGraphEditor",
|
|
39
|
-
className: "perseus-widget-interactive-graph",
|
|
45
|
+
type Range = [number, number]; // [min, max]
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
145
|
-
<
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
507
|
+
</View>
|
|
264
508
|
);
|
|
265
|
-
}
|
|
509
|
+
}
|
|
266
510
|
|
|
267
|
-
changeMatchType
|
|
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:
|
|
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;
|