@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,322 @@
1
+ /* eslint-disable react/prop-types */
2
+ /* eslint-disable react/no-unsafe */
3
+ /* eslint-disable react/sort-comp */
4
+ // @flow
5
+ import {
6
+ components,
7
+ interactiveSizes,
8
+ InteractiveGraphWidget,
9
+ SizingUtils,
10
+ Util,
11
+ } from "@khanacademy/perseus";
12
+ import createReactClass from "create-react-class";
13
+ import * as React from "react";
14
+ import _ from "underscore";
15
+
16
+ import GraphSettings from "../components/graph-settings.jsx";
17
+
18
+ const {InfoTip} = components;
19
+ const {containerSizeClass, getInteractiveBoxFromSizeClass} = SizingUtils;
20
+ const DeprecationMixin = Util.DeprecationMixin;
21
+ const InteractiveGraph = InteractiveGraphWidget.widget;
22
+
23
+ type InteractiveGraphProps = React.ElementConfig<typeof InteractiveGraph>;
24
+
25
+ const defaultBackgroundImage = {
26
+ url: null,
27
+ };
28
+
29
+ const deprecatedProps = {
30
+ showGraph: function (props) {
31
+ return {markings: props.showGraph ? "graph" : "none"};
32
+ },
33
+ };
34
+
35
+ const InteractiveGraphEditor: $FlowFixMe = createReactClass({
36
+ displayName: "InteractiveGraphEditor",
37
+ className: "perseus-widget-interactive-graph",
38
+
39
+ statics: {
40
+ widgetName: "interactive-graph",
41
+ },
42
+
43
+ getDefaultProps: function () {
44
+ return {
45
+ labels: ["x", "y"],
46
+ range: [
47
+ [-10, 10],
48
+ [-10, 10],
49
+ ],
50
+ step: [1, 1],
51
+ valid: true,
52
+ backgroundImage: defaultBackgroundImage,
53
+ markings: "graph",
54
+ showProtractor: false,
55
+ showRuler: false,
56
+ showTooltips: false,
57
+ rulerLabel: "",
58
+ rulerTicks: 10,
59
+ correct: {
60
+ type: "linear",
61
+ coords: null,
62
+ },
63
+ };
64
+ },
65
+
66
+ // TODO(jack): Use versioning instead of DeprecationMixin
67
+ deprecatedProps: deprecatedProps,
68
+
69
+ // TODO(jangmi, CP-3288): Remove usage of `UNSAFE_componentWillMount`
70
+ UNSAFE_componentWillMount() {
71
+ // $FlowFixMe[object-this-reference]
72
+ DeprecationMixin.UNSAFE_componentWillMount.call(this);
73
+ },
74
+
75
+ render: function () {
76
+ let graph;
77
+ let equationString;
78
+
79
+ const gridStep =
80
+ this.props.gridStep ||
81
+ Util.getGridStep(
82
+ this.props.range,
83
+ this.props.step,
84
+ interactiveSizes.defaultBoxSize,
85
+ );
86
+ const snapStep =
87
+ this.props.snapStep || Util.snapStepFromGridStep(gridStep);
88
+
89
+ const sizeClass = containerSizeClass.SMALL;
90
+ if (this.props.valid === true) {
91
+ // TODO(aria): send these down all at once
92
+ const graphProps = {
93
+ ref: "graph",
94
+ box: this.props.box,
95
+ range: this.props.range,
96
+ labels: this.props.labels,
97
+ step: this.props.step,
98
+ gridStep: gridStep,
99
+ snapStep: snapStep,
100
+ graph: this.props.correct,
101
+ backgroundImage: this.props.backgroundImage,
102
+ markings: this.props.markings,
103
+ showProtractor: this.props.showProtractor,
104
+ showRuler: this.props.showRuler,
105
+ rulerLabel: this.props.rulerLabel,
106
+ rulerTicks: this.props.rulerTicks,
107
+ trackInteraction: function () {},
108
+ onChange: (newProps: InteractiveGraphProps) => {
109
+ let correct = this.props.correct;
110
+ // $FlowFixMe[incompatible-use]
111
+ if (correct.type === newProps.graph.type) {
112
+ correct = _.extend({}, correct, newProps.graph);
113
+ } else {
114
+ // Clear options from previous graph
115
+ correct = newProps.graph;
116
+ }
117
+ this.props.onChange({correct: correct});
118
+ },
119
+ };
120
+ graph = (
121
+ // There are a bunch of props that renderer.jsx passes to widgets via
122
+ // getWidgetProps() and widget-container.jsx that the editors don't
123
+ // bother passing.
124
+ // $FlowFixMe[prop-missing]
125
+ <InteractiveGraph
126
+ {...graphProps}
127
+ containerSizeClass={sizeClass}
128
+ apiOptions={{
129
+ ...this.props.apiOptions,
130
+ isMobile: false,
131
+ }}
132
+ />
133
+ );
134
+ // TODO(kevinb): Update getEquationString to only accept the data it actually
135
+ // needs to compute the equation string.
136
+ // $FlowFixMe[prop-missing]
137
+ equationString = InteractiveGraph.getEquationString(graphProps);
138
+ } else {
139
+ graph = <div className="perseus-error">{this.props.valid}</div>;
140
+ }
141
+
142
+ return (
143
+ <div className="perseus-widget-interactive-graph">
144
+ <div>
145
+ Correct answer{" "}
146
+ <InfoTip>
147
+ <p>
148
+ Graph the correct answer in the graph below and
149
+ ensure the equation or point coordinates displayed
150
+ represent the correct answer.
151
+ </p>
152
+ </InfoTip>{" "}
153
+ : {equationString}
154
+ </div>
155
+
156
+ <GraphSettings
157
+ box={getInteractiveBoxFromSizeClass(sizeClass)}
158
+ range={this.props.range}
159
+ labels={this.props.labels}
160
+ step={this.props.step}
161
+ gridStep={gridStep}
162
+ snapStep={snapStep}
163
+ valid={this.props.valid}
164
+ backgroundImage={this.props.backgroundImage}
165
+ markings={this.props.markings}
166
+ showProtractor={this.props.showProtractor}
167
+ showRuler={this.props.showRuler}
168
+ showTooltips={this.props.showTooltips}
169
+ rulerLabel={this.props.rulerLabel}
170
+ rulerTicks={this.props.rulerTicks}
171
+ onChange={this.props.onChange}
172
+ />
173
+
174
+ {this.props.correct.type === "polygon" && (
175
+ <div className="type-settings">
176
+ <label>
177
+ {" "}
178
+ Student answer must{" "}
179
+ <select
180
+ value={this.props.correct.match}
181
+ onChange={this.changeMatchType}
182
+ >
183
+ <option value="exact">match exactly</option>
184
+ <option value="congruent">be congruent</option>
185
+ <option value="approx">
186
+ be approximately congruent
187
+ </option>
188
+ <option value="similar">be similar</option>
189
+ </select>
190
+ </label>
191
+ <InfoTip>
192
+ <ul>
193
+ <li>
194
+ <p>
195
+ <b>Match Exactly:</b> Match exactly in
196
+ size, orientation, and location on the
197
+ grid even if it is not shown in the
198
+ background.
199
+ </p>
200
+ </li>
201
+ <li>
202
+ <p>
203
+ <b>Be Congruent:</b> Be congruent in
204
+ size and shape, but can be located
205
+ anywhere on the grid.
206
+ </p>
207
+ </li>
208
+ <li>
209
+ <p>
210
+ <b>Be Approximately Congruent:</b> Be
211
+ exactly similar, and congruent in size
212
+ and shape to within 0.1 units, but can
213
+ be located anywhere on the grid.{" "}
214
+ <em>
215
+ Use this with snapping to angle
216
+ measure.
217
+ </em>
218
+ </p>
219
+ </li>
220
+ <li>
221
+ <p>
222
+ <b>Be Similar:</b> Be similar with
223
+ matching interior angles, and side
224
+ measures that are matching or a multiple
225
+ of the correct side measures. The figure
226
+ can be located anywhere on the grid.
227
+ </p>
228
+ </li>
229
+ </ul>
230
+ </InfoTip>
231
+ </div>
232
+ )}
233
+ {this.props.correct.type === "angle" && (
234
+ <div className="type-settings">
235
+ <div>
236
+ <label>
237
+ {" "}
238
+ Student answer must{" "}
239
+ <select
240
+ value={this.props.correct.match}
241
+ onChange={this.changeMatchType}
242
+ >
243
+ <option value="exact">match exactly</option>
244
+ <option value="congruent">
245
+ be congruent
246
+ </option>
247
+ </select>
248
+ </label>
249
+ <InfoTip>
250
+ <p>
251
+ Congruency requires only that the angle
252
+ measures are the same. An exact match
253
+ implies congruency, but also requires that
254
+ the angles have the same orientation and
255
+ that the vertices are in the same position.
256
+ </p>
257
+ </InfoTip>
258
+ </div>
259
+ </div>
260
+ )}
261
+ {graph}
262
+ </div>
263
+ );
264
+ },
265
+
266
+ changeMatchType: function (e) {
267
+ const correct = _.extend({}, this.props.correct, {
268
+ match: e.target.value,
269
+ });
270
+ this.props.onChange({correct: correct});
271
+ },
272
+
273
+ serialize: function () {
274
+ const json = _.pick(
275
+ this.props,
276
+ "step",
277
+ "backgroundImage",
278
+ "markings",
279
+ "labels",
280
+ "showProtractor",
281
+ "showRuler",
282
+ "showTooltips",
283
+ "rulerLabel",
284
+ "rulerTicks",
285
+ "range",
286
+ "gridStep",
287
+ "snapStep",
288
+ );
289
+
290
+ // eslint-disable-next-line react/no-string-refs
291
+ const graph = this.refs.graph;
292
+ if (graph) {
293
+ const correct = graph && graph.getUserInput();
294
+ _.extend(json, {
295
+ graph: {type: correct.type},
296
+ correct: correct,
297
+ });
298
+
299
+ _.each(
300
+ [
301
+ "allowReflexAngles",
302
+ "angleOffsetDeg",
303
+ "numPoints",
304
+ "numSides",
305
+ "numSegments",
306
+ "showAngles",
307
+ "showSides",
308
+ "snapTo",
309
+ "snapDegrees",
310
+ ],
311
+ function (key) {
312
+ if (_.has(correct, key)) {
313
+ json.graph[key] = correct[key];
314
+ }
315
+ },
316
+ );
317
+ }
318
+ return json;
319
+ },
320
+ });
321
+
322
+ export default InteractiveGraphEditor;
@@ -0,0 +1,81 @@
1
+ // @flow
2
+ import {action} from "@storybook/addon-actions";
3
+ import {StyleSheet, css} from "aphrodite";
4
+ import * as React from "react";
5
+
6
+ import AnswerChoices from "../answer-choices.jsx";
7
+
8
+ type StoryArgs = {||};
9
+
10
+ type Story = {|
11
+ title: string,
12
+ |};
13
+
14
+ export default ({
15
+ title: "Perseus/Editor/Widgets/Label Image/Answer Choices",
16
+ }: Story);
17
+
18
+ const styles = StyleSheet.create({
19
+ wrapper: {
20
+ // The maximum width of a widget in the editor.
21
+ width: 338,
22
+ },
23
+ });
24
+
25
+ const Wrapper = (props: $FlowFixMe) => {
26
+ return (
27
+ <div className={css(styles.wrapper)}>
28
+ <AnswerChoices
29
+ {...props}
30
+ onChange={(...args) => {
31
+ action("onChange")(...args);
32
+ props.onChange(...args);
33
+ }}
34
+ />
35
+ </div>
36
+ );
37
+ };
38
+
39
+ class WithState extends React.Component<
40
+ {||},
41
+ {|choices: $ReadOnlyArray<string>|},
42
+ > {
43
+ state = {
44
+ choices: [],
45
+ };
46
+
47
+ render(): React.Node {
48
+ const {choices} = this.state;
49
+
50
+ return (
51
+ <Wrapper
52
+ choices={choices}
53
+ onChange={(choices) => this.setState({choices})}
54
+ />
55
+ );
56
+ }
57
+ }
58
+
59
+ export const EmptyNonInteractive = (args: StoryArgs): React.Node => {
60
+ const props = {choices: [], onChange: (...args) => {}};
61
+ return <Wrapper {...props} />;
62
+ };
63
+
64
+ export const FilledNonInteractive = (args: StoryArgs): React.Node => {
65
+ const props = {
66
+ choices: [
67
+ "Lamborghini",
68
+ "BMW",
69
+ "Volkswagen",
70
+ "Fiat",
71
+ "Porsche",
72
+ "Ferrari",
73
+ ],
74
+ onChange: (...args) => {},
75
+ };
76
+ return <Wrapper {...props} />;
77
+ };
78
+
79
+ export const Interactive = (args: StoryArgs): React.Node => {
80
+ return <WithState />;
81
+ };
@@ -0,0 +1,32 @@
1
+ // @flow
2
+ import {action} from "@storybook/addon-actions";
3
+ import * as React from "react";
4
+
5
+ import Behavior from "../behavior.jsx";
6
+
7
+ type StoryArgs = {||};
8
+
9
+ type Story = {|
10
+ title: string,
11
+ |};
12
+
13
+ export default ({
14
+ title: "Perseus/Editor/Widgets/Label Image/Behavior",
15
+ }: Story);
16
+
17
+ export const Default = (args: StoryArgs): React.Node => {
18
+ const [state, setState] = React.useState({
19
+ multipleAnswers: false,
20
+ hideChoicesFromInstructions: false,
21
+ });
22
+
23
+ const props = {
24
+ multipleAnswers: state.multipleAnswers,
25
+ hideChoicesFromInstructions: state.hideChoicesFromInstructions,
26
+ onChange: (newState) => {
27
+ action("onChange")(newState);
28
+ setState({...state, ...newState});
29
+ },
30
+ };
31
+ return <Behavior {...props} />;
32
+ };
@@ -0,0 +1,76 @@
1
+ // @flow
2
+ import {action} from "@storybook/addon-actions";
3
+ import {StyleSheet, css} from "aphrodite";
4
+ import * as React from "react";
5
+
6
+ import Marker from "../marker.jsx";
7
+
8
+ type StoryArgs = {||};
9
+
10
+ type Story = {|
11
+ title: string,
12
+ |};
13
+
14
+ export default ({
15
+ title: "Perseus/Editor/Widgets/Label Image/Marker",
16
+ }: Story);
17
+
18
+ const styles = StyleSheet.create({
19
+ wrapper: {
20
+ // Push the component down as the popover that displays the choices
21
+ // doesn't properly pop over everything.
22
+ marginTop: 150,
23
+
24
+ position: "relative",
25
+
26
+ width: 32,
27
+ height: 32,
28
+ },
29
+ });
30
+
31
+ const Wrapper = (props) => (
32
+ <div className={css(styles.wrapper)}>
33
+ <Marker {...props} />
34
+ </div>
35
+ );
36
+
37
+ export const Empty = (args: StoryArgs): React.Node => {
38
+ const props = {
39
+ answers: [],
40
+ choices: [],
41
+ label: "",
42
+ onChange: (...args) => {
43
+ action("onChange")(...args);
44
+ },
45
+ onRemove: (...args) => {
46
+ action("onRemove")(...args);
47
+ },
48
+ x: 50,
49
+ y: 50,
50
+ };
51
+ return <Wrapper {...props} />;
52
+ };
53
+
54
+ export const WithAnswers = (args: StoryArgs): React.Node => {
55
+ const props = {
56
+ answers: ["BMW", "Ferrari"],
57
+ choices: [
58
+ "Lamborghini",
59
+ "BMW",
60
+ "Volkswagen",
61
+ "Fiat",
62
+ "Porsche",
63
+ "Ferrari",
64
+ ],
65
+ label: "Automotive",
66
+ onChange: (...args) => {
67
+ action("onChange")(...args);
68
+ },
69
+ onRemove: (...args) => {
70
+ action("onRemove")(...args);
71
+ },
72
+ x: 50,
73
+ y: 50,
74
+ };
75
+ return <Wrapper {...props} />;
76
+ };
@@ -0,0 +1,106 @@
1
+ // @flow
2
+ import {StyleSheet, css} from "aphrodite";
3
+ import * as React from "react";
4
+
5
+ import QuestionMarkers from "../question-markers.jsx";
6
+
7
+ import type {MarkerType} from "@khanacademy/perseus";
8
+
9
+ type StoryArgs = {||};
10
+
11
+ type Story = {|
12
+ title: string,
13
+ |};
14
+
15
+ export default ({
16
+ title: "Perseus/Editor/Widgets/Label Image/Question Markers",
17
+ }: Story);
18
+
19
+ const styles = StyleSheet.create({
20
+ wrapper: {
21
+ // The maximum width of a widget in the editor.
22
+ width: 338,
23
+ },
24
+ });
25
+
26
+ const Wrapper = (props) => (
27
+ <div className={css(styles.wrapper)}>
28
+ <QuestionMarkers {...props} />
29
+ </div>
30
+ );
31
+
32
+ class WithState extends React.Component<
33
+ {||},
34
+ {|markers: $ReadOnlyArray<MarkerType>|},
35
+ > {
36
+ state = {
37
+ markers: [
38
+ {
39
+ answers: [],
40
+ label: "",
41
+ x: 50,
42
+ y: 50,
43
+ },
44
+ ],
45
+ };
46
+
47
+ render(): React.Node {
48
+ const {markers} = this.state;
49
+
50
+ return (
51
+ <div className={css(styles.wrapper)}>
52
+ <QuestionMarkers
53
+ choices={[
54
+ "Lamborghini",
55
+ "BMW",
56
+ "Volkswagen",
57
+ "Fiat",
58
+ "Porsche",
59
+ "Ferrari",
60
+ ]}
61
+ imageUrl="https://ka-perseus-images.s3.amazonaws.com/2ee5fc32e35c5178373b39fd304b325b2994c913.png"
62
+ imageWidth={1280}
63
+ imageHeight={1024}
64
+ markers={markers}
65
+ onChange={(markers) => this.setState({markers})}
66
+ />
67
+ </div>
68
+ );
69
+ }
70
+ }
71
+
72
+ export const Empty = (args: StoryArgs): React.Node => {
73
+ const props = {
74
+ choices: [],
75
+ imageUrl: "",
76
+ imageWidth: 0,
77
+ imageHeight: 0,
78
+ markers: [],
79
+ onChange: () => {},
80
+ };
81
+ return <Wrapper {...props} />;
82
+ };
83
+
84
+ export const Filled = (args: StoryArgs): React.Node => {
85
+ const props = {
86
+ choices: [],
87
+ imageUrl:
88
+ "https://ka-perseus-images.s3.amazonaws.com/2ee5fc32e35c5178373b39fd304b325b2994c913.png",
89
+ imageWidth: 1280,
90
+ imageHeight: 1024,
91
+ markers: [
92
+ {
93
+ answers: [],
94
+ label: "",
95
+ x: 50,
96
+ y: 50,
97
+ },
98
+ ],
99
+ onChange: () => {},
100
+ };
101
+ return <Wrapper {...props} />;
102
+ };
103
+
104
+ export const Interactive = (args: StoryArgs): React.Node => {
105
+ return <WithState />;
106
+ };
@@ -0,0 +1,60 @@
1
+ // @flow
2
+ import {StyleSheet, css} from "aphrodite";
3
+ import * as React from "react";
4
+
5
+ import SelectImage from "../select-image.jsx";
6
+
7
+ type StoryArgs = {||};
8
+
9
+ type Story = {|
10
+ title: string,
11
+ |};
12
+
13
+ export default ({
14
+ title: "Perseus/Editor/Widgets/Label Image/Select Image",
15
+ }: Story);
16
+
17
+ const styles = StyleSheet.create({
18
+ wrapper: {
19
+ // The maximum width of a widget in the editor.
20
+ width: 338,
21
+ },
22
+ });
23
+
24
+ const Wrapper = (props) => (
25
+ <div className={css(styles.wrapper)}>
26
+ <SelectImage {...props} />
27
+ </div>
28
+ );
29
+
30
+ const WithState = () => {
31
+ const [url, setUrl] = React.useState("");
32
+
33
+ return (
34
+ <div className={css(styles.wrapper)}>
35
+ <SelectImage onChange={(url) => setUrl(url)} url={url} />
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export const Empty = (args: StoryArgs): React.Node => {
41
+ const props = {
42
+ url: "",
43
+ onChange: () => {},
44
+ };
45
+
46
+ return <Wrapper {...props} />;
47
+ };
48
+
49
+ export const Filled = (args: StoryArgs): React.Node => {
50
+ const props = {
51
+ url: "https://ka-perseus-images.s3.amazonaws.com/2ee5fc32e35c5178373b39fd304b325b2994c913.png",
52
+ onChange: () => {},
53
+ };
54
+
55
+ return <Wrapper {...props} />;
56
+ };
57
+
58
+ export const Interactive = (args: StoryArgs): React.Node => {
59
+ return <WithState />;
60
+ };