@khanacademy/perseus-editor 0.0.1 → 0.1.0

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 (125) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/es/index.js +10 -1
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +10 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/strings.js +6 -1
  8. package/package.json +16 -4
  9. package/src/README.md +7 -0
  10. package/src/__stories__/editor.stories.js +33 -0
  11. package/src/__stories__/section-control-button.stories.jsx +27 -0
  12. package/src/__testdata__/input-number_testdata.js +28 -0
  13. package/src/__tests__/editor_test.jsx +123 -0
  14. package/src/__tests__/i18n_test.jsx +585 -0
  15. package/src/__tests__/traversal_test.jsx +378 -0
  16. package/src/all-editors.js +85 -0
  17. package/src/all-editors_test.js +17 -0
  18. package/src/all-widgets.js +10 -0
  19. package/src/article-editor.jsx +437 -0
  20. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  21. package/src/components/blur-input.jsx +62 -0
  22. package/src/components/checkbox.jsx +194 -0
  23. package/src/components/device-framer.jsx +91 -0
  24. package/src/components/drag-target.jsx +88 -0
  25. package/src/components/dropdown-option.jsx +318 -0
  26. package/src/components/form-wrapped-text-field.jsx +248 -0
  27. package/src/components/graph-settings.jsx +631 -0
  28. package/src/components/hover-behavior.jsx +178 -0
  29. package/src/components/json-editor.jsx +118 -0
  30. package/src/components/link.jsx +173 -0
  31. package/src/components/simple-button.jsx +86 -0
  32. package/src/components/sortable.jsx +186 -0
  33. package/src/components/util.js +39 -0
  34. package/src/components/viewport-resizer.jsx +73 -0
  35. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  36. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  37. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  38. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  39. package/src/diffs/__tests__/split-diff_test.js +34 -0
  40. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  41. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  42. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  43. package/src/diffs/article-diff.jsx +70 -0
  44. package/src/diffs/item-diff.jsx +77 -0
  45. package/src/diffs/renderer-diff.jsx +143 -0
  46. package/src/diffs/split-diff.jsx +30 -0
  47. package/src/diffs/string-array-diff.jsx +74 -0
  48. package/src/diffs/structured-item-diff.jsx +269 -0
  49. package/src/diffs/tags-diff.jsx +50 -0
  50. package/src/diffs/text-diff.jsx +193 -0
  51. package/src/diffs/widget-diff-performer.jsx +74 -0
  52. package/src/diffs/widget-diff.jsx +291 -0
  53. package/src/editor-page.jsx +351 -0
  54. package/src/editor.jsx +1364 -0
  55. package/src/hint-editor.jsx +447 -0
  56. package/src/i18n.jsx +139 -0
  57. package/src/iframe-content-renderer.jsx +201 -0
  58. package/src/index.js +27 -8
  59. package/src/item-editor.jsx +159 -0
  60. package/src/item-extras-editor.jsx +150 -0
  61. package/src/katex-error-view.jsx +87 -0
  62. package/src/multirenderer-editor.jsx +1073 -0
  63. package/src/section-control-button.jsx +53 -0
  64. package/src/stateful-article-editor.jsx +176 -0
  65. package/src/stateful-editor-page.jsx +90 -0
  66. package/src/styles/perseus-editor.less +1339 -0
  67. package/src/testing-widgets.js +12 -0
  68. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  69. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  70. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  71. package/src/widgets/categorizer-editor.jsx +103 -0
  72. package/src/widgets/cs-program-editor.jsx +258 -0
  73. package/src/widgets/definition-editor.jsx +76 -0
  74. package/src/widgets/dropdown-editor.jsx +201 -0
  75. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  76. package/src/widgets/example-graphie-widget.jsx +175 -0
  77. package/src/widgets/example-widget-editor.jsx +60 -0
  78. package/src/widgets/example-widget.jsx +153 -0
  79. package/src/widgets/explanation-editor.jsx +89 -0
  80. package/src/widgets/expression-editor.jsx +559 -0
  81. package/src/widgets/graded-group-editor.jsx +162 -0
  82. package/src/widgets/graded-group-set-editor.jsx +101 -0
  83. package/src/widgets/grapher-editor.jsx +154 -0
  84. package/src/widgets/group-editor.jsx +83 -0
  85. package/src/widgets/iframe-editor.jsx +200 -0
  86. package/src/widgets/image-editor.jsx +326 -0
  87. package/src/widgets/input-number-editor.jsx +269 -0
  88. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  89. package/src/widgets/interaction-editor.jsx +1388 -0
  90. package/src/widgets/interactive-graph-editor.jsx +320 -0
  91. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  92. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  93. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  94. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  95. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  96. package/src/widgets/label-image/answer-choices.jsx +231 -0
  97. package/src/widgets/label-image/behavior.jsx +92 -0
  98. package/src/widgets/label-image/marker.jsx +292 -0
  99. package/src/widgets/label-image/question-markers.jsx +181 -0
  100. package/src/widgets/label-image/select-image.jsx +86 -0
  101. package/src/widgets/label-image-editor.jsx +256 -0
  102. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  103. package/src/widgets/matcher-editor.jsx +147 -0
  104. package/src/widgets/matrix-editor.jsx +155 -0
  105. package/src/widgets/measurer-editor.jsx +245 -0
  106. package/src/widgets/molecule-editor.jsx +63 -0
  107. package/src/widgets/number-line-editor.jsx +498 -0
  108. package/src/widgets/numeric-input-editor.jsx +554 -0
  109. package/src/widgets/orderer-editor.jsx +166 -0
  110. package/src/widgets/passage-editor.jsx +115 -0
  111. package/src/widgets/passage-ref-editor.jsx +77 -0
  112. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  113. package/src/widgets/plotter-editor.jsx +537 -0
  114. package/src/widgets/radio/editor.jsx +399 -0
  115. package/src/widgets/reaction-diagram-editor.jsx +126 -0
  116. package/src/widgets/sequence-editor.jsx +205 -0
  117. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  118. package/src/widgets/simple-markdown-tester.jsx +91 -0
  119. package/src/widgets/simulator-editor.jsx +144 -0
  120. package/src/widgets/sorter-editor.jsx +100 -0
  121. package/src/widgets/table-editor.jsx +164 -0
  122. package/src/widgets/transformer-editor.jsx +1006 -0
  123. package/src/widgets/unit-editor.jsx +293 -0
  124. package/src/widgets/video-editor.jsx +74 -0
  125. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,320 @@
1
+ /* eslint-disable react/no-unsafe, react/sort-comp */
2
+ // @flow
3
+ import {
4
+ components,
5
+ interactiveSizes,
6
+ InteractiveGraphWidget,
7
+ SizingUtils,
8
+ Util,
9
+ } from "@khanacademy/perseus";
10
+ import createReactClass from "create-react-class";
11
+ import * as React from "react";
12
+ import _ from "underscore";
13
+
14
+ import GraphSettings from "../components/graph-settings.jsx";
15
+
16
+ const {InfoTip} = components;
17
+ const {containerSizeClass, getInteractiveBoxFromSizeClass} = SizingUtils;
18
+ const DeprecationMixin = Util.DeprecationMixin;
19
+ const InteractiveGraph = InteractiveGraphWidget.widget;
20
+
21
+ type InteractiveGraphProps = React.ElementConfig<typeof InteractiveGraph>;
22
+
23
+ const defaultBackgroundImage = {
24
+ url: null,
25
+ };
26
+
27
+ const deprecatedProps = {
28
+ showGraph: function (props) {
29
+ return {markings: props.showGraph ? "graph" : "none"};
30
+ },
31
+ };
32
+
33
+ const InteractiveGraphEditor: $FlowFixMe = createReactClass({
34
+ displayName: "InteractiveGraphEditor",
35
+ className: "perseus-widget-interactive-graph",
36
+
37
+ statics: {
38
+ widgetName: "interactive-graph",
39
+ },
40
+
41
+ getDefaultProps: function () {
42
+ return {
43
+ labels: ["x", "y"],
44
+ range: [
45
+ [-10, 10],
46
+ [-10, 10],
47
+ ],
48
+ step: [1, 1],
49
+ valid: true,
50
+ backgroundImage: defaultBackgroundImage,
51
+ markings: "graph",
52
+ showProtractor: false,
53
+ showRuler: false,
54
+ showTooltips: false,
55
+ rulerLabel: "",
56
+ rulerTicks: 10,
57
+ correct: {
58
+ type: "linear",
59
+ coords: null,
60
+ },
61
+ };
62
+ },
63
+
64
+ // TODO(jack): Use versioning instead of DeprecationMixin
65
+ deprecatedProps: deprecatedProps,
66
+
67
+ // TODO(jangmi, CP-3288): Remove usage of `UNSAFE_componentWillMount`
68
+ UNSAFE_componentWillMount() {
69
+ // $FlowFixMe[object-this-reference]
70
+ DeprecationMixin.UNSAFE_componentWillMount.call(this);
71
+ },
72
+
73
+ render: function () {
74
+ let graph;
75
+ let equationString;
76
+
77
+ const gridStep =
78
+ this.props.gridStep ||
79
+ Util.getGridStep(
80
+ this.props.range,
81
+ this.props.step,
82
+ interactiveSizes.defaultBoxSize,
83
+ );
84
+ const snapStep =
85
+ this.props.snapStep || Util.snapStepFromGridStep(gridStep);
86
+
87
+ const sizeClass = containerSizeClass.SMALL;
88
+ if (this.props.valid === true) {
89
+ // TODO(aria): send these down all at once
90
+ const graphProps = {
91
+ ref: "graph",
92
+ box: this.props.box,
93
+ range: this.props.range,
94
+ labels: this.props.labels,
95
+ step: this.props.step,
96
+ gridStep: gridStep,
97
+ snapStep: snapStep,
98
+ graph: this.props.correct,
99
+ backgroundImage: this.props.backgroundImage,
100
+ markings: this.props.markings,
101
+ showProtractor: this.props.showProtractor,
102
+ showRuler: this.props.showRuler,
103
+ rulerLabel: this.props.rulerLabel,
104
+ rulerTicks: this.props.rulerTicks,
105
+ trackInteraction: function () {},
106
+ onChange: (newProps: InteractiveGraphProps) => {
107
+ let correct = this.props.correct;
108
+ // $FlowFixMe[incompatible-use]
109
+ if (correct.type === newProps.graph.type) {
110
+ correct = _.extend({}, correct, newProps.graph);
111
+ } else {
112
+ // Clear options from previous graph
113
+ correct = newProps.graph;
114
+ }
115
+ this.props.onChange({correct: correct});
116
+ },
117
+ };
118
+ graph = (
119
+ // There are a bunch of props that renderer.jsx passes to widgets via
120
+ // getWidgetProps() and widget-container.jsx that the editors don't
121
+ // bother passing.
122
+ // $FlowFixMe[prop-missing]
123
+ <InteractiveGraph
124
+ {...graphProps}
125
+ containerSizeClass={sizeClass}
126
+ apiOptions={{
127
+ ...this.props.apiOptions,
128
+ isMobile: false,
129
+ }}
130
+ />
131
+ );
132
+ // TODO(kevinb): Update getEquationString to only accept the data it actually
133
+ // needs to compute the equation string.
134
+ // $FlowFixMe[prop-missing]
135
+ equationString = InteractiveGraph.getEquationString(graphProps);
136
+ } else {
137
+ graph = <div className="perseus-error">{this.props.valid}</div>;
138
+ }
139
+
140
+ return (
141
+ <div className="perseus-widget-interactive-graph">
142
+ <div>
143
+ Correct answer{" "}
144
+ <InfoTip>
145
+ <p>
146
+ Graph the correct answer in the graph below and
147
+ ensure the equation or point coordinates displayed
148
+ represent the correct answer.
149
+ </p>
150
+ </InfoTip>{" "}
151
+ : {equationString}
152
+ </div>
153
+
154
+ <GraphSettings
155
+ box={getInteractiveBoxFromSizeClass(sizeClass)}
156
+ range={this.props.range}
157
+ labels={this.props.labels}
158
+ step={this.props.step}
159
+ gridStep={gridStep}
160
+ snapStep={snapStep}
161
+ valid={this.props.valid}
162
+ backgroundImage={this.props.backgroundImage}
163
+ markings={this.props.markings}
164
+ showProtractor={this.props.showProtractor}
165
+ showRuler={this.props.showRuler}
166
+ showTooltips={this.props.showTooltips}
167
+ rulerLabel={this.props.rulerLabel}
168
+ rulerTicks={this.props.rulerTicks}
169
+ onChange={this.props.onChange}
170
+ />
171
+
172
+ {this.props.correct.type === "polygon" && (
173
+ <div className="type-settings">
174
+ <label>
175
+ {" "}
176
+ Student answer must{" "}
177
+ <select
178
+ value={this.props.correct.match}
179
+ onChange={this.changeMatchType}
180
+ >
181
+ <option value="exact">match exactly</option>
182
+ <option value="congruent">be congruent</option>
183
+ <option value="approx">
184
+ be approximately congruent
185
+ </option>
186
+ <option value="similar">be similar</option>
187
+ </select>
188
+ </label>
189
+ <InfoTip>
190
+ <ul>
191
+ <li>
192
+ <p>
193
+ <b>Match Exactly:</b> Match exactly in
194
+ size, orientation, and location on the
195
+ grid even if it is not shown in the
196
+ background.
197
+ </p>
198
+ </li>
199
+ <li>
200
+ <p>
201
+ <b>Be Congruent:</b> Be congruent in
202
+ size and shape, but can be located
203
+ anywhere on the grid.
204
+ </p>
205
+ </li>
206
+ <li>
207
+ <p>
208
+ <b>Be Approximately Congruent:</b> Be
209
+ exactly similar, and congruent in size
210
+ and shape to within 0.1 units, but can
211
+ be located anywhere on the grid.{" "}
212
+ <em>
213
+ Use this with snapping to angle
214
+ measure.
215
+ </em>
216
+ </p>
217
+ </li>
218
+ <li>
219
+ <p>
220
+ <b>Be Similar:</b> Be similar with
221
+ matching interior angles, and side
222
+ measures that are matching or a multiple
223
+ of the correct side measures. The figure
224
+ can be located anywhere on the grid.
225
+ </p>
226
+ </li>
227
+ </ul>
228
+ </InfoTip>
229
+ </div>
230
+ )}
231
+ {this.props.correct.type === "angle" && (
232
+ <div className="type-settings">
233
+ <div>
234
+ <label>
235
+ {" "}
236
+ Student answer must{" "}
237
+ <select
238
+ value={this.props.correct.match}
239
+ onChange={this.changeMatchType}
240
+ >
241
+ <option value="exact">match exactly</option>
242
+ <option value="congruent">
243
+ be congruent
244
+ </option>
245
+ </select>
246
+ </label>
247
+ <InfoTip>
248
+ <p>
249
+ Congruency requires only that the angle
250
+ measures are the same. An exact match
251
+ implies congruency, but also requires that
252
+ the angles have the same orientation and
253
+ that the vertices are in the same position.
254
+ </p>
255
+ </InfoTip>
256
+ </div>
257
+ </div>
258
+ )}
259
+ {graph}
260
+ </div>
261
+ );
262
+ },
263
+
264
+ changeMatchType: function (e) {
265
+ const correct = _.extend({}, this.props.correct, {
266
+ match: e.target.value,
267
+ });
268
+ this.props.onChange({correct: correct});
269
+ },
270
+
271
+ serialize: function () {
272
+ const json = _.pick(
273
+ this.props,
274
+ "step",
275
+ "backgroundImage",
276
+ "markings",
277
+ "labels",
278
+ "showProtractor",
279
+ "showRuler",
280
+ "showTooltips",
281
+ "rulerLabel",
282
+ "rulerTicks",
283
+ "range",
284
+ "gridStep",
285
+ "snapStep",
286
+ );
287
+
288
+ // eslint-disable-next-line react/no-string-refs
289
+ const graph = this.refs.graph;
290
+ if (graph) {
291
+ const correct = graph && graph.getUserInput();
292
+ _.extend(json, {
293
+ graph: {type: correct.type},
294
+ correct: correct,
295
+ });
296
+
297
+ _.each(
298
+ [
299
+ "allowReflexAngles",
300
+ "angleOffsetDeg",
301
+ "numPoints",
302
+ "numSides",
303
+ "numSegments",
304
+ "showAngles",
305
+ "showSides",
306
+ "snapTo",
307
+ "snapDegrees",
308
+ ],
309
+ function (key) {
310
+ if (_.has(correct, key)) {
311
+ json.graph[key] = correct[key];
312
+ }
313
+ },
314
+ );
315
+ }
316
+ return json;
317
+ },
318
+ });
319
+
320
+ 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
+ };