@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,537 @@
1
+ /* eslint-disable @babel/no-invalid-this, one-var, react/no-unsafe, react/sort-comp */
2
+ // @flow
3
+ import {number as knumber} from "@khanacademy/kmath";
4
+ import {
5
+ components,
6
+ Dependencies,
7
+ PlotterWidget,
8
+ Util,
9
+ } from "@khanacademy/perseus";
10
+ import PropTypes from "prop-types";
11
+ import * as React from "react";
12
+ import ReactDOM from "react-dom";
13
+ import _ from "underscore";
14
+
15
+ import BlurInput from "../components/blur-input.jsx";
16
+
17
+ const {InfoTip, NumberInput, RangeInput, TextListEditor} = components;
18
+ const Plotter = PlotterWidget.widget;
19
+
20
+ const BAR = "bar",
21
+ LINE = "line",
22
+ PIC = "pic",
23
+ HISTOGRAM = "histogram",
24
+ DOTPLOT = "dotplot";
25
+
26
+ // Return a copy of array with length n, padded with given value
27
+ function padArray(array, n, value) {
28
+ const copy = _.clone(array);
29
+ copy.length = n;
30
+ for (let i = array.length; i < n; i++) {
31
+ copy[i] = value;
32
+ }
33
+ return copy;
34
+ }
35
+
36
+ const editorDefaults = {
37
+ scaleY: 1,
38
+ maxY: 10,
39
+ snapsPerLine: 2,
40
+ };
41
+
42
+ const widgetPropTypes = {
43
+ type: PropTypes.oneOf([BAR, LINE, PIC, HISTOGRAM, DOTPLOT]),
44
+ labels: PropTypes.arrayOf(PropTypes.string),
45
+ categories: PropTypes.arrayOf(
46
+ PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
47
+ ),
48
+
49
+ scaleY: PropTypes.number,
50
+ maxY: PropTypes.number,
51
+ snapsPerLine: PropTypes.number,
52
+
53
+ picSize: PropTypes.number,
54
+ pixBoxHeight: PropTypes.number,
55
+ picUrl: PropTypes.string,
56
+
57
+ plotDimensions: PropTypes.arrayOf(PropTypes.number),
58
+ labelInterval: PropTypes.number,
59
+ starting: PropTypes.arrayOf(PropTypes.number),
60
+ correct: PropTypes.arrayOf(PropTypes.number),
61
+ static: PropTypes.bool,
62
+ onChange: PropTypes.func,
63
+ };
64
+
65
+ const formatNumber = (num) => "$" + knumber.round(num, 2) + "$";
66
+
67
+ type Props = $FlowFixMe;
68
+ type State = $FlowFixMe;
69
+
70
+ class PlotterEditor extends React.Component<Props, State> {
71
+ static propTypes = widgetPropTypes;
72
+ static widgetName: string = "plotter";
73
+
74
+ static defaultProps: Props = {
75
+ ...editorDefaults,
76
+ correct: [1],
77
+ starting: [1],
78
+
79
+ type: BAR,
80
+ labels: ["", ""],
81
+ categories: [""],
82
+
83
+ picSize: 30,
84
+ picBoxHeight: 36,
85
+ plotDimensions: [275, 200],
86
+ labelInterval: 1,
87
+
88
+ get picUrl() {
89
+ const staticUrl = Dependencies.getDependencies().staticUrl;
90
+ if (staticUrl) {
91
+ return staticUrl("/images/badges/earth-small.png");
92
+ }
93
+
94
+ return null;
95
+ },
96
+ };
97
+
98
+ state: State = {
99
+ editing: this.props.static ? "starting" : "correct",
100
+ pic: null,
101
+ loadedUrl: null,
102
+ minX: null,
103
+ maxX: null,
104
+ tickStep: null,
105
+ };
106
+
107
+ // TODO(jangmi, CP-3288): Remove usage of `UNSAFE_componentWillMount`
108
+ UNSAFE_componentWillMount() {
109
+ this.fetchPic(this.props.picUrl);
110
+ }
111
+
112
+ UNSAFE_componentWillReceiveProps(nextProps: $FlowFixMe) {
113
+ this.fetchPic(nextProps.picUrl);
114
+
115
+ if (nextProps.static) {
116
+ this.setState({
117
+ editing: "starting",
118
+ });
119
+ }
120
+ }
121
+
122
+ fetchPic: (string) => $FlowFixMe = (url) => {
123
+ if (this.state.loadedUrl !== url) {
124
+ const pic = new Image();
125
+ pic.src = url;
126
+ pic.onload = () => {
127
+ this.setState({
128
+ pic: pic,
129
+ loadedUrl: url,
130
+ });
131
+ };
132
+ }
133
+ };
134
+
135
+ render(): React.Node {
136
+ const setFromScale = _.contains(
137
+ [LINE, HISTOGRAM, DOTPLOT],
138
+ this.props.type,
139
+ );
140
+ const canChangeSnaps = !_.contains([PIC, DOTPLOT], this.props.type);
141
+ const props = {
142
+ trackInteraction: () => {},
143
+ ...this.props,
144
+ };
145
+
146
+ return (
147
+ <div className="perseus-widget-plotter-editor">
148
+ <div>
149
+ Chart type:{" "}
150
+ {_.map(
151
+ [BAR, LINE, PIC, HISTOGRAM, DOTPLOT],
152
+ function (type) {
153
+ return (
154
+ <label key={type}>
155
+ <input
156
+ type="radio"
157
+ name="chart-type"
158
+ checked={this.props.type === type}
159
+ onChange={_.partial(
160
+ this.changeType,
161
+ type,
162
+ )}
163
+ />
164
+ {type}
165
+ </label>
166
+ );
167
+ },
168
+ this,
169
+ )}
170
+ </div>
171
+ <div>
172
+ Labels:{" "}
173
+ {_.map(
174
+ ["x", "y"],
175
+ function (axis, i) {
176
+ return (
177
+ <label key={axis}>
178
+ {axis + ":"}
179
+ <input
180
+ type="text"
181
+ onChange={_.partial(
182
+ this.changeLabel,
183
+ i,
184
+ )}
185
+ defaultValue={this.props.labels[i]}
186
+ />
187
+ </label>
188
+ );
189
+ },
190
+ this,
191
+ )}
192
+ </div>
193
+
194
+ {setFromScale && (
195
+ <div className="set-from-scale-box">
196
+ <span className="categories-title">
197
+ Set Categories From Scale
198
+ </span>
199
+ <div>
200
+ <label>
201
+ Tick Step:{" "}
202
+ <NumberInput
203
+ placeholder={1}
204
+ useArrowKeys={true}
205
+ value={this.state.tickStep}
206
+ onChange={this.handleChangeTickStep}
207
+ />
208
+ </label>
209
+ <InfoTip>
210
+ <p>The difference between adjacent ticks.</p>
211
+ </InfoTip>
212
+ </div>
213
+ <div>
214
+ <label>
215
+ Range:{" "}
216
+ <RangeInput
217
+ placeholder={[0, 10]}
218
+ useArrowKeys={true}
219
+ value={[this.state.minX, this.state.maxX]}
220
+ onChange={this.handleChangeRange}
221
+ />
222
+ </label>
223
+ </div>
224
+ <div>
225
+ <button onClick={this.setCategoriesFromScale}>
226
+ Set Categories{" "}
227
+ </button>
228
+ </div>
229
+ </div>
230
+ )}
231
+ <div>
232
+ <label>
233
+ Label Interval:{" "}
234
+ <NumberInput
235
+ useArrowKeys={true}
236
+ value={this.props.labelInterval}
237
+ onChange={this.changeLabelInterval}
238
+ />
239
+ </label>
240
+ <InfoTip>
241
+ <p>
242
+ Which ticks to display the labels for. For instance,
243
+ setting this to "4" will only show every 4th label
244
+ (plus the last one)
245
+ </p>
246
+ </InfoTip>
247
+ </div>
248
+ {this.props.type === PIC && (
249
+ <div>
250
+ <label>
251
+ Picture:{" "}
252
+ <BlurInput
253
+ className="pic-url"
254
+ value={this.props.picUrl}
255
+ onChange={this.changePicUrl}
256
+ />
257
+ <InfoTip>
258
+ <p>
259
+ Use the default picture of Earth, or insert
260
+ the URL for a different picture using the
261
+ "Add image" function.
262
+ </p>
263
+ </InfoTip>
264
+ </label>
265
+ {this.state.pic &&
266
+ this.state.pic.width !== this.state.pic.height && (
267
+ <p className="warning">
268
+ <b>Warning</b>: You are using a picture
269
+ which is not square. This means the image
270
+ will get distorted. You should probably crop
271
+ it to be square.
272
+ </p>
273
+ )}
274
+ </div>
275
+ )}
276
+ <div>
277
+ <label>
278
+ Categories:{" "}
279
+ <TextListEditor
280
+ // eslint-disable-next-line react/no-string-refs
281
+ ref="categories"
282
+ layout="horizontal"
283
+ options={this.props.categories}
284
+ onChange={this.changeCategories}
285
+ />
286
+ </label>
287
+ </div>
288
+ <div>
289
+ <label>
290
+ Scale (y):{" "}
291
+ <input
292
+ type="text"
293
+ onChange={this.changeScale}
294
+ defaultValue={this.props.scaleY}
295
+ />
296
+ </label>
297
+ </div>
298
+ <div>
299
+ <label>
300
+ Max y:{" "}
301
+ <input
302
+ type="text"
303
+ // eslint-disable-next-line react/no-string-refs
304
+ ref="maxY"
305
+ onChange={this.changeMax}
306
+ defaultValue={this.props.maxY}
307
+ />
308
+ </label>
309
+ </div>
310
+ {canChangeSnaps && (
311
+ <div>
312
+ <label>
313
+ Snaps per line:{" "}
314
+ <input
315
+ type="text"
316
+ onChange={this.changeSnaps}
317
+ defaultValue={this.props.snapsPerLine}
318
+ />
319
+ </label>
320
+ <InfoTip>
321
+ <p>
322
+ Creates the specified number of divisions
323
+ between the horizontal lines. Fewer snaps
324
+ between lines makes the graph easier for the
325
+ student to create correctly.
326
+ </p>
327
+ </InfoTip>
328
+ </div>
329
+ )}
330
+ <div>
331
+ Editing values:{" "}
332
+ {["correct", "starting"].map((editing) => (
333
+ <label key={editing}>
334
+ <input
335
+ type="radio"
336
+ disabled={
337
+ editing === "correct" && this.props.static
338
+ }
339
+ checked={
340
+ this.props.static
341
+ ? editing === "starting"
342
+ : this.state.editing === editing
343
+ }
344
+ onChange={(e) => this.changeEditing(editing)}
345
+ />
346
+ {editing}
347
+ </label>
348
+ ))}
349
+ <InfoTip>
350
+ <p>
351
+ Use this toggle to switch between editing the
352
+ correct answer (what the student will be graded on)
353
+ and the starting values (what the student will see
354
+ plotted when they start the problem). Note: These
355
+ cannot be the same.
356
+ </p>
357
+ <p>
358
+ In static mode, the starting values are rendered out
359
+ to the displayed widget.
360
+ </p>
361
+ </InfoTip>
362
+ </div>
363
+ <Plotter
364
+ {...props}
365
+ starting={this.props[this.state.editing]}
366
+ onChange={this.handlePlotterChange}
367
+ />
368
+ </div>
369
+ );
370
+ }
371
+
372
+ handleChangeTickStep: (number) => void = (value) => {
373
+ this.setState({
374
+ tickStep: value,
375
+ });
376
+ };
377
+
378
+ handleChangeRange: ([number, number]) => void = (newValue) => {
379
+ this.setState({
380
+ minX: newValue[0],
381
+ maxX: newValue[1],
382
+ });
383
+ };
384
+
385
+ changeLabelInterval: (number) => void = (value) => {
386
+ this.props.onChange({
387
+ labelInterval: value,
388
+ });
389
+ };
390
+
391
+ handlePlotterChange: ($FlowFixMe) => void = (newProps) => {
392
+ const props = {};
393
+ props[this.state.editing] = newProps.values;
394
+ this.props.onChange(props);
395
+ };
396
+
397
+ changeType: ($FlowFixMe) => void = (type) => {
398
+ let categories;
399
+ if (type === HISTOGRAM) {
400
+ // Switching to histogram, add a label (0) to the left
401
+ categories = [formatNumber(0)].concat(this.props.categories);
402
+ this.props.onChange({type: type, categories: categories});
403
+ } else if (this.props.type === HISTOGRAM) {
404
+ // Switching from histogram, remove a label from the left
405
+ categories = this.props.categories.slice(1);
406
+ this.props.onChange({type: type, categories: categories});
407
+ } else {
408
+ this.props.onChange({type: type});
409
+ }
410
+
411
+ if (categories) {
412
+ // eslint-disable-next-line react/no-string-refs
413
+ const node = ReactDOM.findDOMNode(this.refs.categories);
414
+ // $FlowFixMe[incompatible-use]
415
+ // $FlowFixMe[prop-missing]
416
+ node.value = categories.join(", ");
417
+ }
418
+ };
419
+
420
+ changeLabel: (number, $FlowFixMe) => void = (i, e) => {
421
+ const labels = _.clone(this.props.labels);
422
+ labels[i] = e.target.value;
423
+ this.props.onChange({labels: labels});
424
+ };
425
+
426
+ changePicUrl: (string) => void = (value) => {
427
+ // We don't need the labels and other data in the plotter, so just
428
+ // extract the raw image and use that.
429
+ // TODO(emily): Maybe indicate that such a change has happened?
430
+ const url = Util.getRealImageUrl(value);
431
+
432
+ this.props.onChange({picUrl: url});
433
+ };
434
+
435
+ changeCategories: ($FlowFixMe) => void = (categories) => {
436
+ let n = categories.length;
437
+ if (this.props.type === HISTOGRAM) {
438
+ // Histograms with n labels/categories have n - 1 buckets
439
+ n--;
440
+ }
441
+ const value = this.props.scaleY;
442
+
443
+ this.props.onChange({
444
+ categories: categories,
445
+ correct: padArray(this.props.correct, n, value),
446
+ starting: padArray(this.props.starting, n, value),
447
+ });
448
+ };
449
+
450
+ changeScale: ($FlowFixMe) => void = (e) => {
451
+ const oldScale = this.props.scaleY;
452
+ const newScale = +e.target.value || editorDefaults.scaleY;
453
+
454
+ const scale = function (value) {
455
+ return (value * newScale) / oldScale;
456
+ };
457
+
458
+ const maxY = scale(this.props.maxY);
459
+
460
+ this.props.onChange({
461
+ scaleY: newScale,
462
+ maxY: maxY,
463
+ correct: _.map(this.props.correct, scale),
464
+ starting: _.map(this.props.starting, scale),
465
+ });
466
+
467
+ // $FlowFixMe[prop-missing]
468
+ // $FlowFixMe[incompatible-use]
469
+ ReactDOM.findDOMNode(this.refs.maxY).value = maxY; // eslint-disable-line react/no-string-refs
470
+ };
471
+
472
+ changeMax: ($FlowFixMe) => void = (e) => {
473
+ this.props.onChange({
474
+ maxY: +e.target.value || editorDefaults.maxY,
475
+ });
476
+ };
477
+
478
+ changeSnaps: ($FlowFixMe) => void = (e) => {
479
+ this.props.onChange({
480
+ snapsPerLine: +e.target.value || editorDefaults.snapsPerLine,
481
+ });
482
+ };
483
+
484
+ changeEditing: (string) => void = (editing) => {
485
+ this.setState({editing: editing});
486
+ };
487
+
488
+ setCategoriesFromScale: () => void = () => {
489
+ const scale = this.state.tickStep || 1;
490
+ const min = this.state.minX || 0;
491
+ const max = this.state.maxX || 0;
492
+ const length = Math.floor((max - min) / scale) * scale;
493
+
494
+ let categories;
495
+ if (this.props.type === HISTOGRAM || this.props.type === DOTPLOT) {
496
+ // Ranges for histogram and dotplot labels should start at zero
497
+ categories = _.range(0, length + scale, scale);
498
+ } else {
499
+ categories = _.range(scale, length + scale, scale);
500
+ }
501
+
502
+ categories = _.map(categories, (num) => num + min);
503
+ categories = _.map(categories, formatNumber);
504
+
505
+ this.changeCategories(categories);
506
+
507
+ // eslint-disable-next-line react/no-string-refs
508
+ const node = ReactDOM.findDOMNode(this.refs.categories);
509
+
510
+ // $FlowFixMe[incompatible-use]
511
+ // $FlowFixMe[prop-missing]
512
+ node.value = categories.join(", ");
513
+ };
514
+
515
+ serialize: () => $FlowFixMe = () => {
516
+ const json = _.pick(
517
+ this.props,
518
+ "correct",
519
+ "starting",
520
+ "type",
521
+ "labels",
522
+ "categories",
523
+ "scaleY",
524
+ "maxY",
525
+ "snapsPerLine",
526
+ "labelInterval",
527
+ );
528
+
529
+ if (this.props.type === PIC) {
530
+ json.picUrl = this.props.picUrl;
531
+ }
532
+
533
+ return json;
534
+ };
535
+ }
536
+
537
+ export default PlotterEditor;