@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.
- package/.babelrc.js +1 -1
- package/CHANGELOG.md +26 -0
- package/dist/es/index.css +1020 -0
- package/dist/es/index.js +9 -1
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +1020 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/strings.js +6 -1
- package/package.json +16 -4
- package/src/README.md +7 -0
- package/src/__stories__/editor.stories.js +33 -0
- package/src/__stories__/section-control-button.stories.jsx +27 -0
- package/src/__testdata__/input-number_testdata.js +28 -0
- package/src/__tests__/editor_test.jsx +123 -0
- package/src/__tests__/i18n_test.jsx +585 -0
- package/src/__tests__/traversal_test.jsx +378 -0
- package/src/all-editors.js +85 -0
- package/src/all-editors_test.js +17 -0
- package/src/all-widgets.js +10 -0
- package/src/article-editor.jsx +437 -0
- package/src/components/__stories__/blur-input.stories.jsx +24 -0
- package/src/components/blur-input.jsx +62 -0
- package/src/components/checkbox.jsx +194 -0
- package/src/components/device-framer.jsx +91 -0
- package/src/components/drag-target.jsx +88 -0
- package/src/components/dropdown-option.jsx +318 -0
- package/src/components/form-wrapped-text-field.jsx +248 -0
- package/src/components/graph-settings.jsx +631 -0
- package/src/components/hover-behavior.jsx +178 -0
- package/src/components/json-editor.jsx +121 -0
- package/src/components/link.jsx +173 -0
- package/src/components/simple-button.jsx +86 -0
- package/src/components/sortable.jsx +186 -0
- package/src/components/util.js +39 -0
- package/src/components/viewport-resizer.jsx +73 -0
- package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
- package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
- package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
- package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
- package/src/diffs/__tests__/split-diff_test.js +34 -0
- package/src/diffs/__tests__/string-array-diff_test.js +74 -0
- package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
- package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
- package/src/diffs/article-diff.jsx +70 -0
- package/src/diffs/item-diff.jsx +77 -0
- package/src/diffs/renderer-diff.jsx +143 -0
- package/src/diffs/split-diff.jsx +30 -0
- package/src/diffs/string-array-diff.jsx +75 -0
- package/src/diffs/structured-item-diff.jsx +269 -0
- package/src/diffs/tags-diff.jsx +50 -0
- package/src/diffs/text-diff.jsx +194 -0
- package/src/diffs/widget-diff-performer.jsx +74 -0
- package/src/diffs/widget-diff.jsx +291 -0
- package/src/editor-page.jsx +351 -0
- package/src/editor.jsx +1364 -0
- package/src/hint-editor.jsx +447 -0
- package/src/i18n.jsx +139 -0
- package/src/iframe-content-renderer.jsx +201 -0
- package/src/index.js +27 -8
- package/src/item-editor.jsx +159 -0
- package/src/item-extras-editor.jsx +150 -0
- package/src/katex-error-view.jsx +87 -0
- package/src/multirenderer-editor.jsx +1074 -0
- package/src/section-control-button.jsx +53 -0
- package/src/stateful-article-editor.jsx +176 -0
- package/src/stateful-editor-page.jsx +90 -0
- package/src/styles/perseus-editor.less +1339 -0
- package/src/testing-widgets.js +12 -0
- package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
- package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
- package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
- package/src/widgets/categorizer-editor.jsx +103 -0
- package/src/widgets/cs-program-editor.jsx +258 -0
- package/src/widgets/definition-editor.jsx +75 -0
- package/src/widgets/dropdown-editor.jsx +201 -0
- package/src/widgets/example-graphie-widget-editor.jsx +70 -0
- package/src/widgets/example-graphie-widget.jsx +175 -0
- package/src/widgets/example-widget-editor.jsx +60 -0
- package/src/widgets/example-widget.jsx +153 -0
- package/src/widgets/explanation-editor.jsx +89 -0
- package/src/widgets/expression-editor.jsx +560 -0
- package/src/widgets/graded-group-editor.jsx +162 -0
- package/src/widgets/graded-group-set-editor.jsx +101 -0
- package/src/widgets/grapher-editor.jsx +154 -0
- package/src/widgets/group-editor.jsx +83 -0
- package/src/widgets/iframe-editor.jsx +200 -0
- package/src/widgets/image-editor.jsx +326 -0
- package/src/widgets/input-number-editor.jsx +269 -0
- package/src/widgets/interaction/constraint-editor.jsx +147 -0
- package/src/widgets/interaction-editor.jsx +1389 -0
- package/src/widgets/interactive-graph-editor.jsx +322 -0
- package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
- package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
- package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
- package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
- package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
- package/src/widgets/label-image/answer-choices.jsx +231 -0
- package/src/widgets/label-image/behavior.jsx +92 -0
- package/src/widgets/label-image/marker.jsx +292 -0
- package/src/widgets/label-image/question-markers.jsx +181 -0
- package/src/widgets/label-image/select-image.jsx +86 -0
- package/src/widgets/label-image-editor.jsx +257 -0
- package/src/widgets/lights-puzzle-editor.jsx +267 -0
- package/src/widgets/matcher-editor.jsx +147 -0
- package/src/widgets/matrix-editor.jsx +155 -0
- package/src/widgets/measurer-editor.jsx +245 -0
- package/src/widgets/molecule-editor.jsx +63 -0
- package/src/widgets/number-line-editor.jsx +498 -0
- package/src/widgets/numeric-input-editor.jsx +554 -0
- package/src/widgets/orderer-editor.jsx +166 -0
- package/src/widgets/passage-editor.jsx +115 -0
- package/src/widgets/passage-ref-editor.jsx +77 -0
- package/src/widgets/passage-ref-target-editor.jsx +47 -0
- package/src/widgets/plotter-editor.jsx +537 -0
- package/src/widgets/radio/editor.jsx +398 -0
- package/src/widgets/reaction-diagram-editor.jsx +127 -0
- package/src/widgets/sequence-editor.jsx +205 -0
- package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
- package/src/widgets/simple-markdown-tester.jsx +91 -0
- package/src/widgets/simulator-editor.jsx +144 -0
- package/src/widgets/sorter-editor.jsx +100 -0
- package/src/widgets/table-editor.jsx +164 -0
- package/src/widgets/transformer-editor.jsx +1006 -0
- package/src/widgets/unit-editor.jsx +293 -0
- package/src/widgets/video-editor.jsx +74 -0
- 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
|
+
};
|