@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,181 @@
1
+ // @flow
2
+ /**
3
+ * Controlled component to display question image and create markers to
4
+ * label image with answers from list of choices.
5
+ */
6
+
7
+ import {Util, Dependencies, globalStyles} from "@khanacademy/perseus";
8
+ import {StyleSheet, css} from "aphrodite";
9
+ import * as React from "react";
10
+
11
+ import Marker from "./marker.jsx";
12
+
13
+ import type {MarkerType} from "@khanacademy/perseus";
14
+
15
+ const {colors, typography} = globalStyles;
16
+
17
+ type QuestionMarkersProps = {|
18
+ // The list of possible answers in a specific order.
19
+ choices: $ReadOnlyArray<string>,
20
+
21
+ // The question image properties.
22
+ imageUrl: string,
23
+ imageWidth: number,
24
+ imageHeight: number,
25
+
26
+ // The list of markers placed on the question image.
27
+ markers: $ReadOnlyArray<MarkerType>,
28
+
29
+ // Callback for when any of markers change.
30
+ onChange: (markers: $ReadOnlyArray<MarkerType>) => void,
31
+ |};
32
+
33
+ export default class QuestionMarkers extends React.Component<QuestionMarkersProps> {
34
+ // eslint-disable-next-line flowtype/no-mutable-array
35
+ _markers: Array<?Marker> = [];
36
+
37
+ openDropdownForMarkerIndices(indices: $ReadOnlyArray<number>) {
38
+ // Open answer selection dropdown for each of the specified markers.
39
+ indices.forEach((index) => {
40
+ if (this._markers[index]) {
41
+ this._markers[index].openDropdown();
42
+ }
43
+ });
44
+ }
45
+
46
+ handleImageDoubleClick: (e: SyntheticMouseEvent<>) => void = (
47
+ e: SyntheticMouseEvent<>,
48
+ ) => {
49
+ e.preventDefault();
50
+
51
+ const rect =
52
+ ((e.currentTarget: any): HTMLElement).getBoundingClientRect();
53
+
54
+ // Calculate marker position on the question image as percentage offset
55
+ // rounded to one decimal place.
56
+ const x =
57
+ Math.round(((e.clientX - rect.left) / rect.width) * 1000) / 10;
58
+ const y =
59
+ Math.round(((e.clientY - rect.top) / rect.height) * 1000) / 10;
60
+
61
+ const {markers, onChange} = this.props;
62
+
63
+ // Add the new marker to the end of the list.
64
+ onChange([
65
+ ...markers,
66
+ {
67
+ answers: [],
68
+ label: "",
69
+ x,
70
+ y,
71
+ },
72
+ ]);
73
+ };
74
+
75
+ render(): React.Element<"div"> {
76
+ const {choices, imageUrl, imageWidth, imageHeight, markers, onChange} =
77
+ this.props;
78
+
79
+ const staticUrl = Dependencies.getDependencies().staticUrl;
80
+
81
+ // TODO(michaelpolyak): If a Graphie image is specified, only its SVG
82
+ // layer will be rendered, as when rendering the full Graphie its
83
+ // dynamic content may overflow the widget editor, CP-253
84
+ return (
85
+ <div>
86
+ <div className={css(styles.title)}>Markers</div>
87
+
88
+ <div className={css(styles.subtitle)}>
89
+ {imageUrl ? (
90
+ <span>
91
+ Double-click on the image to add a marker.
92
+ <br />
93
+ <br />
94
+ Markers are read by screen readers in the order that
95
+ you add them here, so add in a logical order for the
96
+ learner (e.g. sequentially, clockwise). You can test
97
+ order by using keyboard tabbing.
98
+ </span>
99
+ ) : (
100
+ "Upload an image to place markers."
101
+ )}
102
+ </div>
103
+
104
+ {imageUrl && (
105
+ <div
106
+ className={css(styles.markersCanvas)}
107
+ style={{
108
+ maxWidth: imageWidth,
109
+ maxHeight: imageHeight,
110
+ }}
111
+ >
112
+ <img
113
+ alt=""
114
+ className={css(styles.image)}
115
+ src={staticUrl(Util.getRealImageUrl(imageUrl))}
116
+ onDoubleClick={this.handleImageDoubleClick}
117
+ />
118
+
119
+ {markers.map((marker, index) => (
120
+ <Marker
121
+ {...marker}
122
+ choices={choices}
123
+ key={`${marker.x}.${marker.y}`}
124
+ // Update marker in the list.
125
+ onChange={(marker) =>
126
+ onChange([
127
+ ...markers.slice(0, index),
128
+ marker,
129
+ ...markers.slice(index + 1),
130
+ ])
131
+ }
132
+ // Remove marker from the list.
133
+ onRemove={() =>
134
+ onChange([
135
+ ...markers.slice(0, index),
136
+ ...markers.slice(index + 1),
137
+ ])
138
+ }
139
+ ref={(node) => (this._markers[index] = node)}
140
+ />
141
+ ))}
142
+ </div>
143
+ )}
144
+ </div>
145
+ );
146
+ }
147
+ }
148
+
149
+ const styles = StyleSheet.create({
150
+ title: {
151
+ ...typography.bodyXsmallBold,
152
+
153
+ marginBottom: 6,
154
+
155
+ color: colors.gray17,
156
+ },
157
+
158
+ subtitle: {
159
+ ...typography.labelSmall,
160
+
161
+ marginBottom: 12,
162
+
163
+ color: colors.gray68,
164
+ },
165
+
166
+ markersCanvas: {
167
+ position: "relative",
168
+
169
+ border: "solid 1px rgba(33, 36, 44, 0.16)",
170
+ },
171
+
172
+ image: {
173
+ // This makes `markersCanvas` have the same height as the image,
174
+ // enabling to place markers precisely as they're positioned using
175
+ // percentage offsets.
176
+ display: "block",
177
+
178
+ // Contain question image to the widget editor.
179
+ maxWidth: "100%",
180
+ },
181
+ });
@@ -0,0 +1,86 @@
1
+ // @flow
2
+ /**
3
+ * Controlled component for selecting the question image, for answer labeling.
4
+ */
5
+
6
+ import {globalStyles} from "@khanacademy/perseus";
7
+ import Button from "@khanacademy/wonder-blocks-button";
8
+ import {StyleSheet, css} from "aphrodite";
9
+ import * as React from "react";
10
+
11
+ import FormWrappedTextField from "../../components/form-wrapped-text-field.jsx";
12
+
13
+ const {colors, typography} = globalStyles;
14
+
15
+ type SelectImageProps = {
16
+ // Callback for when image URL is changed.
17
+ onChange: (url: string) => void,
18
+ // The selected image URL.
19
+ url: string,
20
+ ...
21
+ };
22
+
23
+ const SelectImage = ({
24
+ onChange,
25
+ url,
26
+ }: SelectImageProps): React.Element<"div"> => (
27
+ <div>
28
+ <div className={css(styles.title)}>Image</div>
29
+
30
+ <div className={css(styles.components)}>
31
+ <FormWrappedTextField
32
+ placeholder="URL"
33
+ grow={1}
34
+ onChange={(e) => onChange(e.target.value)}
35
+ value={url}
36
+ />
37
+
38
+ <div className={css(styles.spacer)} />
39
+
40
+ <Button
41
+ // TODO(michaelpolyak): Design calls for uploading an image from
42
+ // the widget. This functionality already exists in
43
+ // `perseus-one-package/item-controls.jsx`, it's not obvious if
44
+ // it can be triggered from within the widget, or requires
45
+ // a similar implementation locally, in which case consider
46
+ // refactoring `perseus-admin-package/image-upload-dialog.jsx`
47
+ // as common utility, CP-118
48
+ disabled={!url}
49
+ aria-label={
50
+ url
51
+ ? ""
52
+ : "Not implemented. Use the 'Add Image' button in " +
53
+ "the editor to upload image, then copy the URL here."
54
+ }
55
+ onClick={() => onChange("")}
56
+ style={styles.btn}
57
+ >
58
+ {url ? "Remove" : "Upload"}
59
+ </Button>
60
+ </div>
61
+ </div>
62
+ );
63
+
64
+ const styles = StyleSheet.create({
65
+ title: {
66
+ ...typography.bodyXsmallBold,
67
+
68
+ marginBottom: 6,
69
+
70
+ color: colors.gray17,
71
+ },
72
+
73
+ components: {
74
+ display: "flex",
75
+ },
76
+
77
+ spacer: {
78
+ width: 16,
79
+ },
80
+
81
+ btn: {
82
+ minWidth: 90,
83
+ },
84
+ });
85
+
86
+ export default SelectImage;
@@ -0,0 +1,257 @@
1
+ // @flow
2
+ /**
3
+ * Direct image labeling widget editor.
4
+ *
5
+ * Label on image widget enables creating more natural, conceptual questions
6
+ * that involve the use of images, and enable learners to demonstrate their
7
+ * knowledge by directly interacting with the image.
8
+ */
9
+
10
+ import {EditorJsonify, Util} from "@khanacademy/perseus";
11
+ import {StyleSheet, css} from "aphrodite";
12
+ import * as React from "react";
13
+
14
+ import FormWrappedTextField from "../components/form-wrapped-text-field.jsx";
15
+
16
+ import AnswerChoices from "./label-image/answer-choices.jsx";
17
+ import Behavior from "./label-image/behavior.jsx";
18
+ import QuestionMarkers from "./label-image/question-markers.jsx";
19
+ import SelectImage from "./label-image/select-image.jsx";
20
+
21
+ import type {MarkerType} from "@khanacademy/perseus";
22
+
23
+ type LabelImageEditorProps = {|
24
+ // List of answer choices to label question image with.
25
+ choices: $ReadOnlyArray<string>,
26
+
27
+ // The question image properties.
28
+ imageAlt: string,
29
+ imageUrl: string,
30
+ imageWidth: number,
31
+ imageHeight: number,
32
+
33
+ // The list of label markers on the question image.
34
+ markers: $ReadOnlyArray<MarkerType>,
35
+
36
+ // Whether multiple answer choices may be selected for markers.
37
+ multipleAnswers: boolean,
38
+
39
+ // Whether to hide answer choices from user instructions.
40
+ hideChoicesFromInstructions: boolean,
41
+
42
+ // Callback for when a widget prop is changed.
43
+ onChange: (options: Object) => void,
44
+ |};
45
+
46
+ class LabelImageEditor extends React.Component<LabelImageEditorProps> {
47
+ _questionMarkers: ?QuestionMarkers;
48
+
49
+ static defaultProps: {|
50
+ choices: $ReadOnlyArray<any>,
51
+ hideChoicesFromInstructions: boolean,
52
+ imageAlt: string,
53
+ imageHeight: number,
54
+ imageUrl: string,
55
+ imageWidth: number,
56
+ markers: $ReadOnlyArray<any>,
57
+ multipleAnswers: boolean,
58
+ |} = {
59
+ choices: [],
60
+ imageAlt: "",
61
+ imageUrl: "",
62
+ imageWidth: 0,
63
+ imageHeight: 0,
64
+ markers: [],
65
+ multipleAnswers: false,
66
+ hideChoicesFromInstructions: false,
67
+ };
68
+
69
+ static widgetName: string = "label-image";
70
+
71
+ componentDidUpdate(prevProps: LabelImageEditorProps) {
72
+ const coordsToMarkers = {};
73
+
74
+ prevProps.markers.forEach(
75
+ (marker) => (coordsToMarkers[`${marker.x}.${marker.y}`] = marker),
76
+ );
77
+
78
+ // Find the newly added marker indices.
79
+ const newIndices = this.props.markers
80
+ .map((marker, index) =>
81
+ // eslint-disable-next-line no-prototype-builtins
82
+ coordsToMarkers.hasOwnProperty(`${marker.x}.${marker.y}`)
83
+ ? -1
84
+ : index,
85
+ )
86
+ .filter((index) => index !== -1);
87
+
88
+ // Automatically reveal their dropdowns as a prompt to the content
89
+ // creator to select answers and set the ARIA label.
90
+ if (newIndices.length && this._questionMarkers) {
91
+ this._questionMarkers.openDropdownForMarkerIndices(newIndices);
92
+ }
93
+ }
94
+
95
+ getSaveWarnings: () => $ReadOnlyArray<any | string> = () => {
96
+ const {choices, imageAlt, imageUrl, markers} = this.props;
97
+
98
+ const warnings = [];
99
+
100
+ if (choices.length < 2) {
101
+ warnings.push("Question requires at least two answer choices");
102
+ }
103
+
104
+ if (!imageUrl) {
105
+ warnings.push("Image is not specified for question");
106
+ } else if (!imageAlt) {
107
+ warnings.push("Question image has no alt text");
108
+ }
109
+
110
+ if (!markers.length) {
111
+ warnings.push("Question has no markers, to label answers on image");
112
+ } else {
113
+ let numNoAnswers = 0;
114
+ let numNoLabels = 0;
115
+
116
+ for (const marker of markers) {
117
+ if (!marker.answers.length) {
118
+ numNoAnswers++;
119
+ }
120
+
121
+ if (!marker.label) {
122
+ numNoLabels++;
123
+ }
124
+ }
125
+
126
+ if (numNoAnswers) {
127
+ warnings.push(
128
+ `Question has ${numNoAnswers} markers with no ` +
129
+ "answers selected",
130
+ );
131
+ }
132
+
133
+ if (numNoLabels) {
134
+ warnings.push(
135
+ `Question has ${numNoLabels} markers with no ` +
136
+ "ARIA label",
137
+ );
138
+ }
139
+ }
140
+
141
+ return warnings;
142
+ };
143
+
144
+ serialize(): any {
145
+ return EditorJsonify.serialize.call(this);
146
+ }
147
+
148
+ handleImageChange: (url: string) => void = (url: string) => {
149
+ this.props.onChange({
150
+ imageUrl: url,
151
+ // Initially reset image size when URL is changed so it can be later
152
+ // measured.
153
+ imageWidth: 0,
154
+ imageHeight: 0,
155
+ });
156
+
157
+ if (url) {
158
+ Util.getImageSize(url, (width, height) => {
159
+ this.props.onChange({
160
+ imageWidth: width,
161
+ imageHeight: height,
162
+ });
163
+ });
164
+ }
165
+ };
166
+
167
+ handleAltChange: (alt: string) => void = (alt: string) => {
168
+ this.props.onChange({imageAlt: alt});
169
+ };
170
+
171
+ handleChoicesChange: (choices: $ReadOnlyArray<string>) => void = (
172
+ choices: $ReadOnlyArray<string>,
173
+ ) => {
174
+ this.props.onChange({choices});
175
+ };
176
+
177
+ handleMarkersChange: (markers: $ReadOnlyArray<MarkerType>) => void = (
178
+ markers: $ReadOnlyArray<MarkerType>,
179
+ ) => {
180
+ this.props.onChange({markers});
181
+ };
182
+
183
+ handleBehaviorChange: (options: any) => void = (options: Object) => {
184
+ this.props.onChange(options);
185
+ };
186
+
187
+ render(): React.Element<"div"> {
188
+ const {
189
+ choices,
190
+ imageAlt,
191
+ imageUrl,
192
+ imageWidth,
193
+ imageHeight,
194
+ markers,
195
+ multipleAnswers,
196
+ hideChoicesFromInstructions,
197
+ } = this.props;
198
+
199
+ const imageSelected = imageUrl && imageWidth > 0 && imageHeight > 0;
200
+
201
+ return (
202
+ <div>
203
+ <SelectImage onChange={this.handleImageChange} url={imageUrl} />
204
+
205
+ <div className={css(styles.smallSpacer)} />
206
+
207
+ {imageSelected && (
208
+ <FormWrappedTextField
209
+ placeholder="Alt text (for screen readers)"
210
+ onChange={(e) => this.handleAltChange(e.target.value)}
211
+ value={imageAlt}
212
+ width="100%"
213
+ />
214
+ )}
215
+
216
+ <div className={css(styles.largeSpacer)} />
217
+
218
+ <QuestionMarkers
219
+ choices={choices}
220
+ imageUrl={imageSelected ? imageUrl : ""}
221
+ imageWidth={imageWidth}
222
+ imageHeight={imageHeight}
223
+ markers={markers}
224
+ onChange={this.handleMarkersChange}
225
+ ref={(node) => (this._questionMarkers = node)}
226
+ />
227
+
228
+ <div className={css(styles.largeSpacer)} />
229
+
230
+ <AnswerChoices
231
+ choices={choices}
232
+ onChange={this.handleChoicesChange}
233
+ />
234
+
235
+ <div className={css(styles.largeSpacer)} />
236
+
237
+ <Behavior
238
+ multipleAnswers={multipleAnswers}
239
+ hideChoicesFromInstructions={hideChoicesFromInstructions}
240
+ onChange={this.handleBehaviorChange}
241
+ />
242
+ </div>
243
+ );
244
+ }
245
+ }
246
+
247
+ const styles = StyleSheet.create({
248
+ largeSpacer: {
249
+ height: 32,
250
+ },
251
+
252
+ smallSpacer: {
253
+ height: 16,
254
+ },
255
+ });
256
+
257
+ export default LabelImageEditor;