@khanacademy/perseus-editor 0.0.1 → 0.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.
Files changed (125) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/es/index.js +10 -1
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +10 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/strings.js +6 -1
  8. package/package.json +16 -4
  9. package/src/README.md +7 -0
  10. package/src/__stories__/editor.stories.js +33 -0
  11. package/src/__stories__/section-control-button.stories.jsx +27 -0
  12. package/src/__testdata__/input-number_testdata.js +28 -0
  13. package/src/__tests__/editor_test.jsx +123 -0
  14. package/src/__tests__/i18n_test.jsx +585 -0
  15. package/src/__tests__/traversal_test.jsx +378 -0
  16. package/src/all-editors.js +85 -0
  17. package/src/all-editors_test.js +17 -0
  18. package/src/all-widgets.js +10 -0
  19. package/src/article-editor.jsx +437 -0
  20. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  21. package/src/components/blur-input.jsx +62 -0
  22. package/src/components/checkbox.jsx +194 -0
  23. package/src/components/device-framer.jsx +91 -0
  24. package/src/components/drag-target.jsx +88 -0
  25. package/src/components/dropdown-option.jsx +318 -0
  26. package/src/components/form-wrapped-text-field.jsx +248 -0
  27. package/src/components/graph-settings.jsx +631 -0
  28. package/src/components/hover-behavior.jsx +178 -0
  29. package/src/components/json-editor.jsx +118 -0
  30. package/src/components/link.jsx +173 -0
  31. package/src/components/simple-button.jsx +86 -0
  32. package/src/components/sortable.jsx +186 -0
  33. package/src/components/util.js +39 -0
  34. package/src/components/viewport-resizer.jsx +73 -0
  35. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  36. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  37. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  38. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  39. package/src/diffs/__tests__/split-diff_test.js +34 -0
  40. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  41. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  42. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  43. package/src/diffs/article-diff.jsx +70 -0
  44. package/src/diffs/item-diff.jsx +77 -0
  45. package/src/diffs/renderer-diff.jsx +143 -0
  46. package/src/diffs/split-diff.jsx +30 -0
  47. package/src/diffs/string-array-diff.jsx +74 -0
  48. package/src/diffs/structured-item-diff.jsx +269 -0
  49. package/src/diffs/tags-diff.jsx +50 -0
  50. package/src/diffs/text-diff.jsx +193 -0
  51. package/src/diffs/widget-diff-performer.jsx +74 -0
  52. package/src/diffs/widget-diff.jsx +291 -0
  53. package/src/editor-page.jsx +351 -0
  54. package/src/editor.jsx +1364 -0
  55. package/src/hint-editor.jsx +447 -0
  56. package/src/i18n.jsx +139 -0
  57. package/src/iframe-content-renderer.jsx +201 -0
  58. package/src/index.js +27 -8
  59. package/src/item-editor.jsx +159 -0
  60. package/src/item-extras-editor.jsx +150 -0
  61. package/src/katex-error-view.jsx +87 -0
  62. package/src/multirenderer-editor.jsx +1073 -0
  63. package/src/section-control-button.jsx +53 -0
  64. package/src/stateful-article-editor.jsx +176 -0
  65. package/src/stateful-editor-page.jsx +90 -0
  66. package/src/styles/perseus-editor.less +1339 -0
  67. package/src/testing-widgets.js +12 -0
  68. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  69. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  70. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  71. package/src/widgets/categorizer-editor.jsx +103 -0
  72. package/src/widgets/cs-program-editor.jsx +258 -0
  73. package/src/widgets/definition-editor.jsx +76 -0
  74. package/src/widgets/dropdown-editor.jsx +201 -0
  75. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  76. package/src/widgets/example-graphie-widget.jsx +175 -0
  77. package/src/widgets/example-widget-editor.jsx +60 -0
  78. package/src/widgets/example-widget.jsx +153 -0
  79. package/src/widgets/explanation-editor.jsx +89 -0
  80. package/src/widgets/expression-editor.jsx +559 -0
  81. package/src/widgets/graded-group-editor.jsx +162 -0
  82. package/src/widgets/graded-group-set-editor.jsx +101 -0
  83. package/src/widgets/grapher-editor.jsx +154 -0
  84. package/src/widgets/group-editor.jsx +83 -0
  85. package/src/widgets/iframe-editor.jsx +200 -0
  86. package/src/widgets/image-editor.jsx +326 -0
  87. package/src/widgets/input-number-editor.jsx +269 -0
  88. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  89. package/src/widgets/interaction-editor.jsx +1388 -0
  90. package/src/widgets/interactive-graph-editor.jsx +320 -0
  91. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  92. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  93. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  94. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  95. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  96. package/src/widgets/label-image/answer-choices.jsx +231 -0
  97. package/src/widgets/label-image/behavior.jsx +92 -0
  98. package/src/widgets/label-image/marker.jsx +292 -0
  99. package/src/widgets/label-image/question-markers.jsx +181 -0
  100. package/src/widgets/label-image/select-image.jsx +86 -0
  101. package/src/widgets/label-image-editor.jsx +256 -0
  102. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  103. package/src/widgets/matcher-editor.jsx +147 -0
  104. package/src/widgets/matrix-editor.jsx +155 -0
  105. package/src/widgets/measurer-editor.jsx +245 -0
  106. package/src/widgets/molecule-editor.jsx +63 -0
  107. package/src/widgets/number-line-editor.jsx +498 -0
  108. package/src/widgets/numeric-input-editor.jsx +554 -0
  109. package/src/widgets/orderer-editor.jsx +166 -0
  110. package/src/widgets/passage-editor.jsx +115 -0
  111. package/src/widgets/passage-ref-editor.jsx +77 -0
  112. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  113. package/src/widgets/plotter-editor.jsx +537 -0
  114. package/src/widgets/radio/editor.jsx +399 -0
  115. package/src/widgets/reaction-diagram-editor.jsx +126 -0
  116. package/src/widgets/sequence-editor.jsx +205 -0
  117. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  118. package/src/widgets/simple-markdown-tester.jsx +91 -0
  119. package/src/widgets/simulator-editor.jsx +144 -0
  120. package/src/widgets/sorter-editor.jsx +100 -0
  121. package/src/widgets/table-editor.jsx +164 -0
  122. package/src/widgets/transformer-editor.jsx +1006 -0
  123. package/src/widgets/unit-editor.jsx +293 -0
  124. package/src/widgets/video-editor.jsx +74 -0
  125. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,1388 @@
1
+ /* eslint-disable @babel/no-invalid-this, react/no-unsafe, react/sort-comp */
2
+ // @flow
3
+ import {
4
+ components,
5
+ Changeable,
6
+ Dependencies,
7
+ EditorJsonify,
8
+ KhanColors,
9
+ Util,
10
+ ArrowPicker,
11
+ ColorPicker,
12
+ DashPicker,
13
+ ElementContainer,
14
+ } from "@khanacademy/perseus";
15
+ import PropTypes from "prop-types";
16
+ import * as React from "react";
17
+ import _ from "underscore";
18
+
19
+ import GraphSettings from "../components/graph-settings.jsx";
20
+
21
+ import ConstraintEditor from "./interaction/constraint-editor.jsx";
22
+
23
+ const {MathInput, NumberInput, TextInput} = components;
24
+ const {getDependencies} = Dependencies;
25
+ const {unescapeMathMode} = Util;
26
+
27
+ const defaultInteractionProps = {
28
+ graph: {
29
+ box: [400, 400],
30
+ labels: ["x", "y"],
31
+ range: [
32
+ [-10, 10],
33
+ [-10, 10],
34
+ ],
35
+ tickStep: [1, 1],
36
+ gridStep: [1, 1],
37
+ markings: "graph",
38
+ },
39
+ elements: [],
40
+ };
41
+
42
+ type PointEditorProps = $FlowFixMe;
43
+
44
+ //
45
+ // Editor for non-interactive points
46
+ //
47
+ // TODO(eater): Factor this out
48
+ //
49
+ class PointEditor extends React.Component<PointEditorProps> {
50
+ static propTypes = {
51
+ ...Changeable.propTypes,
52
+ coordX: PropTypes.string,
53
+ coordY: PropTypes.string,
54
+ color: PropTypes.string,
55
+ };
56
+
57
+ static defaultProps = {
58
+ coordX: "0",
59
+ coordY: "0",
60
+ color: KhanColors.BLACK,
61
+ };
62
+
63
+ render(): React.Node {
64
+ const {TeX} = getDependencies();
65
+
66
+ return (
67
+ <div className="graph-settings">
68
+ <div className="perseus-widget-row">
69
+ Coordinate: <TeX>\Large(</TeX>
70
+ <MathInput
71
+ buttonSets={[]}
72
+ buttonsVisible="never"
73
+ value={this.props.coordX}
74
+ onChange={this.change("coordX")}
75
+ />
76
+ <TeX>,</TeX>{" "}
77
+ <MathInput
78
+ buttonSets={[]}
79
+ buttonsVisible="never"
80
+ value={this.props.coordY}
81
+ onChange={this.change("coordY")}
82
+ />
83
+ <TeX>\Large)</TeX>
84
+ </div>
85
+ <div className="perseus-widget-row">
86
+ <ColorPicker
87
+ value={this.props.color}
88
+ onChange={this.change("color")}
89
+ />
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
96
+ return Changeable.change.apply(this, args);
97
+ };
98
+
99
+ serialize = () => {
100
+ return EditorJsonify.serialize.call(this);
101
+ };
102
+ }
103
+
104
+ type LineEditorProps = $FlowFixMe;
105
+
106
+ //
107
+ // Editor for non-interactive line segments
108
+ //
109
+ // TODO(eater): Factor this out
110
+ //
111
+ class LineEditor extends React.Component<LineEditorProps> {
112
+ static propTypes = {
113
+ ...Changeable.propTypes,
114
+ startX: PropTypes.string,
115
+ startY: PropTypes.string,
116
+ endX: PropTypes.string,
117
+ endY: PropTypes.string,
118
+ color: PropTypes.string,
119
+ strokeDasharray: PropTypes.string,
120
+ arrows: PropTypes.string,
121
+ strokeWidth: PropTypes.number,
122
+ };
123
+
124
+ static defaultProps = {
125
+ startX: "-5",
126
+ startY: "5",
127
+ endX: "5",
128
+ endY: "5",
129
+ color: KhanColors.BLACK,
130
+ strokeDasharray: "",
131
+ arrows: "",
132
+ strokeWidth: 2,
133
+ };
134
+
135
+ render(): React.Node {
136
+ const {TeX} = getDependencies();
137
+
138
+ return (
139
+ <div className="graph-settings">
140
+ <div className="perseus-widget-row">
141
+ Start: <TeX>\Large(</TeX>
142
+ <MathInput
143
+ buttonSets={[]}
144
+ buttonsVisible="never"
145
+ value={this.props.startX}
146
+ onChange={this.change("startX")}
147
+ />
148
+ <TeX>,</TeX>{" "}
149
+ <MathInput
150
+ buttonSets={[]}
151
+ buttonsVisible="never"
152
+ value={this.props.startY}
153
+ onChange={this.change("startY")}
154
+ />
155
+ <TeX>\Large)</TeX>
156
+ </div>
157
+ <div className="perseus-widget-row">
158
+ End: <TeX>\Large(</TeX>
159
+ <MathInput
160
+ buttonSets={[]}
161
+ buttonsVisible="never"
162
+ value={this.props.endX}
163
+ onChange={this.change("endX")}
164
+ />
165
+ <TeX>,</TeX>{" "}
166
+ <MathInput
167
+ buttonSets={[]}
168
+ buttonsVisible="never"
169
+ value={this.props.endY}
170
+ onChange={this.change("endY")}
171
+ />
172
+ <TeX>\Large)</TeX>
173
+ </div>
174
+ <div className="perseus-widget-row">
175
+ <ColorPicker
176
+ value={this.props.color}
177
+ onChange={this.change("color")}
178
+ />
179
+ </div>
180
+ <div className="perseus-widget-row">
181
+ <DashPicker
182
+ value={this.props.strokeDasharray}
183
+ onChange={this.change("strokeDasharray")}
184
+ />
185
+ &nbsp; &nbsp;
186
+ <ArrowPicker
187
+ value={this.props.arrows}
188
+ onChange={this.change("arrows")}
189
+ />
190
+ </div>
191
+ <div className="perseus-widget-row">
192
+ <div className="perseus-widget-left-col">
193
+ Width:{" "}
194
+ <NumberInput
195
+ value={this.props.strokeWidth}
196
+ placeholder={2}
197
+ onChange={this.change("strokeWidth")}
198
+ />
199
+ </div>
200
+ </div>
201
+ </div>
202
+ );
203
+ }
204
+
205
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
206
+ return Changeable.change.apply(this, args);
207
+ };
208
+
209
+ serialize = () => {
210
+ return EditorJsonify.serialize.call(this);
211
+ };
212
+ }
213
+
214
+ type MovablePointEditorProps = $FlowFixMe;
215
+
216
+ //
217
+ // Editor for interactive movable points
218
+ //
219
+ // TODO(eater): Factor this out
220
+ // TODO(eater): Rethink how constraints are represented
221
+ //
222
+ class MovablePointEditor extends React.Component<MovablePointEditorProps> {
223
+ static propTypes = {
224
+ ...Changeable.propTypes,
225
+ startX: PropTypes.string,
226
+ startY: PropTypes.string,
227
+ constraint: PropTypes.string,
228
+ snap: PropTypes.number,
229
+ constraintFn: PropTypes.string,
230
+ };
231
+
232
+ static defaultProps = {
233
+ startX: "0",
234
+ startY: "0",
235
+ constraint: "none",
236
+ snap: 0.5,
237
+ constraintFn: "0",
238
+ constraintXMin: "-10",
239
+ constraintXMax: "10",
240
+ constraintYMin: "-10",
241
+ constraintYMax: "10",
242
+ };
243
+
244
+ render(): React.Node {
245
+ const {TeX} = getDependencies();
246
+
247
+ return (
248
+ <div className="graph-settings">
249
+ <div className="perseus-widget-row">
250
+ Start: <TeX>\Large(</TeX>
251
+ <MathInput
252
+ buttonSets={[]}
253
+ buttonsVisible="never"
254
+ value={this.props.startX}
255
+ onChange={this.change("startX")}
256
+ />
257
+ <TeX>,</TeX>{" "}
258
+ <MathInput
259
+ buttonSets={[]}
260
+ buttonsVisible="never"
261
+ value={this.props.startY}
262
+ onChange={this.change("startY")}
263
+ />
264
+ <TeX>\Large)</TeX>
265
+ </div>
266
+ <div className="perseus-widget-row">
267
+ Update <TeX>(x_n, y_n)</TeX> for <TeX>n =</TeX>{" "}
268
+ <NumberInput
269
+ value={this.props.varSubscript}
270
+ placeholder={0}
271
+ onChange={this.change("varSubscript")}
272
+ />
273
+ </div>
274
+ <ConstraintEditor {...this.props} />
275
+ </div>
276
+ );
277
+ }
278
+
279
+ change = (...args) => {
280
+ return Changeable.change.apply(this, args);
281
+ };
282
+
283
+ serialize = () => {
284
+ return EditorJsonify.serialize.call(this);
285
+ };
286
+ }
287
+
288
+ type MovableLineEditorProps = $FlowFixMe;
289
+
290
+ //
291
+ // Editor for interactive movable line segments
292
+ //
293
+ // TODO(eater): Factor this out
294
+ // TODO(eater): Rethink how constraints are represented
295
+ //
296
+ class MovableLineEditor extends React.Component<MovableLineEditorProps> {
297
+ static propTypes = {
298
+ ...Changeable.propTypes,
299
+ startX: PropTypes.string,
300
+ startY: PropTypes.string,
301
+ endX: PropTypes.string,
302
+ endY: PropTypes.string,
303
+ constraint: PropTypes.string,
304
+ snap: PropTypes.number,
305
+ constraintFn: PropTypes.string,
306
+ };
307
+
308
+ static defaultProps = {
309
+ startX: "-5",
310
+ startY: "5",
311
+ endX: "5",
312
+ endY: "5",
313
+ constraint: "none",
314
+ snap: 0.5,
315
+ constraintFn: "0",
316
+ constraintXMin: "-10",
317
+ constraintXMax: "10",
318
+ constraintYMin: "-10",
319
+ constraintYMax: "10",
320
+ };
321
+
322
+ render(): React.Node {
323
+ const {TeX} = getDependencies();
324
+
325
+ return (
326
+ <div className="graph-settings">
327
+ Initial position:
328
+ <div className="perseus-widget-row">
329
+ Start: <TeX>\Large(</TeX>
330
+ <MathInput
331
+ buttonSets={[]}
332
+ buttonsVisible="never"
333
+ value={this.props.startX}
334
+ onChange={this.change("startX")}
335
+ />
336
+ <TeX>,</TeX>{" "}
337
+ <MathInput
338
+ buttonSets={[]}
339
+ buttonsVisible="never"
340
+ value={this.props.startY}
341
+ onChange={this.change("startY")}
342
+ />
343
+ <TeX>\Large)</TeX>
344
+ </div>
345
+ <div className="perseus-widget-row">
346
+ End: <TeX>\Large(</TeX>
347
+ <MathInput
348
+ buttonSets={[]}
349
+ buttonsVisible="never"
350
+ value={this.props.endX}
351
+ onChange={this.change("endX")}
352
+ />
353
+ <TeX>,</TeX>{" "}
354
+ <MathInput
355
+ buttonSets={[]}
356
+ buttonsVisible="never"
357
+ value={this.props.endY}
358
+ onChange={this.change("endY")}
359
+ />
360
+ <TeX>\Large)</TeX>
361
+ </div>
362
+ <div className="perseus-widget-row">
363
+ Start updates <TeX>(x_n, y_n)</TeX> for <TeX>n =</TeX>
364
+ <NumberInput
365
+ value={this.props.startSubscript}
366
+ placeholder={0}
367
+ onChange={this.change("startSubscript")}
368
+ />
369
+ </div>
370
+ <div className="perseus-widget-row">
371
+ End updates <TeX>(x_m, y_m)</TeX> for <TeX>m =</TeX>
372
+ <NumberInput
373
+ value={this.props.endSubscript}
374
+ placeholder={0}
375
+ onChange={this.change("endSubscript")}
376
+ />
377
+ </div>
378
+ <div className="perseus-widget-row">
379
+ All constraints are applied to the start point.
380
+ </div>
381
+ <ConstraintEditor {...this.props} />
382
+ </div>
383
+ );
384
+ }
385
+
386
+ change = (...args) => {
387
+ return Changeable.change.apply(this, args);
388
+ };
389
+
390
+ serialize = () => {
391
+ return EditorJsonify.serialize.call(this);
392
+ };
393
+ }
394
+
395
+ type FunctionEditorProps = $FlowFixMe;
396
+
397
+ //
398
+ // Editor for function plots
399
+ //
400
+ // TODO(eater): Factor this out
401
+ //
402
+ class FunctionEditor extends React.Component<FunctionEditorProps> {
403
+ static propTypes = {
404
+ ...Changeable.propTypes,
405
+ value: PropTypes.string,
406
+ rangeMin: PropTypes.string,
407
+ rangeMax: PropTypes.string,
408
+ color: PropTypes.string,
409
+ strokeDashArray: PropTypes.string,
410
+ strokeWidth: PropTypes.number,
411
+ };
412
+
413
+ static defaultProps = {
414
+ value: "x",
415
+ rangeMin: "-10",
416
+ rangeMax: "10",
417
+ color: KhanColors.BLUE,
418
+ strokeDasharray: "",
419
+ strokeWidth: 2,
420
+ };
421
+
422
+ render(): React.Node {
423
+ const {TeX} = getDependencies();
424
+
425
+ return (
426
+ <div className="graph-settings">
427
+ <div className="perseus-widget-row">
428
+ <TeX>{this.props.funcName + "(x)="}</TeX>{" "}
429
+ <MathInput
430
+ buttonSets={[]}
431
+ buttonsVisible="never"
432
+ value={this.props.value}
433
+ onChange={this.change("value")}
434
+ />
435
+ </div>
436
+ <div className="perseus-widget-row">
437
+ Range: <TeX>\Large(</TeX>
438
+ <MathInput
439
+ buttonSets={[]}
440
+ buttonsVisible="never"
441
+ value={this.props.rangeMin}
442
+ onChange={this.change("rangeMin")}
443
+ />
444
+ <TeX>,</TeX>{" "}
445
+ <MathInput
446
+ buttonSets={[]}
447
+ buttonsVisible="never"
448
+ value={this.props.rangeMax}
449
+ onChange={this.change("rangeMax")}
450
+ />
451
+ <TeX>\Large)</TeX>
452
+ </div>
453
+ <div className="perseus-widget-row">
454
+ <ColorPicker
455
+ value={this.props.color}
456
+ onChange={this.change("color")}
457
+ />
458
+ </div>
459
+ <div className="perseus-widget-row">
460
+ <DashPicker
461
+ value={this.props.strokeDasharray}
462
+ onChange={this.change("strokeDasharray")}
463
+ />
464
+ </div>
465
+ <div className="perseus-widget-row">
466
+ <div className="perseus-widget-left-col">
467
+ Width:{" "}
468
+ <NumberInput
469
+ value={this.props.strokeWidth}
470
+ placeholder={2}
471
+ onChange={this.change("strokeWidth")}
472
+ />
473
+ </div>
474
+ </div>
475
+ </div>
476
+ );
477
+ }
478
+
479
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
480
+ return Changeable.change.apply(this, args);
481
+ };
482
+
483
+ serialize = () => {
484
+ return EditorJsonify.serialize.call(this);
485
+ };
486
+ }
487
+
488
+ type ParametricEditorProps = $FlowFixMe;
489
+
490
+ //
491
+ // Editor for parametric plots
492
+ //
493
+ // TODO(eater): Factor this out
494
+ //
495
+ class ParametricEditor extends React.Component<ParametricEditorProps> {
496
+ static propTypes = {
497
+ ...Changeable.propTypes,
498
+ x: PropTypes.string,
499
+ y: PropTypes.string,
500
+ rangeMin: PropTypes.string,
501
+ rangeMax: PropTypes.string,
502
+ color: PropTypes.string,
503
+ strokeDashArray: PropTypes.string,
504
+ strokeWidth: PropTypes.number,
505
+ };
506
+
507
+ static defaultProps = {
508
+ x: "cos(t)",
509
+ y: "sin(t)",
510
+ rangeMin: "0",
511
+ rangeMax: "2\\pi",
512
+ color: KhanColors.BLUE,
513
+ strokeDasharray: "",
514
+ strokeWidth: 2,
515
+ };
516
+
517
+ render(): React.Node {
518
+ const {TeX} = getDependencies();
519
+
520
+ return (
521
+ <div className="graph-settings">
522
+ <div className="perseus-widget-row">
523
+ <TeX>X(t) =</TeX>{" "}
524
+ <MathInput
525
+ buttonSets={[]}
526
+ buttonsVisible="never"
527
+ value={this.props.x}
528
+ onChange={this.change("x")}
529
+ />
530
+ </div>
531
+ <div className="perseus-widget-row">
532
+ <TeX>Y(t) =</TeX>{" "}
533
+ <MathInput
534
+ buttonSets={[]}
535
+ buttonsVisible="never"
536
+ value={this.props.y}
537
+ onChange={this.change("y")}
538
+ />
539
+ </div>
540
+ <div className="perseus-widget-row">
541
+ Range: <TeX>\Large(</TeX>
542
+ <MathInput
543
+ buttonSets={[]}
544
+ buttonsVisible="never"
545
+ value={this.props.rangeMin}
546
+ onChange={this.change("rangeMin")}
547
+ />
548
+ <TeX>,</TeX>{" "}
549
+ <MathInput
550
+ buttonSets={[]}
551
+ buttonsVisible="never"
552
+ value={this.props.rangeMax}
553
+ onChange={this.change("rangeMax")}
554
+ />
555
+ <TeX>\Large)</TeX>
556
+ </div>
557
+ <div className="perseus-widget-row">
558
+ <ColorPicker
559
+ value={this.props.color}
560
+ onChange={this.change("color")}
561
+ />
562
+ </div>
563
+ <div className="perseus-widget-row">
564
+ <DashPicker
565
+ value={this.props.strokeDasharray}
566
+ onChange={this.change("strokeDasharray")}
567
+ />
568
+ </div>
569
+ <div className="perseus-widget-row">
570
+ <div className="perseus-widget-left-col">
571
+ Width:{" "}
572
+ <NumberInput
573
+ value={this.props.strokeWidth}
574
+ placeholder={2}
575
+ onChange={this.change("strokeWidth")}
576
+ />
577
+ </div>
578
+ </div>
579
+ </div>
580
+ );
581
+ }
582
+
583
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
584
+ return Changeable.change.apply(this, args);
585
+ };
586
+
587
+ serialize = () => {
588
+ return EditorJsonify.serialize.call(this);
589
+ };
590
+ }
591
+
592
+ type LabelEditorProps = $FlowFixMe;
593
+
594
+ //
595
+ // Editor for labels
596
+ //
597
+ // TODO(eater): Factor this out maybe?
598
+ // TODO(eater): Add text direction
599
+ //
600
+ class LabelEditor extends React.Component<LabelEditorProps> {
601
+ static propTypes = {
602
+ ...Changeable.propTypes,
603
+ color: PropTypes.string,
604
+ coordX: PropTypes.string,
605
+ coordY: PropTypes.string,
606
+ label: PropTypes.string,
607
+ };
608
+
609
+ static defaultProps = {
610
+ coordX: "0",
611
+ coordY: "0",
612
+ color: KhanColors.BLACK,
613
+ label: "\\phi",
614
+ };
615
+
616
+ render(): React.Node {
617
+ const {TeX} = getDependencies();
618
+
619
+ return (
620
+ <div className="graph-settings">
621
+ <div className="perseus-widget-row">
622
+ <TextInput
623
+ value={this.props.label}
624
+ onChange={this.change("label")}
625
+ style={{
626
+ width: "100%",
627
+ }}
628
+ />
629
+ </div>
630
+ <div className="perseus-widget-row">
631
+ Location: <TeX>\Large(</TeX>
632
+ <MathInput
633
+ buttonSets={[]}
634
+ buttonsVisible="never"
635
+ value={this.props.coordX}
636
+ onChange={this.change("coordX")}
637
+ />
638
+ <TeX>,</TeX>{" "}
639
+ <MathInput
640
+ buttonSets={[]}
641
+ buttonsVisible="never"
642
+ value={this.props.coordY}
643
+ onChange={this.change("coordY")}
644
+ />
645
+ <TeX>\Large)</TeX>
646
+ </div>
647
+ <div className="perseus-widget-row">
648
+ <ColorPicker
649
+ value={this.props.color}
650
+ onChange={this.change("color")}
651
+ />
652
+ </div>
653
+ </div>
654
+ );
655
+ }
656
+
657
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
658
+ return Changeable.change.apply(this, args);
659
+ };
660
+
661
+ serialize = () => {
662
+ return EditorJsonify.serialize.call(this);
663
+ };
664
+ }
665
+
666
+ type RectangleEditorProps = $FlowFixMe;
667
+
668
+ //
669
+ // Editor for rectangles
670
+ //
671
+ // TODO(eater): Factor this out maybe?
672
+ //
673
+ class RectangleEditor extends React.Component<RectangleEditorProps> {
674
+ static propTypes = {
675
+ ...Changeable.propTypes,
676
+ color: PropTypes.string,
677
+ coordX: PropTypes.string,
678
+ coordY: PropTypes.string,
679
+ height: PropTypes.string,
680
+ width: PropTypes.string,
681
+ };
682
+
683
+ static defaultProps = {
684
+ coordX: "-5",
685
+ coordY: "5",
686
+ width: "2",
687
+ height: "3",
688
+ color: KhanColors.LIGHT_BLUE,
689
+ };
690
+
691
+ render(): React.Node {
692
+ const {TeX} = getDependencies();
693
+
694
+ return (
695
+ <div className="graph-settings">
696
+ <div className="perseus-widget-row">
697
+ Bottom left: <TeX>\Large(</TeX>
698
+ <MathInput
699
+ buttonSets={[]}
700
+ buttonsVisible="never"
701
+ value={this.props.coordX}
702
+ onChange={this.change("coordX")}
703
+ />
704
+ <TeX>,</TeX>{" "}
705
+ <MathInput
706
+ buttonSets={[]}
707
+ buttonsVisible="never"
708
+ value={this.props.coordY}
709
+ onChange={this.change("coordY")}
710
+ />
711
+ <TeX>\Large)</TeX>
712
+ </div>
713
+ <div className="perseus-widget-row">
714
+ Width:{" "}
715
+ <MathInput
716
+ buttonSets={[]}
717
+ buttonsVisible="never"
718
+ value={this.props.width}
719
+ onChange={this.change("width")}
720
+ />
721
+ </div>
722
+ <div className="perseus-widget-row">
723
+ Height:{" "}
724
+ <MathInput
725
+ buttonSets={[]}
726
+ buttonsVisible="never"
727
+ value={this.props.height}
728
+ onChange={this.change("height")}
729
+ />
730
+ </div>
731
+ <div className="perseus-widget-row">
732
+ <ColorPicker
733
+ value={this.props.color}
734
+ lightColors={true}
735
+ onChange={this.change("color")}
736
+ />
737
+ </div>
738
+ <div className="perseus-widget-row">
739
+ You want a border? Sorry, draw your own.
740
+ </div>
741
+ </div>
742
+ );
743
+ }
744
+
745
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
746
+ return Changeable.change.apply(this, args);
747
+ };
748
+
749
+ serialize = () => {
750
+ return EditorJsonify.serialize.call(this);
751
+ };
752
+ }
753
+
754
+ type InteractionEditorProps = $FlowFixMe;
755
+ type InteractionEditorState = $FlowFixMe;
756
+
757
+ class InteractionEditor extends React.Component<
758
+ InteractionEditorProps,
759
+ InteractionEditorState,
760
+ > {
761
+ // TODO(eater): Make more better
762
+ static propTypes = {
763
+ ...Changeable.propTypes,
764
+ elements: PropTypes.arrayOf(PropTypes.object),
765
+ graph: PropTypes.objectOf(PropTypes.any),
766
+ };
767
+
768
+ static widgetName: "interaction" = "interaction";
769
+ static defaultProps: InteractionEditorProps = defaultInteractionProps;
770
+
771
+ state: InteractionEditorState = {
772
+ usedVarSubscripts: this._getAllVarSubscripts(this.props.elements),
773
+ usedFunctionNames: this._getAllFunctionNames(this.props.elements),
774
+ };
775
+
776
+ UNSAFE_componentWillReceiveProps(nextProps: InteractionEditorProps) {
777
+ this.setState({
778
+ usedVarSubscripts: this._getAllVarSubscripts(nextProps.elements),
779
+ usedFunctionNames: this._getAllFunctionNames(nextProps.elements),
780
+ });
781
+ }
782
+
783
+ _getAllVarSubscripts(
784
+ elements: $ReadOnlyArray<$FlowFixMe>,
785
+ ): $ReadOnlyArray<$FlowFixMe> {
786
+ return _.map(
787
+ _.where(elements, {type: "movable-point"}),
788
+ (element) => element.options.varSubscript,
789
+ )
790
+ .concat(
791
+ _.map(
792
+ _.where(elements, {type: "movable-line"}),
793
+ (element) => element.options.startSubscript,
794
+ ),
795
+ )
796
+ .concat(
797
+ _.map(
798
+ _.where(elements, {type: "movable-line"}),
799
+ (element) => element.options.endSubscript,
800
+ ),
801
+ );
802
+ }
803
+
804
+ _getAllFunctionNames(
805
+ elements: $ReadOnlyArray<$FlowFixMe>,
806
+ ): $ReadOnlyArray<string> {
807
+ return _.map(
808
+ _.where(elements, {type: "function"}),
809
+ (element) => element.options.funcName,
810
+ );
811
+ }
812
+
813
+ _updateGraphProps: ($FlowFixMe) => $FlowFixMe = (newProps) => {
814
+ // TODO(eater): GraphSettings should name this tickStep instead
815
+ // of step. Grr..
816
+ this.change({
817
+ graph: _.extend(_.omit(newProps, "step"), {
818
+ tickStep: newProps.step,
819
+ }),
820
+ });
821
+ };
822
+
823
+ _addNewElement: (SyntheticInputEvent<>) => void = (e) => {
824
+ const elementType = e.target.value;
825
+ if (elementType === "") {
826
+ return;
827
+ }
828
+ e.target.value = "";
829
+ const newElement = {
830
+ type: elementType,
831
+ key:
832
+ elementType +
833
+ "-" +
834
+ ((Math.random() * 0xffffff) << 0).toString(16),
835
+ options:
836
+ elementType === "point"
837
+ ? _.clone(PointEditor.defaultProps)
838
+ : elementType === "line"
839
+ ? _.clone(LineEditor.defaultProps)
840
+ : elementType === "movable-point"
841
+ ? _.clone(MovablePointEditor.defaultProps)
842
+ : elementType === "movable-line"
843
+ ? _.clone(MovableLineEditor.defaultProps)
844
+ : elementType === "function"
845
+ ? _.clone(FunctionEditor.defaultProps)
846
+ : elementType === "parametric"
847
+ ? _.clone(ParametricEditor.defaultProps)
848
+ : elementType === "label"
849
+ ? _.clone(LabelEditor.defaultProps)
850
+ : elementType === "rectangle"
851
+ ? _.clone(RectangleEditor.defaultProps)
852
+ : {},
853
+ };
854
+
855
+ let nextSubscript;
856
+ if (elementType === "movable-point") {
857
+ nextSubscript =
858
+ _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
859
+ newElement.options.varSubscript = nextSubscript;
860
+ } else if (elementType === "movable-line") {
861
+ nextSubscript =
862
+ _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
863
+ newElement.options.startSubscript = nextSubscript;
864
+ newElement.options.endSubscript = nextSubscript + 1;
865
+ } else if (elementType === "function") {
866
+ // TODO(eater): The 22nd function added will be {(x) since '{'
867
+ // comes after 'z'
868
+ const nextLetter = String.fromCharCode(
869
+ _.max([
870
+ _.max(
871
+ _.map(this.state.usedFunctionNames, function (c) {
872
+ return c.charCodeAt(0);
873
+ }),
874
+ ),
875
+ "e".charCodeAt(0),
876
+ ]) + 1,
877
+ );
878
+ newElement.options.funcName = nextLetter;
879
+ }
880
+ this.change({
881
+ elements: this.props.elements.concat(newElement),
882
+ });
883
+ };
884
+
885
+ _deleteElement: (number) => void = (index) => {
886
+ const element = this.props.elements[index];
887
+ this.change({elements: _.without(this.props.elements, element)});
888
+ };
889
+
890
+ _moveElementUp: (number) => void = (index) => {
891
+ const element = this.props.elements[index];
892
+ const newElements = _.without(this.props.elements, element);
893
+ newElements.splice(index - 1, 0, element);
894
+ this.change({elements: newElements});
895
+ };
896
+
897
+ _moveElementDown: (number) => void = (index) => {
898
+ const element = this.props.elements[index];
899
+ const newElements = _.without(this.props.elements, element);
900
+ newElements.splice(index + 1, 0, element);
901
+ this.change({elements: newElements});
902
+ };
903
+
904
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
905
+ return Changeable.change.apply(this, args);
906
+ };
907
+
908
+ render(): React.Node {
909
+ const {TeX} = getDependencies();
910
+
911
+ return (
912
+ <div className="perseus-widget-interaction-editor">
913
+ <ElementContainer title="Grid settings">
914
+ <GraphSettings
915
+ editableSettings={["canvas", "graph"]}
916
+ box={this.props.graph.box}
917
+ labels={this.props.graph.labels}
918
+ range={this.props.graph.range}
919
+ step={this.props.graph.tickStep}
920
+ gridStep={this.props.graph.gridStep}
921
+ markings={this.props.graph.markings}
922
+ onChange={this._updateGraphProps}
923
+ />
924
+ {/* $FlowFixMe[incompatible-type] */}
925
+ {this.props.graph.valid !== true && (
926
+ <div>{this.props.graph.valid}</div>
927
+ )}
928
+ </ElementContainer>
929
+ {_.map(
930
+ this.props.elements,
931
+ function (element, n) {
932
+ if (element.type === "movable-point") {
933
+ return (
934
+ <ElementContainer
935
+ title={
936
+ <span>
937
+ Movable point{" "}
938
+ <TeX>
939
+ {"(x_{" +
940
+ element.options
941
+ .varSubscript +
942
+ "}, y_{" +
943
+ element.options
944
+ .varSubscript +
945
+ "})"}
946
+ </TeX>
947
+ </span>
948
+ }
949
+ // eslint-disable-next-line react/jsx-no-bind
950
+ onUp={
951
+ n === 0
952
+ ? null
953
+ : this._moveElementUp.bind(this, n)
954
+ }
955
+ // eslint-disable-next-line react/jsx-no-bind
956
+ onDown={
957
+ n === this.props.elements.length - 1
958
+ ? null
959
+ : this._moveElementDown.bind(
960
+ this,
961
+ n,
962
+ )
963
+ }
964
+ // eslint-disable-next-line react/jsx-no-bind
965
+ onDelete={this._deleteElement.bind(this, n)}
966
+ key={element.key}
967
+ >
968
+ <MovablePointEditor
969
+ {...element.options}
970
+ onChange={(newProps) => {
971
+ const elements = JSON.parse(
972
+ JSON.stringify(
973
+ this.props.elements,
974
+ ),
975
+ );
976
+ _.extend(
977
+ elements[n].options,
978
+ newProps,
979
+ );
980
+ this.change({elements: elements});
981
+ }}
982
+ />
983
+ </ElementContainer>
984
+ );
985
+ }
986
+ if (element.type === "movable-line") {
987
+ return (
988
+ <ElementContainer
989
+ title={
990
+ <span>
991
+ Movable line{" "}
992
+ <TeX>
993
+ {"(x_{" +
994
+ element.options
995
+ .startSubscript +
996
+ "}, y_{" +
997
+ element.options
998
+ .startSubscript +
999
+ "})"}
1000
+ </TeX>{" "}
1001
+ to{" "}
1002
+ <TeX>
1003
+ {"(x_{" +
1004
+ element.options
1005
+ .endSubscript +
1006
+ "}, y_{" +
1007
+ element.options
1008
+ .endSubscript +
1009
+ "})"}
1010
+ </TeX>
1011
+ </span>
1012
+ }
1013
+ // eslint-disable-next-line react/jsx-no-bind
1014
+ onUp={
1015
+ n === 0
1016
+ ? null
1017
+ : this._moveElementUp.bind(this, n)
1018
+ }
1019
+ // eslint-disable-next-line react/jsx-no-bind
1020
+ onDown={
1021
+ n === this.props.elements.length - 1
1022
+ ? null
1023
+ : this._moveElementDown.bind(
1024
+ this,
1025
+ n,
1026
+ )
1027
+ }
1028
+ // eslint-disable-next-line react/jsx-no-bind
1029
+ onDelete={this._deleteElement.bind(this, n)}
1030
+ key={element.key}
1031
+ >
1032
+ <MovableLineEditor
1033
+ {...element.options}
1034
+ onChange={(newProps) => {
1035
+ const elements = JSON.parse(
1036
+ JSON.stringify(
1037
+ this.props.elements,
1038
+ ),
1039
+ );
1040
+ _.extend(
1041
+ elements[n].options,
1042
+ newProps,
1043
+ );
1044
+ this.change({elements: elements});
1045
+ }}
1046
+ />
1047
+ </ElementContainer>
1048
+ );
1049
+ }
1050
+ if (element.type === "point") {
1051
+ return (
1052
+ <ElementContainer
1053
+ title={
1054
+ <span>
1055
+ Point{" "}
1056
+ <TeX>
1057
+ {"(" +
1058
+ element.options.coordX +
1059
+ ", " +
1060
+ element.options.coordY +
1061
+ ")"}
1062
+ </TeX>
1063
+ </span>
1064
+ }
1065
+ // eslint-disable-next-line react/jsx-no-bind
1066
+ onUp={
1067
+ n === 0
1068
+ ? null
1069
+ : this._moveElementUp.bind(this, n)
1070
+ }
1071
+ // eslint-disable-next-line react/jsx-no-bind
1072
+ onDown={
1073
+ n === this.props.elements.length - 1
1074
+ ? null
1075
+ : this._moveElementDown.bind(
1076
+ this,
1077
+ n,
1078
+ )
1079
+ }
1080
+ // eslint-disable-next-line react/jsx-no-bind
1081
+ onDelete={this._deleteElement.bind(this, n)}
1082
+ key={element.key}
1083
+ >
1084
+ <PointEditor
1085
+ {...element.options}
1086
+ onChange={(newProps) => {
1087
+ const elements = JSON.parse(
1088
+ JSON.stringify(
1089
+ this.props.elements,
1090
+ ),
1091
+ );
1092
+ _.extend(
1093
+ elements[n].options,
1094
+ newProps,
1095
+ );
1096
+ this.change({elements: elements});
1097
+ }}
1098
+ />
1099
+ </ElementContainer>
1100
+ );
1101
+ }
1102
+ if (element.type === "line") {
1103
+ return (
1104
+ <ElementContainer
1105
+ title={
1106
+ <span>
1107
+ Line{" "}
1108
+ <TeX>
1109
+ {"(" +
1110
+ element.options.startX +
1111
+ ", " +
1112
+ element.options.startY +
1113
+ ")"}
1114
+ </TeX>{" "}
1115
+ to{" "}
1116
+ <TeX>
1117
+ {"(" +
1118
+ element.options.endX +
1119
+ ", " +
1120
+ element.options.endY +
1121
+ ")"}
1122
+ </TeX>
1123
+ </span>
1124
+ }
1125
+ // eslint-disable-next-line react/jsx-no-bind
1126
+ onUp={
1127
+ n === 0
1128
+ ? null
1129
+ : this._moveElementUp.bind(this, n)
1130
+ }
1131
+ // eslint-disable-next-line react/jsx-no-bind
1132
+ onDown={
1133
+ n === this.props.elements.length - 1
1134
+ ? null
1135
+ : this._moveElementDown.bind(
1136
+ this,
1137
+ n,
1138
+ )
1139
+ }
1140
+ // eslint-disable-next-line react/jsx-no-bind
1141
+ onDelete={this._deleteElement.bind(this, n)}
1142
+ key={element.key}
1143
+ >
1144
+ <LineEditor
1145
+ {...element.options}
1146
+ onChange={(newProps) => {
1147
+ const elements = JSON.parse(
1148
+ JSON.stringify(
1149
+ this.props.elements,
1150
+ ),
1151
+ );
1152
+ _.extend(
1153
+ elements[n].options,
1154
+ newProps,
1155
+ );
1156
+ this.change({elements: elements});
1157
+ }}
1158
+ />
1159
+ </ElementContainer>
1160
+ );
1161
+ }
1162
+ if (element.type === "function") {
1163
+ return (
1164
+ <ElementContainer
1165
+ title={
1166
+ <span>
1167
+ Function{" "}
1168
+ <TeX>
1169
+ {element.options.funcName +
1170
+ "(x) = " +
1171
+ element.options.value}
1172
+ </TeX>
1173
+ </span>
1174
+ }
1175
+ // eslint-disable-next-line react/jsx-no-bind
1176
+ onUp={
1177
+ n === 0
1178
+ ? null
1179
+ : this._moveElementUp.bind(this, n)
1180
+ }
1181
+ // eslint-disable-next-line react/jsx-no-bind
1182
+ onDown={
1183
+ n === this.props.elements.length - 1
1184
+ ? null
1185
+ : this._moveElementDown.bind(
1186
+ this,
1187
+ n,
1188
+ )
1189
+ }
1190
+ onDelete={this._deleteElement}
1191
+ key={element.key}
1192
+ >
1193
+ <FunctionEditor
1194
+ {...element.options}
1195
+ onChange={(newProps) => {
1196
+ const elements = JSON.parse(
1197
+ JSON.stringify(
1198
+ this.props.elements,
1199
+ ),
1200
+ );
1201
+ _.extend(
1202
+ elements[n].options,
1203
+ newProps,
1204
+ );
1205
+ this.change({elements: elements});
1206
+ }}
1207
+ />
1208
+ </ElementContainer>
1209
+ );
1210
+ }
1211
+ if (element.type === "parametric") {
1212
+ return (
1213
+ <ElementContainer
1214
+ title={<span>Parametric</span>}
1215
+ // eslint-disable-next-line react/jsx-no-bind
1216
+ onUp={
1217
+ n === 0
1218
+ ? null
1219
+ : this._moveElementUp.bind(this, n)
1220
+ }
1221
+ // eslint-disable-next-line react/jsx-no-bind
1222
+ onDown={
1223
+ n === this.props.elements.length - 1
1224
+ ? null
1225
+ : this._moveElementDown.bind(
1226
+ this,
1227
+ n,
1228
+ )
1229
+ }
1230
+ onDelete={this._deleteElement}
1231
+ key={element.key}
1232
+ >
1233
+ <ParametricEditor
1234
+ {...element.options}
1235
+ onChange={(newProps) => {
1236
+ const elements = JSON.parse(
1237
+ JSON.stringify(
1238
+ this.props.elements,
1239
+ ),
1240
+ );
1241
+ _.extend(
1242
+ elements[n].options,
1243
+ newProps,
1244
+ );
1245
+ this.change({elements: elements});
1246
+ }}
1247
+ />
1248
+ </ElementContainer>
1249
+ );
1250
+ }
1251
+ if (element.type === "label") {
1252
+ return (
1253
+ <ElementContainer
1254
+ title={
1255
+ <span>
1256
+ Label{" "}
1257
+ <TeX>
1258
+ {unescapeMathMode(
1259
+ element.options.label,
1260
+ )}
1261
+ </TeX>{" "}
1262
+ </span>
1263
+ }
1264
+ // eslint-disable-next-line react/jsx-no-bind
1265
+ onUp={
1266
+ n === 0
1267
+ ? null
1268
+ : this._moveElementUp.bind(this, n)
1269
+ }
1270
+ // eslint-disable-next-line react/jsx-no-bind
1271
+ onDown={
1272
+ n === this.props.elements.length - 1
1273
+ ? null
1274
+ : this._moveElementDown.bind(
1275
+ this,
1276
+ n,
1277
+ )
1278
+ }
1279
+ onDelete={this._deleteElement}
1280
+ key={element.key}
1281
+ >
1282
+ <LabelEditor
1283
+ {...element.options}
1284
+ onChange={(newProps) => {
1285
+ const elements = JSON.parse(
1286
+ JSON.stringify(
1287
+ this.props.elements,
1288
+ ),
1289
+ );
1290
+ _.extend(
1291
+ elements[n].options,
1292
+ newProps,
1293
+ );
1294
+ this.change({elements: elements});
1295
+ }}
1296
+ />
1297
+ </ElementContainer>
1298
+ );
1299
+ }
1300
+ if (element.type === "rectangle") {
1301
+ return (
1302
+ <ElementContainer
1303
+ title={
1304
+ <span>
1305
+ Rectangle{" "}
1306
+ <TeX>
1307
+ {"(" +
1308
+ element.options.coordX +
1309
+ ", " +
1310
+ element.options.coordY +
1311
+ ")"}
1312
+ </TeX>
1313
+ &nbsp;&mdash;&nbsp;
1314
+ <TeX>
1315
+ {element.options.width +
1316
+ " \\times " +
1317
+ element.options.height}
1318
+ </TeX>
1319
+ </span>
1320
+ }
1321
+ // eslint-disable-next-line react/jsx-no-bind
1322
+ onUp={
1323
+ n === 0
1324
+ ? null
1325
+ : this._moveElementUp.bind(this, n)
1326
+ }
1327
+ // eslint-disable-next-line react/jsx-no-bind
1328
+ onDown={
1329
+ n === this.props.elements.length - 1
1330
+ ? null
1331
+ : this._moveElementDown.bind(
1332
+ this,
1333
+ n,
1334
+ )
1335
+ }
1336
+ onDelete={this._deleteElement}
1337
+ key={element.key}
1338
+ >
1339
+ <RectangleEditor
1340
+ {...element.options}
1341
+ onChange={(newProps) => {
1342
+ const elements = JSON.parse(
1343
+ JSON.stringify(
1344
+ this.props.elements,
1345
+ ),
1346
+ );
1347
+ _.extend(
1348
+ elements[n].options,
1349
+ newProps,
1350
+ );
1351
+ this.change({elements: elements});
1352
+ }}
1353
+ />
1354
+ </ElementContainer>
1355
+ );
1356
+ }
1357
+ },
1358
+ this,
1359
+ )}
1360
+ <div className="perseus-widget-interaction-editor-select-element">
1361
+ <select onChange={this._addNewElement}>
1362
+ <option value="">Add an element{"\u2026"}</option>
1363
+ <option disabled>--</option>
1364
+ <option value="point">Point</option>
1365
+ <option value="line">Line segment</option>
1366
+ <option value="function">Function plot</option>
1367
+ <option value="parametric">Parametric plot</option>
1368
+ <option value="label">Label</option>
1369
+ <option value="rectangle">Rectangle</option>
1370
+ <option value="movable-point">
1371
+ &#x2605; Movable point
1372
+ </option>
1373
+ <option value="movable-line">
1374
+ &#x2605; Movable line segment
1375
+ </option>
1376
+ </select>
1377
+ </div>
1378
+ </div>
1379
+ );
1380
+ /* eslint-enable max-len */
1381
+ }
1382
+
1383
+ serialize: () => $FlowFixMe = () => {
1384
+ return EditorJsonify.serialize.call(this);
1385
+ };
1386
+ }
1387
+
1388
+ export default InteractionEditor;