@khanacademy/perseus-editor 0.0.1 → 0.1.2

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 (127) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/es/index.css +1020 -0
  4. package/dist/es/index.js +9 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1020 -0
  7. package/dist/index.js +9 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +6 -1
  10. package/package.json +16 -4
  11. package/src/README.md +7 -0
  12. package/src/__stories__/editor.stories.js +33 -0
  13. package/src/__stories__/section-control-button.stories.jsx +27 -0
  14. package/src/__testdata__/input-number_testdata.js +28 -0
  15. package/src/__tests__/editor_test.jsx +123 -0
  16. package/src/__tests__/i18n_test.jsx +585 -0
  17. package/src/__tests__/traversal_test.jsx +378 -0
  18. package/src/all-editors.js +85 -0
  19. package/src/all-editors_test.js +17 -0
  20. package/src/all-widgets.js +10 -0
  21. package/src/article-editor.jsx +437 -0
  22. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  23. package/src/components/blur-input.jsx +62 -0
  24. package/src/components/checkbox.jsx +194 -0
  25. package/src/components/device-framer.jsx +91 -0
  26. package/src/components/drag-target.jsx +88 -0
  27. package/src/components/dropdown-option.jsx +318 -0
  28. package/src/components/form-wrapped-text-field.jsx +248 -0
  29. package/src/components/graph-settings.jsx +631 -0
  30. package/src/components/hover-behavior.jsx +178 -0
  31. package/src/components/json-editor.jsx +121 -0
  32. package/src/components/link.jsx +173 -0
  33. package/src/components/simple-button.jsx +86 -0
  34. package/src/components/sortable.jsx +186 -0
  35. package/src/components/util.js +39 -0
  36. package/src/components/viewport-resizer.jsx +73 -0
  37. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  38. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  39. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  40. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  41. package/src/diffs/__tests__/split-diff_test.js +34 -0
  42. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  43. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  44. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  45. package/src/diffs/article-diff.jsx +70 -0
  46. package/src/diffs/item-diff.jsx +77 -0
  47. package/src/diffs/renderer-diff.jsx +143 -0
  48. package/src/diffs/split-diff.jsx +30 -0
  49. package/src/diffs/string-array-diff.jsx +75 -0
  50. package/src/diffs/structured-item-diff.jsx +269 -0
  51. package/src/diffs/tags-diff.jsx +50 -0
  52. package/src/diffs/text-diff.jsx +194 -0
  53. package/src/diffs/widget-diff-performer.jsx +74 -0
  54. package/src/diffs/widget-diff.jsx +291 -0
  55. package/src/editor-page.jsx +351 -0
  56. package/src/editor.jsx +1364 -0
  57. package/src/hint-editor.jsx +447 -0
  58. package/src/i18n.jsx +139 -0
  59. package/src/iframe-content-renderer.jsx +201 -0
  60. package/src/index.js +27 -8
  61. package/src/item-editor.jsx +159 -0
  62. package/src/item-extras-editor.jsx +150 -0
  63. package/src/katex-error-view.jsx +87 -0
  64. package/src/multirenderer-editor.jsx +1074 -0
  65. package/src/section-control-button.jsx +53 -0
  66. package/src/stateful-article-editor.jsx +176 -0
  67. package/src/stateful-editor-page.jsx +90 -0
  68. package/src/styles/perseus-editor.less +1339 -0
  69. package/src/testing-widgets.js +12 -0
  70. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  71. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  72. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  73. package/src/widgets/categorizer-editor.jsx +103 -0
  74. package/src/widgets/cs-program-editor.jsx +258 -0
  75. package/src/widgets/definition-editor.jsx +75 -0
  76. package/src/widgets/dropdown-editor.jsx +201 -0
  77. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  78. package/src/widgets/example-graphie-widget.jsx +175 -0
  79. package/src/widgets/example-widget-editor.jsx +60 -0
  80. package/src/widgets/example-widget.jsx +153 -0
  81. package/src/widgets/explanation-editor.jsx +89 -0
  82. package/src/widgets/expression-editor.jsx +560 -0
  83. package/src/widgets/graded-group-editor.jsx +162 -0
  84. package/src/widgets/graded-group-set-editor.jsx +101 -0
  85. package/src/widgets/grapher-editor.jsx +154 -0
  86. package/src/widgets/group-editor.jsx +83 -0
  87. package/src/widgets/iframe-editor.jsx +200 -0
  88. package/src/widgets/image-editor.jsx +326 -0
  89. package/src/widgets/input-number-editor.jsx +269 -0
  90. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  91. package/src/widgets/interaction-editor.jsx +1389 -0
  92. package/src/widgets/interactive-graph-editor.jsx +322 -0
  93. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  94. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  95. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  96. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  97. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  98. package/src/widgets/label-image/answer-choices.jsx +231 -0
  99. package/src/widgets/label-image/behavior.jsx +92 -0
  100. package/src/widgets/label-image/marker.jsx +292 -0
  101. package/src/widgets/label-image/question-markers.jsx +181 -0
  102. package/src/widgets/label-image/select-image.jsx +86 -0
  103. package/src/widgets/label-image-editor.jsx +257 -0
  104. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  105. package/src/widgets/matcher-editor.jsx +147 -0
  106. package/src/widgets/matrix-editor.jsx +155 -0
  107. package/src/widgets/measurer-editor.jsx +245 -0
  108. package/src/widgets/molecule-editor.jsx +63 -0
  109. package/src/widgets/number-line-editor.jsx +498 -0
  110. package/src/widgets/numeric-input-editor.jsx +554 -0
  111. package/src/widgets/orderer-editor.jsx +166 -0
  112. package/src/widgets/passage-editor.jsx +115 -0
  113. package/src/widgets/passage-ref-editor.jsx +77 -0
  114. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  115. package/src/widgets/plotter-editor.jsx +537 -0
  116. package/src/widgets/radio/editor.jsx +398 -0
  117. package/src/widgets/reaction-diagram-editor.jsx +127 -0
  118. package/src/widgets/sequence-editor.jsx +205 -0
  119. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  120. package/src/widgets/simple-markdown-tester.jsx +91 -0
  121. package/src/widgets/simulator-editor.jsx +144 -0
  122. package/src/widgets/sorter-editor.jsx +100 -0
  123. package/src/widgets/table-editor.jsx +164 -0
  124. package/src/widgets/transformer-editor.jsx +1006 -0
  125. package/src/widgets/unit-editor.jsx +293 -0
  126. package/src/widgets/video-editor.jsx +74 -0
  127. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,1389 @@
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
+ // eslint-disable-next-line react/forbid-prop-types
765
+ elements: PropTypes.arrayOf(PropTypes.object),
766
+ graph: PropTypes.objectOf(PropTypes.any),
767
+ };
768
+
769
+ static widgetName: "interaction" = "interaction";
770
+ static defaultProps: InteractionEditorProps = defaultInteractionProps;
771
+
772
+ state: InteractionEditorState = {
773
+ usedVarSubscripts: this._getAllVarSubscripts(this.props.elements),
774
+ usedFunctionNames: this._getAllFunctionNames(this.props.elements),
775
+ };
776
+
777
+ UNSAFE_componentWillReceiveProps(nextProps: InteractionEditorProps) {
778
+ this.setState({
779
+ usedVarSubscripts: this._getAllVarSubscripts(nextProps.elements),
780
+ usedFunctionNames: this._getAllFunctionNames(nextProps.elements),
781
+ });
782
+ }
783
+
784
+ _getAllVarSubscripts(
785
+ elements: $ReadOnlyArray<$FlowFixMe>,
786
+ ): $ReadOnlyArray<$FlowFixMe> {
787
+ return _.map(
788
+ _.where(elements, {type: "movable-point"}),
789
+ (element) => element.options.varSubscript,
790
+ )
791
+ .concat(
792
+ _.map(
793
+ _.where(elements, {type: "movable-line"}),
794
+ (element) => element.options.startSubscript,
795
+ ),
796
+ )
797
+ .concat(
798
+ _.map(
799
+ _.where(elements, {type: "movable-line"}),
800
+ (element) => element.options.endSubscript,
801
+ ),
802
+ );
803
+ }
804
+
805
+ _getAllFunctionNames(
806
+ elements: $ReadOnlyArray<$FlowFixMe>,
807
+ ): $ReadOnlyArray<string> {
808
+ return _.map(
809
+ _.where(elements, {type: "function"}),
810
+ (element) => element.options.funcName,
811
+ );
812
+ }
813
+
814
+ _updateGraphProps: ($FlowFixMe) => $FlowFixMe = (newProps) => {
815
+ // TODO(eater): GraphSettings should name this tickStep instead
816
+ // of step. Grr..
817
+ this.change({
818
+ graph: _.extend(_.omit(newProps, "step"), {
819
+ tickStep: newProps.step,
820
+ }),
821
+ });
822
+ };
823
+
824
+ _addNewElement: (SyntheticInputEvent<>) => void = (e) => {
825
+ const elementType = e.target.value;
826
+ if (elementType === "") {
827
+ return;
828
+ }
829
+ e.target.value = "";
830
+ const newElement = {
831
+ type: elementType,
832
+ key:
833
+ elementType +
834
+ "-" +
835
+ ((Math.random() * 0xffffff) << 0).toString(16),
836
+ options:
837
+ elementType === "point"
838
+ ? _.clone(PointEditor.defaultProps)
839
+ : elementType === "line"
840
+ ? _.clone(LineEditor.defaultProps)
841
+ : elementType === "movable-point"
842
+ ? _.clone(MovablePointEditor.defaultProps)
843
+ : elementType === "movable-line"
844
+ ? _.clone(MovableLineEditor.defaultProps)
845
+ : elementType === "function"
846
+ ? _.clone(FunctionEditor.defaultProps)
847
+ : elementType === "parametric"
848
+ ? _.clone(ParametricEditor.defaultProps)
849
+ : elementType === "label"
850
+ ? _.clone(LabelEditor.defaultProps)
851
+ : elementType === "rectangle"
852
+ ? _.clone(RectangleEditor.defaultProps)
853
+ : {},
854
+ };
855
+
856
+ let nextSubscript;
857
+ if (elementType === "movable-point") {
858
+ nextSubscript =
859
+ _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
860
+ newElement.options.varSubscript = nextSubscript;
861
+ } else if (elementType === "movable-line") {
862
+ nextSubscript =
863
+ _.max([_.max(this.state.usedVarSubscripts), -1]) + 1;
864
+ newElement.options.startSubscript = nextSubscript;
865
+ newElement.options.endSubscript = nextSubscript + 1;
866
+ } else if (elementType === "function") {
867
+ // TODO(eater): The 22nd function added will be {(x) since '{'
868
+ // comes after 'z'
869
+ const nextLetter = String.fromCharCode(
870
+ _.max([
871
+ _.max(
872
+ _.map(this.state.usedFunctionNames, function (c) {
873
+ return c.charCodeAt(0);
874
+ }),
875
+ ),
876
+ "e".charCodeAt(0),
877
+ ]) + 1,
878
+ );
879
+ newElement.options.funcName = nextLetter;
880
+ }
881
+ this.change({
882
+ elements: this.props.elements.concat(newElement),
883
+ });
884
+ };
885
+
886
+ _deleteElement: (number) => void = (index) => {
887
+ const element = this.props.elements[index];
888
+ this.change({elements: _.without(this.props.elements, element)});
889
+ };
890
+
891
+ _moveElementUp: (number) => void = (index) => {
892
+ const element = this.props.elements[index];
893
+ const newElements = _.without(this.props.elements, element);
894
+ newElements.splice(index - 1, 0, element);
895
+ this.change({elements: newElements});
896
+ };
897
+
898
+ _moveElementDown: (number) => void = (index) => {
899
+ const element = this.props.elements[index];
900
+ const newElements = _.without(this.props.elements, element);
901
+ newElements.splice(index + 1, 0, element);
902
+ this.change({elements: newElements});
903
+ };
904
+
905
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
906
+ return Changeable.change.apply(this, args);
907
+ };
908
+
909
+ render(): React.Node {
910
+ const {TeX} = getDependencies();
911
+
912
+ return (
913
+ <div className="perseus-widget-interaction-editor">
914
+ <ElementContainer title="Grid settings">
915
+ <GraphSettings
916
+ editableSettings={["canvas", "graph"]}
917
+ box={this.props.graph.box}
918
+ labels={this.props.graph.labels}
919
+ range={this.props.graph.range}
920
+ step={this.props.graph.tickStep}
921
+ gridStep={this.props.graph.gridStep}
922
+ markings={this.props.graph.markings}
923
+ onChange={this._updateGraphProps}
924
+ />
925
+ {/* $FlowFixMe[incompatible-type] */}
926
+ {this.props.graph.valid !== true && (
927
+ <div>{this.props.graph.valid}</div>
928
+ )}
929
+ </ElementContainer>
930
+ {_.map(
931
+ this.props.elements,
932
+ function (element, n) {
933
+ if (element.type === "movable-point") {
934
+ return (
935
+ <ElementContainer
936
+ title={
937
+ <span>
938
+ Movable point{" "}
939
+ <TeX>
940
+ {"(x_{" +
941
+ element.options
942
+ .varSubscript +
943
+ "}, y_{" +
944
+ element.options
945
+ .varSubscript +
946
+ "})"}
947
+ </TeX>
948
+ </span>
949
+ }
950
+ // eslint-disable-next-line react/jsx-no-bind
951
+ onUp={
952
+ n === 0
953
+ ? null
954
+ : this._moveElementUp.bind(this, n)
955
+ }
956
+ // eslint-disable-next-line react/jsx-no-bind
957
+ onDown={
958
+ n === this.props.elements.length - 1
959
+ ? null
960
+ : this._moveElementDown.bind(
961
+ this,
962
+ n,
963
+ )
964
+ }
965
+ // eslint-disable-next-line react/jsx-no-bind
966
+ onDelete={this._deleteElement.bind(this, n)}
967
+ key={element.key}
968
+ >
969
+ <MovablePointEditor
970
+ {...element.options}
971
+ onChange={(newProps) => {
972
+ const elements = JSON.parse(
973
+ JSON.stringify(
974
+ this.props.elements,
975
+ ),
976
+ );
977
+ _.extend(
978
+ elements[n].options,
979
+ newProps,
980
+ );
981
+ this.change({elements: elements});
982
+ }}
983
+ />
984
+ </ElementContainer>
985
+ );
986
+ }
987
+ if (element.type === "movable-line") {
988
+ return (
989
+ <ElementContainer
990
+ title={
991
+ <span>
992
+ Movable line{" "}
993
+ <TeX>
994
+ {"(x_{" +
995
+ element.options
996
+ .startSubscript +
997
+ "}, y_{" +
998
+ element.options
999
+ .startSubscript +
1000
+ "})"}
1001
+ </TeX>{" "}
1002
+ to{" "}
1003
+ <TeX>
1004
+ {"(x_{" +
1005
+ element.options
1006
+ .endSubscript +
1007
+ "}, y_{" +
1008
+ element.options
1009
+ .endSubscript +
1010
+ "})"}
1011
+ </TeX>
1012
+ </span>
1013
+ }
1014
+ // eslint-disable-next-line react/jsx-no-bind
1015
+ onUp={
1016
+ n === 0
1017
+ ? null
1018
+ : this._moveElementUp.bind(this, n)
1019
+ }
1020
+ // eslint-disable-next-line react/jsx-no-bind
1021
+ onDown={
1022
+ n === this.props.elements.length - 1
1023
+ ? null
1024
+ : this._moveElementDown.bind(
1025
+ this,
1026
+ n,
1027
+ )
1028
+ }
1029
+ // eslint-disable-next-line react/jsx-no-bind
1030
+ onDelete={this._deleteElement.bind(this, n)}
1031
+ key={element.key}
1032
+ >
1033
+ <MovableLineEditor
1034
+ {...element.options}
1035
+ onChange={(newProps) => {
1036
+ const elements = JSON.parse(
1037
+ JSON.stringify(
1038
+ this.props.elements,
1039
+ ),
1040
+ );
1041
+ _.extend(
1042
+ elements[n].options,
1043
+ newProps,
1044
+ );
1045
+ this.change({elements: elements});
1046
+ }}
1047
+ />
1048
+ </ElementContainer>
1049
+ );
1050
+ }
1051
+ if (element.type === "point") {
1052
+ return (
1053
+ <ElementContainer
1054
+ title={
1055
+ <span>
1056
+ Point{" "}
1057
+ <TeX>
1058
+ {"(" +
1059
+ element.options.coordX +
1060
+ ", " +
1061
+ element.options.coordY +
1062
+ ")"}
1063
+ </TeX>
1064
+ </span>
1065
+ }
1066
+ // eslint-disable-next-line react/jsx-no-bind
1067
+ onUp={
1068
+ n === 0
1069
+ ? null
1070
+ : this._moveElementUp.bind(this, n)
1071
+ }
1072
+ // eslint-disable-next-line react/jsx-no-bind
1073
+ onDown={
1074
+ n === this.props.elements.length - 1
1075
+ ? null
1076
+ : this._moveElementDown.bind(
1077
+ this,
1078
+ n,
1079
+ )
1080
+ }
1081
+ // eslint-disable-next-line react/jsx-no-bind
1082
+ onDelete={this._deleteElement.bind(this, n)}
1083
+ key={element.key}
1084
+ >
1085
+ <PointEditor
1086
+ {...element.options}
1087
+ onChange={(newProps) => {
1088
+ const elements = JSON.parse(
1089
+ JSON.stringify(
1090
+ this.props.elements,
1091
+ ),
1092
+ );
1093
+ _.extend(
1094
+ elements[n].options,
1095
+ newProps,
1096
+ );
1097
+ this.change({elements: elements});
1098
+ }}
1099
+ />
1100
+ </ElementContainer>
1101
+ );
1102
+ }
1103
+ if (element.type === "line") {
1104
+ return (
1105
+ <ElementContainer
1106
+ title={
1107
+ <span>
1108
+ Line{" "}
1109
+ <TeX>
1110
+ {"(" +
1111
+ element.options.startX +
1112
+ ", " +
1113
+ element.options.startY +
1114
+ ")"}
1115
+ </TeX>{" "}
1116
+ to{" "}
1117
+ <TeX>
1118
+ {"(" +
1119
+ element.options.endX +
1120
+ ", " +
1121
+ element.options.endY +
1122
+ ")"}
1123
+ </TeX>
1124
+ </span>
1125
+ }
1126
+ // eslint-disable-next-line react/jsx-no-bind
1127
+ onUp={
1128
+ n === 0
1129
+ ? null
1130
+ : this._moveElementUp.bind(this, n)
1131
+ }
1132
+ // eslint-disable-next-line react/jsx-no-bind
1133
+ onDown={
1134
+ n === this.props.elements.length - 1
1135
+ ? null
1136
+ : this._moveElementDown.bind(
1137
+ this,
1138
+ n,
1139
+ )
1140
+ }
1141
+ // eslint-disable-next-line react/jsx-no-bind
1142
+ onDelete={this._deleteElement.bind(this, n)}
1143
+ key={element.key}
1144
+ >
1145
+ <LineEditor
1146
+ {...element.options}
1147
+ onChange={(newProps) => {
1148
+ const elements = JSON.parse(
1149
+ JSON.stringify(
1150
+ this.props.elements,
1151
+ ),
1152
+ );
1153
+ _.extend(
1154
+ elements[n].options,
1155
+ newProps,
1156
+ );
1157
+ this.change({elements: elements});
1158
+ }}
1159
+ />
1160
+ </ElementContainer>
1161
+ );
1162
+ }
1163
+ if (element.type === "function") {
1164
+ return (
1165
+ <ElementContainer
1166
+ title={
1167
+ <span>
1168
+ Function{" "}
1169
+ <TeX>
1170
+ {element.options.funcName +
1171
+ "(x) = " +
1172
+ element.options.value}
1173
+ </TeX>
1174
+ </span>
1175
+ }
1176
+ // eslint-disable-next-line react/jsx-no-bind
1177
+ onUp={
1178
+ n === 0
1179
+ ? null
1180
+ : this._moveElementUp.bind(this, n)
1181
+ }
1182
+ // eslint-disable-next-line react/jsx-no-bind
1183
+ onDown={
1184
+ n === this.props.elements.length - 1
1185
+ ? null
1186
+ : this._moveElementDown.bind(
1187
+ this,
1188
+ n,
1189
+ )
1190
+ }
1191
+ onDelete={this._deleteElement}
1192
+ key={element.key}
1193
+ >
1194
+ <FunctionEditor
1195
+ {...element.options}
1196
+ onChange={(newProps) => {
1197
+ const elements = JSON.parse(
1198
+ JSON.stringify(
1199
+ this.props.elements,
1200
+ ),
1201
+ );
1202
+ _.extend(
1203
+ elements[n].options,
1204
+ newProps,
1205
+ );
1206
+ this.change({elements: elements});
1207
+ }}
1208
+ />
1209
+ </ElementContainer>
1210
+ );
1211
+ }
1212
+ if (element.type === "parametric") {
1213
+ return (
1214
+ <ElementContainer
1215
+ title={<span>Parametric</span>}
1216
+ // eslint-disable-next-line react/jsx-no-bind
1217
+ onUp={
1218
+ n === 0
1219
+ ? null
1220
+ : this._moveElementUp.bind(this, n)
1221
+ }
1222
+ // eslint-disable-next-line react/jsx-no-bind
1223
+ onDown={
1224
+ n === this.props.elements.length - 1
1225
+ ? null
1226
+ : this._moveElementDown.bind(
1227
+ this,
1228
+ n,
1229
+ )
1230
+ }
1231
+ onDelete={this._deleteElement}
1232
+ key={element.key}
1233
+ >
1234
+ <ParametricEditor
1235
+ {...element.options}
1236
+ onChange={(newProps) => {
1237
+ const elements = JSON.parse(
1238
+ JSON.stringify(
1239
+ this.props.elements,
1240
+ ),
1241
+ );
1242
+ _.extend(
1243
+ elements[n].options,
1244
+ newProps,
1245
+ );
1246
+ this.change({elements: elements});
1247
+ }}
1248
+ />
1249
+ </ElementContainer>
1250
+ );
1251
+ }
1252
+ if (element.type === "label") {
1253
+ return (
1254
+ <ElementContainer
1255
+ title={
1256
+ <span>
1257
+ Label{" "}
1258
+ <TeX>
1259
+ {unescapeMathMode(
1260
+ element.options.label,
1261
+ )}
1262
+ </TeX>{" "}
1263
+ </span>
1264
+ }
1265
+ // eslint-disable-next-line react/jsx-no-bind
1266
+ onUp={
1267
+ n === 0
1268
+ ? null
1269
+ : this._moveElementUp.bind(this, n)
1270
+ }
1271
+ // eslint-disable-next-line react/jsx-no-bind
1272
+ onDown={
1273
+ n === this.props.elements.length - 1
1274
+ ? null
1275
+ : this._moveElementDown.bind(
1276
+ this,
1277
+ n,
1278
+ )
1279
+ }
1280
+ onDelete={this._deleteElement}
1281
+ key={element.key}
1282
+ >
1283
+ <LabelEditor
1284
+ {...element.options}
1285
+ onChange={(newProps) => {
1286
+ const elements = JSON.parse(
1287
+ JSON.stringify(
1288
+ this.props.elements,
1289
+ ),
1290
+ );
1291
+ _.extend(
1292
+ elements[n].options,
1293
+ newProps,
1294
+ );
1295
+ this.change({elements: elements});
1296
+ }}
1297
+ />
1298
+ </ElementContainer>
1299
+ );
1300
+ }
1301
+ if (element.type === "rectangle") {
1302
+ return (
1303
+ <ElementContainer
1304
+ title={
1305
+ <span>
1306
+ Rectangle{" "}
1307
+ <TeX>
1308
+ {"(" +
1309
+ element.options.coordX +
1310
+ ", " +
1311
+ element.options.coordY +
1312
+ ")"}
1313
+ </TeX>
1314
+ &nbsp;&mdash;&nbsp;
1315
+ <TeX>
1316
+ {element.options.width +
1317
+ " \\times " +
1318
+ element.options.height}
1319
+ </TeX>
1320
+ </span>
1321
+ }
1322
+ // eslint-disable-next-line react/jsx-no-bind
1323
+ onUp={
1324
+ n === 0
1325
+ ? null
1326
+ : this._moveElementUp.bind(this, n)
1327
+ }
1328
+ // eslint-disable-next-line react/jsx-no-bind
1329
+ onDown={
1330
+ n === this.props.elements.length - 1
1331
+ ? null
1332
+ : this._moveElementDown.bind(
1333
+ this,
1334
+ n,
1335
+ )
1336
+ }
1337
+ onDelete={this._deleteElement}
1338
+ key={element.key}
1339
+ >
1340
+ <RectangleEditor
1341
+ {...element.options}
1342
+ onChange={(newProps) => {
1343
+ const elements = JSON.parse(
1344
+ JSON.stringify(
1345
+ this.props.elements,
1346
+ ),
1347
+ );
1348
+ _.extend(
1349
+ elements[n].options,
1350
+ newProps,
1351
+ );
1352
+ this.change({elements: elements});
1353
+ }}
1354
+ />
1355
+ </ElementContainer>
1356
+ );
1357
+ }
1358
+ },
1359
+ this,
1360
+ )}
1361
+ <div className="perseus-widget-interaction-editor-select-element">
1362
+ <select onChange={this._addNewElement}>
1363
+ <option value="">Add an element{"\u2026"}</option>
1364
+ <option disabled>--</option>
1365
+ <option value="point">Point</option>
1366
+ <option value="line">Line segment</option>
1367
+ <option value="function">Function plot</option>
1368
+ <option value="parametric">Parametric plot</option>
1369
+ <option value="label">Label</option>
1370
+ <option value="rectangle">Rectangle</option>
1371
+ <option value="movable-point">
1372
+ &#x2605; Movable point
1373
+ </option>
1374
+ <option value="movable-line">
1375
+ &#x2605; Movable line segment
1376
+ </option>
1377
+ </select>
1378
+ </div>
1379
+ </div>
1380
+ );
1381
+ /* eslint-enable max-len */
1382
+ }
1383
+
1384
+ serialize: () => $FlowFixMe = () => {
1385
+ return EditorJsonify.serialize.call(this);
1386
+ };
1387
+ }
1388
+
1389
+ export default InteractionEditor;