@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,398 @@
1
+ /* eslint-disable jsx-a11y/anchor-is-valid, react/forbid-prop-types, react/sort-comp */
2
+ // @flow
3
+ import {
4
+ components,
5
+ icons,
6
+ ApiOptions,
7
+ BaseRadio,
8
+ Changeable,
9
+ } from "@khanacademy/perseus";
10
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
11
+ import PropTypes from "prop-types";
12
+ import * as React from "react";
13
+ import _ from "underscore";
14
+
15
+ import Editor from "../../editor.jsx";
16
+
17
+ const {InlineIcon, PropCheckBox} = components;
18
+ const {iconPlus, iconTrash} = icons;
19
+
20
+ class ChoiceEditor extends React.Component<$FlowFixMe> {
21
+ static propTypes = {
22
+ apiOptions: ApiOptions.propTypes,
23
+
24
+ choice: PropTypes.object,
25
+ showDelete: PropTypes.bool,
26
+ onClueChange: PropTypes.func,
27
+ onContentChange: PropTypes.func,
28
+ onDelete: PropTypes.func,
29
+ };
30
+
31
+ render(): React.Node {
32
+ const checkedClass = this.props.choice.correct
33
+ ? "correct"
34
+ : "incorrect";
35
+ let placeholder = "Type a choice here...";
36
+
37
+ if (this.props.choice.isNoneOfTheAbove) {
38
+ placeholder = this.props.choice.correct
39
+ ? "Type the answer to reveal to the user..."
40
+ : "None of the above";
41
+ }
42
+
43
+ const editor = (
44
+ <Editor
45
+ // eslint-disable-next-line react/no-string-refs
46
+ ref="content-editor"
47
+ apiOptions={this.props.apiOptions}
48
+ content={this.props.choice.content || ""}
49
+ widgetEnabled={false}
50
+ placeholder={placeholder}
51
+ disabled={
52
+ this.props.choice.isNoneOfTheAbove &&
53
+ !this.props.choice.correct
54
+ }
55
+ onChange={this.props.onContentChange}
56
+ />
57
+ );
58
+
59
+ const clueEditor = (
60
+ <Editor
61
+ // eslint-disable-next-line react/no-string-refs
62
+ ref="clue-editor"
63
+ apiOptions={this.props.apiOptions}
64
+ content={this.props.choice.clue || ""}
65
+ widgetEnabled={false}
66
+ placeholder={i18n._(
67
+ "Why is this choice %(correctOrIncorrect)s?",
68
+ {correctOrIncorrect: checkedClass},
69
+ )}
70
+ onChange={this.props.onClueChange}
71
+ />
72
+ );
73
+
74
+ const deleteLink = (
75
+ <a
76
+ className="simple-button orange delete-choice"
77
+ href="#"
78
+ onClick={this.props.onDelete}
79
+ title="Remove this choice"
80
+ >
81
+ <InlineIcon {...iconTrash} />
82
+ </a>
83
+ );
84
+
85
+ return (
86
+ <div className="choice-clue-editors">
87
+ <div className={`choice-editor ${checkedClass}`}>{editor}</div>
88
+ <div className="clue-editor">{clueEditor}</div>
89
+ {this.props.showDelete && deleteLink}
90
+ </div>
91
+ );
92
+ }
93
+ }
94
+
95
+ class RadioEditor extends React.Component<$FlowFixMe> {
96
+ static propTypes = {
97
+ ...Changeable.propTypes,
98
+ apiOptions: ApiOptions.propTypes,
99
+ choices: PropTypes.arrayOf(
100
+ PropTypes.shape({
101
+ content: PropTypes.string,
102
+ clue: PropTypes.string,
103
+ correct: PropTypes.bool,
104
+ }),
105
+ ),
106
+ displayCount: PropTypes.number,
107
+ randomize: PropTypes.bool,
108
+ hasNoneOfTheAbove: PropTypes.bool,
109
+ multipleSelect: PropTypes.bool,
110
+ countChoices: PropTypes.bool,
111
+
112
+ // TODO(kevinb): DEPRECATED: This is be used to force deselectEnabled
113
+ // behavior on mobile but not on desktop. When enabled, the user can
114
+ // deselect a radio input by tapping on it again.
115
+ deselectEnabled: PropTypes.bool,
116
+
117
+ static: PropTypes.bool,
118
+ };
119
+
120
+ static widgetName: string = "radio";
121
+
122
+ static defaultProps: $FlowFixMe = {
123
+ choices: [{}, {}],
124
+ displayCount: null,
125
+ randomize: false,
126
+ hasNoneOfTheAbove: false,
127
+ multipleSelect: false,
128
+ countChoices: false,
129
+ deselectEnabled: false,
130
+ };
131
+
132
+ render(): React.Node {
133
+ const numCorrect = _.reduce(
134
+ this.props.choices,
135
+ function (memo, choice) {
136
+ return choice.correct ? memo + 1 : memo;
137
+ },
138
+ 0,
139
+ );
140
+ return (
141
+ <div>
142
+ <div className="perseus-widget-row">
143
+ <a
144
+ href={
145
+ // This is an editor component, not user-facing.
146
+ "https://docs.google.com/document/d/1frZf7yrWVWb1n4tVjqlzqVUiv1pn4cZXbxgP62-JDBY/edit#heading=h.8ng1isya19nu"
147
+ }
148
+ target="_blank"
149
+ >
150
+ Multiple choice style guide
151
+ </a>
152
+ <br />
153
+ <div className="perseus-widget-left-col">
154
+ <PropCheckBox
155
+ label="Multiple selections"
156
+ labelAlignment="right"
157
+ multipleSelect={this.props.multipleSelect}
158
+ onChange={this.onMultipleSelectChange}
159
+ />
160
+ </div>
161
+ <div className="perseus-widget-right-col">
162
+ <PropCheckBox
163
+ label="Randomize order"
164
+ labelAlignment="right"
165
+ randomize={this.props.randomize}
166
+ onChange={this.props.onChange}
167
+ />
168
+ </div>
169
+ {this.props.multipleSelect && (
170
+ <div className="perseus-widget-left-col">
171
+ <PropCheckBox
172
+ label="Specify number correct"
173
+ labelAlignment="right"
174
+ countChoices={this.props.countChoices}
175
+ onChange={this.onCountChoicesChange}
176
+ />
177
+ </div>
178
+ )}
179
+ </div>
180
+
181
+ <BaseRadio
182
+ // eslint-disable-next-line react/no-string-refs
183
+ ref="baseRadio"
184
+ multipleSelect={this.props.multipleSelect}
185
+ countChoices={this.props.countChoices}
186
+ numCorrect={numCorrect}
187
+ editMode={true}
188
+ labelWrap={false}
189
+ apiOptions={this.props.apiOptions}
190
+ choices={this.props.choices.map(function (choice, i) {
191
+ return {
192
+ content: (
193
+ <ChoiceEditor
194
+ ref={`choice-editor${i}`}
195
+ apiOptions={this.props.apiOptions}
196
+ choice={choice}
197
+ onContentChange={(newProps) => {
198
+ if ("content" in newProps) {
199
+ this.onContentChange(
200
+ i,
201
+ newProps.content,
202
+ );
203
+ }
204
+ }}
205
+ onClueChange={(newProps) => {
206
+ if ("content" in newProps) {
207
+ this.onClueChange(
208
+ i,
209
+ newProps.content,
210
+ );
211
+ }
212
+ }}
213
+ // eslint-disable-next-line react/jsx-no-bind
214
+ onDelete={this.onDelete.bind(this, i)}
215
+ showDelete={this.props.choices.length >= 2}
216
+ />
217
+ ),
218
+ isNoneOfTheAbove: choice.isNoneOfTheAbove,
219
+ checked: choice.correct,
220
+ };
221
+ }, this)}
222
+ onChange={this.onChange}
223
+ />
224
+
225
+ <div className="add-choice-container">
226
+ <a
227
+ className="simple-button orange"
228
+ href="#"
229
+ // eslint-disable-next-line react/jsx-no-bind
230
+ onClick={this.addChoice.bind(this, false)}
231
+ >
232
+ <InlineIcon {...iconPlus} /> Add a choice{" "}
233
+ </a>
234
+
235
+ {!this.props.hasNoneOfTheAbove && (
236
+ <a
237
+ className="simple-button"
238
+ href="#"
239
+ // eslint-disable-next-line react/jsx-no-bind
240
+ onClick={this.addChoice.bind(this, true)}
241
+ >
242
+ <InlineIcon {...iconPlus} /> None of the above{" "}
243
+ </a>
244
+ )}
245
+ </div>
246
+ </div>
247
+ );
248
+ }
249
+
250
+ change: (...args: $ReadOnlyArray<mixed>) => $FlowFixMe = (...args) => {
251
+ // $FlowFixMe[incompatible-call]
252
+ return Changeable.change.apply(this, args);
253
+ };
254
+
255
+ onMultipleSelectChange: ($FlowFixMe) => $FlowFixMe = (allowMultiple) => {
256
+ allowMultiple = allowMultiple.multipleSelect;
257
+
258
+ const numCorrect = _.reduce(
259
+ this.props.choices,
260
+ function (memo, choice) {
261
+ return choice.correct ? memo + 1 : memo;
262
+ },
263
+ 0,
264
+ );
265
+
266
+ if (!allowMultiple && numCorrect > 1) {
267
+ const choices = _.map(this.props.choices, function (choice) {
268
+ return _.defaults(
269
+ {
270
+ correct: false,
271
+ },
272
+ choice,
273
+ );
274
+ });
275
+ this.props.onChange({
276
+ multipleSelect: allowMultiple,
277
+ choices: choices,
278
+ });
279
+ } else {
280
+ this.props.onChange({
281
+ multipleSelect: allowMultiple,
282
+ });
283
+ }
284
+ };
285
+
286
+ onCountChoicesChange: ($FlowFixMe) => void = (count) => {
287
+ count = count.countChoices;
288
+ this.props.onChange({countChoices: count});
289
+ };
290
+
291
+ onChange: ($FlowFixMe) => void = ({checked}) => {
292
+ const choices = _.map(this.props.choices, (choice, i) => {
293
+ return _.extend({}, choice, {
294
+ correct: checked[i],
295
+ content:
296
+ choice.isNoneOfTheAbove && !checked[i]
297
+ ? ""
298
+ : choice.content,
299
+ });
300
+ });
301
+
302
+ this.props.onChange({choices: choices});
303
+ };
304
+
305
+ onContentChange: ($FlowFixMe, $FlowFixMe) => void = (
306
+ choiceIndex,
307
+ newContent,
308
+ ) => {
309
+ const choices = this.props.choices.slice();
310
+ choices[choiceIndex] = _.extend({}, choices[choiceIndex], {
311
+ content: newContent,
312
+ });
313
+ this.props.onChange({choices: choices});
314
+ };
315
+
316
+ onClueChange: ($FlowFixMe, $FlowFixMe) => void = (choiceIndex, newClue) => {
317
+ const choices = this.props.choices.slice();
318
+ choices[choiceIndex] = _.extend({}, choices[choiceIndex], {
319
+ clue: newClue,
320
+ });
321
+ if (newClue === "") {
322
+ delete choices[choiceIndex].clue;
323
+ }
324
+ this.props.onChange({choices: choices});
325
+ };
326
+
327
+ onDelete: (number, $FlowFixMe) => void = (choiceIndex, e) => {
328
+ e.preventDefault();
329
+
330
+ const choices = this.props.choices.slice();
331
+ const deleted = choices[choiceIndex];
332
+
333
+ choices.splice(choiceIndex, 1);
334
+
335
+ this.props.onChange({
336
+ choices: choices,
337
+ hasNoneOfTheAbove:
338
+ this.props.hasNoneOfTheAbove && !deleted.isNoneOfTheAbove,
339
+ });
340
+ };
341
+
342
+ addChoice: (boolean, $FlowFixMe) => void = (noneOfTheAbove, e) => {
343
+ e.preventDefault();
344
+
345
+ const choices = this.props.choices.slice();
346
+ const newChoice = {isNoneOfTheAbove: noneOfTheAbove};
347
+ const addIndex =
348
+ choices.length - (this.props.hasNoneOfTheAbove ? 1 : 0);
349
+
350
+ choices.splice(addIndex, 0, newChoice);
351
+
352
+ this.props.onChange(
353
+ {
354
+ choices: choices,
355
+ hasNoneOfTheAbove:
356
+ noneOfTheAbove || this.props.hasNoneOfTheAbove,
357
+ },
358
+ () => {
359
+ // eslint-disable-next-line react/no-string-refs
360
+ this.refs[`choice-editor${addIndex}`].refs[
361
+ "content-editor"
362
+ ].focus();
363
+ },
364
+ );
365
+ };
366
+
367
+ setDisplayCount: (number) => void = (num) => {
368
+ this.props.onChange({displayCount: num});
369
+ };
370
+
371
+ focus: () => boolean = () => {
372
+ // eslint-disable-next-line react/no-string-refs
373
+ this.refs["choice-editor0"].refs["content-editor"].focus();
374
+ return true;
375
+ };
376
+
377
+ getSaveWarnings: () => $ReadOnlyArray<string> = () => {
378
+ if (!_.some(_.pluck(this.props.choices, "correct"))) {
379
+ return ["No choice is marked as correct."];
380
+ }
381
+ return [];
382
+ };
383
+
384
+ serialize: () => $FlowFixMe = () => {
385
+ return _.pick(
386
+ this.props,
387
+ "choices",
388
+ "randomize",
389
+ "multipleSelect",
390
+ "countChoices",
391
+ "displayCount",
392
+ "hasNoneOfTheAbove",
393
+ "deselectEnabled",
394
+ );
395
+ };
396
+ }
397
+
398
+ export default RadioEditor;
@@ -0,0 +1,127 @@
1
+ // @flow
2
+ import {components, Changeable, EditorJsonify} from "@khanacademy/perseus";
3
+ import PropTypes from "prop-types";
4
+ import * as React from "react";
5
+
6
+ const {NumberInput, TextInput} = components;
7
+
8
+ type Props = $FlowFixMe;
9
+
10
+ class ReactionDiagramWidgetEditor extends React.Component<Props> {
11
+ static propTypes = {
12
+ ...Changeable.propTypes,
13
+ rotationAngle: PropTypes.arrayOf(PropTypes.number),
14
+ // eslint-disable-next-line react/forbid-prop-types
15
+ separators: PropTypes.arrayOf(PropTypes.object),
16
+ smiles: PropTypes.arrayOf(PropTypes.string),
17
+ };
18
+
19
+ static widgetName: "reaction-diagram" = "reaction-diagram";
20
+
21
+ static defaultProps: Props = {
22
+ smiles: ["", ""],
23
+ rotationAngle: [0, 0],
24
+ separators: [{type: "right", topText: "", bottomText: ""}],
25
+ };
26
+
27
+ change: ($FlowFixMe, $FlowFixMe, $FlowFixMe) => $FlowFixMe = (...args) => {
28
+ return Changeable.change.apply(this, args);
29
+ };
30
+
31
+ updateMolecule: (number) => ($FlowFixMe) => void = (idx) => {
32
+ return function (newValue) {
33
+ const newSmiles = [...this.props.smiles];
34
+ newSmiles[idx] = newValue;
35
+ this.change({smiles: newSmiles});
36
+ }.bind(this);
37
+ };
38
+
39
+ updateRotation: (number) => ($FlowFixMe) => void = (idx) => {
40
+ return function (newValue) {
41
+ const newRot = [...this.props.rotationAngle];
42
+ newRot[idx] = newValue;
43
+ this.change({rotationAngle: newRot});
44
+ }.bind(this);
45
+ };
46
+
47
+ updateSeparators: (number, string) => ($FlowFixMe) => void = (
48
+ idx,
49
+ propName,
50
+ ) => {
51
+ return (newValue) => {
52
+ const newSep = this.props.separators.map((sep) => {
53
+ return {...sep};
54
+ });
55
+ newSep[idx][propName] = newValue;
56
+ this.change({separators: newSep});
57
+ };
58
+ };
59
+
60
+ serialize: () => $FlowFixMe = () => {
61
+ return EditorJsonify.serialize.call(this);
62
+ };
63
+
64
+ render(): React.Node {
65
+ // TODO(colin): use styling instead of &nbsp hacks.
66
+ return (
67
+ <div>
68
+ <div>
69
+ <label>
70
+ LHS SMILES:&nbsp;
71
+ <TextInput
72
+ onChange={this.updateMolecule(0)}
73
+ value={this.props.smiles[0]}
74
+ />
75
+ </label>
76
+ </div>
77
+ <div>
78
+ <label>
79
+ LHS Rotation (deg):&nbsp;
80
+ <NumberInput
81
+ onChange={this.updateRotation(0)}
82
+ value={this.props.rotationAngle[0]}
83
+ />
84
+ </label>
85
+ </div>
86
+ <div>
87
+ <label>
88
+ RHS SMILES:&nbsp;
89
+ <TextInput
90
+ onChange={this.updateMolecule(1)}
91
+ value={this.props.smiles[1]}
92
+ />
93
+ </label>
94
+ </div>
95
+ <div>
96
+ <label>
97
+ RHS Rotation (deg):&nbsp;
98
+ <NumberInput
99
+ onChange={this.updateRotation(1)}
100
+ value={this.props.rotationAngle[1]}
101
+ />
102
+ </label>
103
+ </div>
104
+ <div>
105
+ <label>
106
+ Top of arrow text:&nbsp;
107
+ <TextInput
108
+ onChange={this.updateSeparators(0, "topText")}
109
+ value={this.props.separators[0].topText}
110
+ />
111
+ </label>
112
+ </div>
113
+ <div>
114
+ <label>
115
+ Bottom of arrow text:&nbsp;
116
+ <TextInput
117
+ onChange={this.updateSeparators(0, "bottomText")}
118
+ value={this.props.separators[0].bottomText}
119
+ />
120
+ </label>
121
+ </div>
122
+ </div>
123
+ );
124
+ }
125
+ }
126
+
127
+ export default ReactionDiagramWidgetEditor;