@khanacademy/perseus-editor 4.1.4 → 4.1.6
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/dist/es/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +9 -6
- package/.babelrc.js +0 -8
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -1146
- package/src/README.md +0 -7
- package/src/__stories__/editor-page.stories.tsx +0 -65
- package/src/__stories__/editor.stories.tsx +0 -134
- package/src/__stories__/item-editor.stories.tsx +0 -41
- package/src/__stories__/tex-error-view.stories.tsx +0 -26
- package/src/__testdata__/input-number.testdata.ts +0 -27
- package/src/__tests__/editor.test.tsx +0 -142
- package/src/__tests__/hint-editor.test.tsx +0 -83
- package/src/__tests__/i18n.test.ts +0 -501
- package/src/__tests__/integration.test.ts +0 -5
- package/src/__tests__/item-extras-editor.test.tsx +0 -85
- package/src/__tests__/traversal.test.ts +0 -377
- package/src/all-editors.test.ts +0 -16
- package/src/all-editors.ts +0 -83
- package/src/all-widgets.ts +0 -7
- package/src/article-editor.tsx +0 -438
- package/src/components/__stories__/blur-input.stories.tsx +0 -22
- package/src/components/__stories__/section-control-button.stories.tsx +0 -26
- package/src/components/__tests__/blur-input.test.tsx +0 -67
- package/src/components/blur-input.tsx +0 -69
- package/src/components/checkbox.tsx +0 -194
- package/src/components/device-framer.tsx +0 -90
- package/src/components/drag-target.tsx +0 -87
- package/src/components/dropdown-option.tsx +0 -311
- package/src/components/form-wrapped-text-field.tsx +0 -245
- package/src/components/graph-points-count-selector.tsx +0 -31
- package/src/components/graph-settings.tsx +0 -627
- package/src/components/graph-type-selector.tsx +0 -31
- package/src/components/hover-behavior.ts +0 -177
- package/src/components/json-editor.tsx +0 -121
- package/src/components/link.ts +0 -173
- package/src/components/section-control-button.tsx +0 -53
- package/src/components/segment-count-selector.tsx +0 -27
- package/src/components/simple-button.tsx +0 -85
- package/src/components/sortable.tsx +0 -187
- package/src/components/util.ts +0 -38
- package/src/components/viewport-resizer.tsx +0 -72
- package/src/components/widget-editor.tsx +0 -222
- package/src/components/widget-select.tsx +0 -48
- package/src/diffs/__stories__/perseus-diff-wrapper.tsx +0 -11
- package/src/diffs/__stories__/structured-item-diff.stories.tsx +0 -855
- package/src/diffs/__stories__/tags-diff.stories.tsx +0 -36
- package/src/diffs/__stories__/text-diff.stories.tsx +0 -35
- package/src/diffs/__tests__/split-diff.test.ts +0 -33
- package/src/diffs/__tests__/string-array-diff.test.ts +0 -73
- package/src/diffs/__tests__/structured-item-diff.test.ts +0 -433
- package/src/diffs/__tests__/widget-diff-performer.test.ts +0 -43
- package/src/diffs/article-diff.tsx +0 -69
- package/src/diffs/item-diff.tsx +0 -76
- package/src/diffs/renderer-diff.tsx +0 -137
- package/src/diffs/split-diff.ts +0 -31
- package/src/diffs/string-array-diff.ts +0 -74
- package/src/diffs/structured-item-diff.tsx +0 -275
- package/src/diffs/tags-diff.tsx +0 -49
- package/src/diffs/text-diff.tsx +0 -193
- package/src/diffs/widget-diff-performer.ts +0 -69
- package/src/diffs/widget-diff.tsx +0 -290
- package/src/editor-page.tsx +0 -325
- package/src/editor.tsx +0 -1099
- package/src/hint-editor.tsx +0 -469
- package/src/i18n.ts +0 -140
- package/src/iframe-content-renderer.tsx +0 -188
- package/src/index.ts +0 -30
- package/src/item-editor.tsx +0 -158
- package/src/item-extras-editor.tsx +0 -230
- package/src/katex-mhchem/index.js +0 -73
- package/src/katex-mhchem/parser.js +0 -2659
- package/src/multirenderer-editor.tsx +0 -1061
- package/src/stateful-article-editor.tsx +0 -173
- package/src/stateful-editor-page.tsx +0 -88
- package/src/styles/perseus-editor.less +0 -1321
- package/src/testing-widgets.ts +0 -11
- package/src/tex-error-view.tsx +0 -90
- package/src/util/points.test.ts +0 -24
- package/src/util/points.ts +0 -13
- package/src/util/register-all-widgets-and-editors-for-testing.test.ts +0 -17
- package/src/util/register-all-widgets-and-editors-for-testing.ts +0 -17
- package/src/version.ts +0 -10
- package/src/widgets/__stories__/categorizer-editor.stories.tsx +0 -24
- package/src/widgets/__stories__/definition-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/dropdown-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/explanation-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/expression-editor.stories.tsx +0 -122
- package/src/widgets/__stories__/image-editor.stories.tsx +0 -72
- package/src/widgets/__stories__/input-number-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/interaction-editor.stories.tsx +0 -37
- package/src/widgets/__stories__/label-image-editor.stories.tsx +0 -99
- package/src/widgets/__stories__/matcher-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/number-line-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/numeric-input-editor.stories.tsx +0 -18
- package/src/widgets/__stories__/radio-editor.stories.tsx +0 -116
- package/src/widgets/__stories__/sorter-editor.stories.tsx +0 -18
- package/src/widgets/__tests__/categorizer-editor.test.tsx +0 -51
- package/src/widgets/__tests__/definition-editor.test.tsx +0 -41
- package/src/widgets/__tests__/dropdown-editor.test.tsx +0 -57
- package/src/widgets/__tests__/explanation-editor.test.tsx +0 -59
- package/src/widgets/__tests__/expression-editor.test.tsx +0 -403
- package/src/widgets/__tests__/input-number-editor.test.tsx +0 -128
- package/src/widgets/__tests__/matcher-editor.test.tsx +0 -47
- package/src/widgets/__tests__/number-line-editor.test.tsx +0 -144
- package/src/widgets/__tests__/numeric-input-editor.test.tsx +0 -177
- package/src/widgets/__tests__/radio-editor.test.tsx +0 -118
- package/src/widgets/__tests__/sorter-editor.test.tsx +0 -55
- package/src/widgets/categorizer-editor.tsx +0 -104
- package/src/widgets/cs-program-editor.tsx +0 -256
- package/src/widgets/definition-editor.tsx +0 -77
- package/src/widgets/deprecated-standin-editor.tsx +0 -29
- package/src/widgets/dropdown-editor.tsx +0 -211
- package/src/widgets/example-graphie-widget-editor.tsx +0 -70
- package/src/widgets/example-graphie-widget.tsx +0 -172
- package/src/widgets/example-widget-editor.tsx +0 -62
- package/src/widgets/example-widget.tsx +0 -154
- package/src/widgets/explanation-editor.tsx +0 -92
- package/src/widgets/expression-editor.tsx +0 -557
- package/src/widgets/graded-group-editor.tsx +0 -161
- package/src/widgets/graded-group-set-editor.tsx +0 -104
- package/src/widgets/grapher-editor.tsx +0 -155
- package/src/widgets/group-editor.tsx +0 -83
- package/src/widgets/iframe-editor.tsx +0 -202
- package/src/widgets/image-editor.tsx +0 -367
- package/src/widgets/input-number-editor.tsx +0 -288
- package/src/widgets/interaction-editor/constraint-editor.tsx +0 -134
- package/src/widgets/interaction-editor/function-editor.tsx +0 -108
- package/src/widgets/interaction-editor/index.ts +0 -3
- package/src/widgets/interaction-editor/interaction-editor.tsx +0 -749
- package/src/widgets/interaction-editor/label-editor.tsx +0 -87
- package/src/widgets/interaction-editor/line-editor.tsx +0 -125
- package/src/widgets/interaction-editor/mathquill-input.tsx +0 -81
- package/src/widgets/interaction-editor/movable-line-editor.tsx +0 -121
- package/src/widgets/interaction-editor/movable-point-editor.tsx +0 -89
- package/src/widgets/interaction-editor/parametric-editor.tsx +0 -117
- package/src/widgets/interaction-editor/point-editor.tsx +0 -72
- package/src/widgets/interaction-editor/rectangle-editor.tsx +0 -96
- package/src/widgets/interactive-graph-editor.tsx +0 -571
- package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -82
- package/src/widgets/label-image/__stories__/behavior.stories.tsx +0 -31
- package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -75
- package/src/widgets/label-image/__stories__/question-markers.stories.tsx +0 -107
- package/src/widgets/label-image/__stories__/select-image.stories.tsx +0 -59
- package/src/widgets/label-image/answer-choices.tsx +0 -231
- package/src/widgets/label-image/behavior.tsx +0 -122
- package/src/widgets/label-image/marker.tsx +0 -290
- package/src/widgets/label-image/question-markers.tsx +0 -176
- package/src/widgets/label-image/select-image.tsx +0 -81
- package/src/widgets/label-image-editor.tsx +0 -252
- package/src/widgets/lights-puzzle-editor.tsx +0 -278
- package/src/widgets/matcher-editor.tsx +0 -149
- package/src/widgets/matrix-editor.tsx +0 -156
- package/src/widgets/measurer-editor.tsx +0 -253
- package/src/widgets/molecule-editor.tsx +0 -64
- package/src/widgets/number-line-editor.tsx +0 -512
- package/src/widgets/numeric-input-editor.tsx +0 -536
- package/src/widgets/orderer-editor.tsx +0 -172
- package/src/widgets/passage-editor.tsx +0 -117
- package/src/widgets/passage-ref-editor.tsx +0 -80
- package/src/widgets/passage-ref-target-editor.tsx +0 -49
- package/src/widgets/plotter-editor.tsx +0 -542
- package/src/widgets/radio/editor.tsx +0 -398
- package/src/widgets/reaction-diagram-editor.tsx +0 -131
- package/src/widgets/sequence-editor.tsx +0 -205
- package/src/widgets/simple-markdown-tester-editor.tsx +0 -86
- package/src/widgets/simple-markdown-tester.tsx +0 -92
- package/src/widgets/simulator-editor.tsx +0 -146
- package/src/widgets/sorter-editor.tsx +0 -103
- package/src/widgets/table-editor.tsx +0 -164
- package/src/widgets/unit-editor.tsx +0 -299
- package/src/widgets/video-editor.tsx +0 -73
- package/tsconfig-build.json +0 -22
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/* eslint-disable jsx-a11y/anchor-is-valid, react/sort-comp */
|
|
2
|
-
import {
|
|
3
|
-
components,
|
|
4
|
-
icons,
|
|
5
|
-
Changeable,
|
|
6
|
-
EditorJsonify,
|
|
7
|
-
Util,
|
|
8
|
-
} from "@khanacademy/perseus";
|
|
9
|
-
import * as React from "react";
|
|
10
|
-
import _ from "underscore";
|
|
11
|
-
|
|
12
|
-
import BlurInput from "../components/blur-input";
|
|
13
|
-
import Editor from "../editor";
|
|
14
|
-
|
|
15
|
-
import type {APIOptions, Range, Size} from "@khanacademy/perseus";
|
|
16
|
-
|
|
17
|
-
const {InfoTip, InlineIcon, RangeInput} = components;
|
|
18
|
-
|
|
19
|
-
const defaultBoxSize = 400;
|
|
20
|
-
const defaultRange = [0, 10] as const;
|
|
21
|
-
const defaultBackgroundImage = {
|
|
22
|
-
url: null,
|
|
23
|
-
width: 0,
|
|
24
|
-
height: 0,
|
|
25
|
-
} as const;
|
|
26
|
-
|
|
27
|
-
// Match any image URL (including "web+graphie" links) that is hosted by KA.
|
|
28
|
-
// We're somewhat generous in our AWS URL matching
|
|
29
|
-
// ("ka-<something>.s3.amazonaws.com") so that we don't have to update Perseus
|
|
30
|
-
// every time we add a new proxied AWS bucket.
|
|
31
|
-
const INTERNALLY_HOSTED_DOMAINS =
|
|
32
|
-
"(" +
|
|
33
|
-
"ka-.*.s3.amazonaws.com|" +
|
|
34
|
-
"(fastly|cdn).kastatic.org|" +
|
|
35
|
-
"khanacademy.org|" +
|
|
36
|
-
"kasandbox.org" +
|
|
37
|
-
")";
|
|
38
|
-
const INTERNALLY_HOSTED_URL_RE = new RegExp(
|
|
39
|
-
"^(https?|web\\+graphie)://[^/]*" + INTERNALLY_HOSTED_DOMAINS,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Alignment option for captions, relative to specified coordinates.
|
|
44
|
-
*/
|
|
45
|
-
const captionAlignments = [
|
|
46
|
-
"center",
|
|
47
|
-
"above",
|
|
48
|
-
"above right",
|
|
49
|
-
"right",
|
|
50
|
-
"below right",
|
|
51
|
-
"below",
|
|
52
|
-
"below left",
|
|
53
|
-
"left",
|
|
54
|
-
"above left",
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
type Props = Changeable.ChangeableProps & {
|
|
58
|
-
apiOptions: APIOptions;
|
|
59
|
-
|
|
60
|
-
title: string;
|
|
61
|
-
range: [Readonly<Range>, Readonly<Range>];
|
|
62
|
-
box: Size;
|
|
63
|
-
backgroundImage: any;
|
|
64
|
-
labels: ReadonlyArray<string>;
|
|
65
|
-
alt: string;
|
|
66
|
-
caption: string;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
type DefaultProps = {
|
|
70
|
-
title: NonNullable<Props["title"]>;
|
|
71
|
-
range: NonNullable<Props["range"]>;
|
|
72
|
-
box: NonNullable<Props["box"]>;
|
|
73
|
-
backgroundImage: NonNullable<Props["backgroundImage"]>;
|
|
74
|
-
labels: NonNullable<Props["labels"]>;
|
|
75
|
-
alt: NonNullable<Props["alt"]>;
|
|
76
|
-
caption: NonNullable<Props["caption"]>;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
type State = {
|
|
80
|
-
backgroundImageError?: string;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
class ImageEditor extends React.Component<Props> {
|
|
84
|
-
_isMounted = false;
|
|
85
|
-
|
|
86
|
-
static displayName = "ImageEditor";
|
|
87
|
-
static widgetName = "image";
|
|
88
|
-
|
|
89
|
-
static defaultProps: DefaultProps = {
|
|
90
|
-
title: "",
|
|
91
|
-
range: [defaultRange, defaultRange],
|
|
92
|
-
box: [defaultBoxSize, defaultBoxSize],
|
|
93
|
-
backgroundImage: defaultBackgroundImage,
|
|
94
|
-
labels: [],
|
|
95
|
-
alt: "",
|
|
96
|
-
caption: "",
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
state: State = {
|
|
100
|
-
backgroundImageError: "",
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
componentDidMount() {
|
|
104
|
-
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
105
|
-
// this.isMounted() but is still considered an anti-pattern.
|
|
106
|
-
this._isMounted = true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
componentWillUnmount() {
|
|
110
|
-
this._isMounted = false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
render() {
|
|
114
|
-
const backgroundImage = this.props.backgroundImage;
|
|
115
|
-
|
|
116
|
-
const imageSettings = (
|
|
117
|
-
<div className="image-settings">
|
|
118
|
-
{!Util.isLabeledSVG(backgroundImage.url) && (
|
|
119
|
-
<div>
|
|
120
|
-
<label>
|
|
121
|
-
<div>Preview:</div>
|
|
122
|
-
<img
|
|
123
|
-
alt="Editor preview of image"
|
|
124
|
-
src={backgroundImage.url}
|
|
125
|
-
style={{
|
|
126
|
-
width: "100%",
|
|
127
|
-
}}
|
|
128
|
-
/>
|
|
129
|
-
</label>
|
|
130
|
-
</div>
|
|
131
|
-
)}
|
|
132
|
-
<div>
|
|
133
|
-
<label>
|
|
134
|
-
<div>Dimensions:</div>
|
|
135
|
-
<p>
|
|
136
|
-
{backgroundImage.width}x{backgroundImage.height}
|
|
137
|
-
</p>
|
|
138
|
-
</label>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<div>
|
|
142
|
-
<label>
|
|
143
|
-
<div>
|
|
144
|
-
Alt text:
|
|
145
|
-
<InfoTip>
|
|
146
|
-
This is important for screenreaders. The content
|
|
147
|
-
of this alt text will be formatted as markdown
|
|
148
|
-
(tables, emphasis, etc. are supported).
|
|
149
|
-
</InfoTip>
|
|
150
|
-
</div>
|
|
151
|
-
<Editor
|
|
152
|
-
apiOptions={this.props.apiOptions}
|
|
153
|
-
content={this.props.alt}
|
|
154
|
-
onChange={(props) => {
|
|
155
|
-
if (props.content != null) {
|
|
156
|
-
this.change("alt", props.content);
|
|
157
|
-
}
|
|
158
|
-
}}
|
|
159
|
-
widgetEnabled={false}
|
|
160
|
-
/>
|
|
161
|
-
</label>
|
|
162
|
-
</div>
|
|
163
|
-
<div>
|
|
164
|
-
<label>
|
|
165
|
-
<div>Caption:</div>
|
|
166
|
-
<Editor
|
|
167
|
-
apiOptions={this.props.apiOptions}
|
|
168
|
-
content={this.props.caption}
|
|
169
|
-
onChange={(props) => {
|
|
170
|
-
if (props.content != null) {
|
|
171
|
-
this.change("caption", props.content);
|
|
172
|
-
}
|
|
173
|
-
}}
|
|
174
|
-
widgetEnabled={false}
|
|
175
|
-
/>
|
|
176
|
-
</label>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const backgroundImageErrorText = (
|
|
182
|
-
<div className="renderer-widget-error">
|
|
183
|
-
{this.state.backgroundImageError}
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<div className="perseus-image-editor">
|
|
189
|
-
<label>
|
|
190
|
-
Image url:
|
|
191
|
-
<InfoTip>Paste an image or graphie image URL.</InfoTip>
|
|
192
|
-
{this.state.backgroundImageError &&
|
|
193
|
-
backgroundImageErrorText}
|
|
194
|
-
<BlurInput
|
|
195
|
-
value={backgroundImage.url || ""}
|
|
196
|
-
style={{width: 332}}
|
|
197
|
-
onChange={(url) => this.onUrlChange(url, false)}
|
|
198
|
-
/>
|
|
199
|
-
</label>
|
|
200
|
-
|
|
201
|
-
{backgroundImage.url && imageSettings}
|
|
202
|
-
</div>
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
_renderRowForLabel(label, i) {
|
|
207
|
-
return (
|
|
208
|
-
<tr key={i}>
|
|
209
|
-
<td>
|
|
210
|
-
<RangeInput
|
|
211
|
-
value={label.coordinates}
|
|
212
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
213
|
-
onChange={this.onCoordinateChange.bind(this, i)}
|
|
214
|
-
/>
|
|
215
|
-
</td>
|
|
216
|
-
<td style={{verticalAlign: "bottom", width: "5px"}}>
|
|
217
|
-
<input
|
|
218
|
-
type="text"
|
|
219
|
-
className="graph-settings-axis-label"
|
|
220
|
-
value={label.content}
|
|
221
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
222
|
-
onChange={this.onContentChange.bind(this, i)}
|
|
223
|
-
/>
|
|
224
|
-
</td>
|
|
225
|
-
<td>
|
|
226
|
-
<select
|
|
227
|
-
className="perseus-widget-dropdown"
|
|
228
|
-
value={label.alignment}
|
|
229
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
230
|
-
onChange={this.onAlignmentChange.bind(this, i)}
|
|
231
|
-
>
|
|
232
|
-
{captionAlignments.map(function (alignment, i) {
|
|
233
|
-
return (
|
|
234
|
-
<option key={"" + i} value={alignment}>
|
|
235
|
-
{alignment}
|
|
236
|
-
</option>
|
|
237
|
-
);
|
|
238
|
-
}, this)}
|
|
239
|
-
</select>
|
|
240
|
-
</td>
|
|
241
|
-
<td>
|
|
242
|
-
<a
|
|
243
|
-
href="#"
|
|
244
|
-
className="simple-button orange delete-label"
|
|
245
|
-
title="Remove this label"
|
|
246
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
247
|
-
onClick={this.removeLabel.bind(this, i)}
|
|
248
|
-
>
|
|
249
|
-
<InlineIcon {...icons.iconTrash} />
|
|
250
|
-
</a>
|
|
251
|
-
</td>
|
|
252
|
-
</tr>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
change(...args) {
|
|
257
|
-
return Changeable.change.apply(this, args);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
removeLabel(labelIndex, e) {
|
|
261
|
-
e.preventDefault();
|
|
262
|
-
const labels = [...this.props.labels];
|
|
263
|
-
labels.splice(labelIndex, 1);
|
|
264
|
-
this.props.onChange({labels: labels});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
onCoordinateChange(labelIndex, newCoordinates) {
|
|
268
|
-
const labels = this.props.labels.slice();
|
|
269
|
-
labels[labelIndex] = _.extend({}, labels[labelIndex], {
|
|
270
|
-
coordinates: newCoordinates,
|
|
271
|
-
});
|
|
272
|
-
this.props.onChange({labels: labels});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
onContentChange(labelIndex, e) {
|
|
276
|
-
const newContent = e.target.value;
|
|
277
|
-
const labels = this.props.labels.slice();
|
|
278
|
-
labels[labelIndex] = _.extend({}, labels[labelIndex], {
|
|
279
|
-
content: newContent,
|
|
280
|
-
});
|
|
281
|
-
this.props.onChange({labels: labels});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
onAlignmentChange(labelIndex, e) {
|
|
285
|
-
const newAlignment = e.target.value;
|
|
286
|
-
const labels = this.props.labels.slice();
|
|
287
|
-
labels[labelIndex] = _.extend({}, labels[labelIndex], {
|
|
288
|
-
alignment: newAlignment,
|
|
289
|
-
});
|
|
290
|
-
this.props.onChange({labels: labels});
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
setUrl(url, width, height, silent) {
|
|
294
|
-
// Because this calls into WidgetEditor._handleWidgetChange, which
|
|
295
|
-
// checks for this widget's ref to serialize it.
|
|
296
|
-
//
|
|
297
|
-
// Errors if you switch items before the `Image` from `onUrlChange`
|
|
298
|
-
// loads.
|
|
299
|
-
if (!this._isMounted) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const image = _.clone(this.props.backgroundImage);
|
|
304
|
-
image.url = url;
|
|
305
|
-
image.width = width;
|
|
306
|
-
image.height = height;
|
|
307
|
-
const box = [image.width, image.height];
|
|
308
|
-
this.props.onChange(
|
|
309
|
-
{
|
|
310
|
-
backgroundImage: image,
|
|
311
|
-
box: box,
|
|
312
|
-
},
|
|
313
|
-
null,
|
|
314
|
-
silent,
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// silently load the image when the component mounts
|
|
319
|
-
// silently update url and sizes when the image loads
|
|
320
|
-
// noisily load the image in response to the author changing it
|
|
321
|
-
async onUrlChange(url: string | undefined | null, silent: boolean) {
|
|
322
|
-
// Check if we've been passed something that looks like a URL
|
|
323
|
-
if (!url) {
|
|
324
|
-
this.setUrl(url, 0, 0, silent);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// All article content must be KA-owned!
|
|
329
|
-
if (!INTERNALLY_HOSTED_URL_RE.test(url)) {
|
|
330
|
-
this.setState({
|
|
331
|
-
backgroundImageError:
|
|
332
|
-
"Images must be from sites hosted by Khan Academy. " +
|
|
333
|
-
"Please input a Khan Academy-owned address, or use the " +
|
|
334
|
-
"Add Image tool to rehost an existing image",
|
|
335
|
-
});
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Clear previous errors
|
|
340
|
-
this.setState({backgroundImageError: ""});
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
const size = await Util.getImageSizeModern(url);
|
|
344
|
-
this.setUrl(url, size[0], size[1], true);
|
|
345
|
-
} catch (error) {
|
|
346
|
-
this.setState({
|
|
347
|
-
backgroundImageError: `There was an error loading the image URL: ${JSON.stringify(
|
|
348
|
-
error,
|
|
349
|
-
null,
|
|
350
|
-
2,
|
|
351
|
-
)}`,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
onRangeChange(type, newRange) {
|
|
357
|
-
const range = this.props.range.slice();
|
|
358
|
-
range[type] = newRange;
|
|
359
|
-
this.props.onChange({range: range});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
serialize() {
|
|
363
|
-
return EditorJsonify.serialize.call(this);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export default ImageEditor;
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/sort-comp */
|
|
2
|
-
import {components, Util} from "@khanacademy/perseus";
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import _ from "underscore";
|
|
5
|
-
|
|
6
|
-
import BlurInput from "../components/blur-input";
|
|
7
|
-
|
|
8
|
-
import type {ParsedValue, InputNumber} from "@khanacademy/perseus";
|
|
9
|
-
import type {PropsFor} from "@khanacademy/wonder-blocks-core";
|
|
10
|
-
|
|
11
|
-
const {InfoTip} = components;
|
|
12
|
-
|
|
13
|
-
const answerTypes = {
|
|
14
|
-
number: {
|
|
15
|
-
name: "Numbers",
|
|
16
|
-
forms: "integer, decimal, proper, improper, mixed",
|
|
17
|
-
},
|
|
18
|
-
decimal: {
|
|
19
|
-
name: "Decimals",
|
|
20
|
-
forms: "decimal",
|
|
21
|
-
},
|
|
22
|
-
integer: {
|
|
23
|
-
name: "Integers",
|
|
24
|
-
forms: "integer",
|
|
25
|
-
},
|
|
26
|
-
rational: {
|
|
27
|
-
name: "Fractions and mixed numbers",
|
|
28
|
-
forms: "integer, proper, improper, mixed",
|
|
29
|
-
},
|
|
30
|
-
improper: {
|
|
31
|
-
name: "Improper numbers (no mixed)",
|
|
32
|
-
forms: "integer, proper, improper",
|
|
33
|
-
},
|
|
34
|
-
mixed: {
|
|
35
|
-
name: "Mixed numbers (no improper)",
|
|
36
|
-
forms: "integer, proper, mixed",
|
|
37
|
-
},
|
|
38
|
-
percent: {
|
|
39
|
-
name: "Numbers or percents",
|
|
40
|
-
forms: "integer, decimal, proper, improper, mixed, percent",
|
|
41
|
-
},
|
|
42
|
-
pi: {
|
|
43
|
-
name: "Numbers with pi",
|
|
44
|
-
forms: "pi",
|
|
45
|
-
},
|
|
46
|
-
} as const;
|
|
47
|
-
|
|
48
|
-
type Props = {
|
|
49
|
-
value: number;
|
|
50
|
-
simplify: PropsFor<typeof InputNumber.widget>["simplify"];
|
|
51
|
-
size: PropsFor<typeof InputNumber.widget>["size"];
|
|
52
|
-
inexact: PropsFor<typeof InputNumber.widget>["reviewModeRubric"]["inexact"];
|
|
53
|
-
maxError: PropsFor<
|
|
54
|
-
typeof InputNumber.widget
|
|
55
|
-
>["reviewModeRubric"]["maxError"];
|
|
56
|
-
answerType: PropsFor<typeof InputNumber.widget>["answerType"];
|
|
57
|
-
rightAlign: PropsFor<typeof InputNumber.widget>["rightAlign"];
|
|
58
|
-
onChange: (arg1: {
|
|
59
|
-
value?: ParsedValue | 0;
|
|
60
|
-
simplify?: Props["simplify"];
|
|
61
|
-
size?: Props["size"];
|
|
62
|
-
inexact?: Props["inexact"];
|
|
63
|
-
maxError?: Props["maxError"];
|
|
64
|
-
answerType?: Props["answerType"];
|
|
65
|
-
rightAlign?: Props["rightAlign"];
|
|
66
|
-
}) => void;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
type DefaultProps = {
|
|
70
|
-
value: Props["value"];
|
|
71
|
-
simplify: Props["simplify"];
|
|
72
|
-
size: Props["size"];
|
|
73
|
-
inexact: Props["inexact"];
|
|
74
|
-
maxError: Props["maxError"];
|
|
75
|
-
answerType: Props["answerType"];
|
|
76
|
-
rightAlign: Props["rightAlign"];
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
class InputNumberEditor extends React.Component<Props> {
|
|
80
|
-
static widgetName = "input-number" as const;
|
|
81
|
-
|
|
82
|
-
static defaultProps: DefaultProps = {
|
|
83
|
-
value: 0,
|
|
84
|
-
simplify: "required",
|
|
85
|
-
size: "normal",
|
|
86
|
-
inexact: false,
|
|
87
|
-
maxError: 0.1,
|
|
88
|
-
answerType: "number",
|
|
89
|
-
rightAlign: false,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
input = React.createRef<BlurInput>();
|
|
93
|
-
|
|
94
|
-
handleAnswerChange: (arg1: string) => void = (str) => {
|
|
95
|
-
const value = Util.firstNumericalParse(str) || 0;
|
|
96
|
-
this.props.onChange({value: value});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
render(): React.ReactNode {
|
|
100
|
-
const answerTypeOptions = _.map(
|
|
101
|
-
answerTypes,
|
|
102
|
-
function (v, k) {
|
|
103
|
-
return (
|
|
104
|
-
<option value={k} key={k}>
|
|
105
|
-
{v.name}
|
|
106
|
-
</option>
|
|
107
|
-
);
|
|
108
|
-
},
|
|
109
|
-
this,
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<div>
|
|
114
|
-
<div>
|
|
115
|
-
<label>
|
|
116
|
-
Correct answer:{" "}
|
|
117
|
-
<BlurInput
|
|
118
|
-
value={"" + this.props.value}
|
|
119
|
-
onChange={this.handleAnswerChange}
|
|
120
|
-
ref={this.input}
|
|
121
|
-
/>
|
|
122
|
-
</label>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<div>
|
|
126
|
-
<label>
|
|
127
|
-
Unsimplified answers{" "}
|
|
128
|
-
<select
|
|
129
|
-
value={this.props.simplify}
|
|
130
|
-
onChange={(e) => {
|
|
131
|
-
this.props.onChange({
|
|
132
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type '"optional" | "required" | "enforced" | undefined'.
|
|
133
|
-
simplify: e.target.value,
|
|
134
|
-
});
|
|
135
|
-
}}
|
|
136
|
-
>
|
|
137
|
-
<option value="required">will not be graded</option>
|
|
138
|
-
<option value="optional">will be accepted</option>
|
|
139
|
-
<option value="enforced">
|
|
140
|
-
will be marked wrong
|
|
141
|
-
</option>
|
|
142
|
-
</select>
|
|
143
|
-
</label>
|
|
144
|
-
<InfoTip>
|
|
145
|
-
<p>
|
|
146
|
-
Normally select "will not be graded". This will give
|
|
147
|
-
the user a message saying the answer is correct but
|
|
148
|
-
not simplified. The user will then have to simplify
|
|
149
|
-
it and re-enter, but will not be penalized. (5th
|
|
150
|
-
grade and anything after)
|
|
151
|
-
</p>
|
|
152
|
-
<p>
|
|
153
|
-
Select "will be accepted" only if the user is not
|
|
154
|
-
expected to know how to simplify fractions yet.
|
|
155
|
-
(Anything prior to 5th grade)
|
|
156
|
-
</p>
|
|
157
|
-
<p>
|
|
158
|
-
Select "will be marked wrong" only if we are
|
|
159
|
-
specifically assessing the ability to simplify.
|
|
160
|
-
</p>
|
|
161
|
-
</InfoTip>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<div>
|
|
165
|
-
<label>
|
|
166
|
-
<input
|
|
167
|
-
type="checkbox"
|
|
168
|
-
checked={this.props.inexact}
|
|
169
|
-
onChange={(e) => {
|
|
170
|
-
this.props.onChange({
|
|
171
|
-
inexact: e.target.checked,
|
|
172
|
-
});
|
|
173
|
-
}}
|
|
174
|
-
/>{" "}
|
|
175
|
-
Allow inexact answers
|
|
176
|
-
</label>
|
|
177
|
-
|
|
178
|
-
<label>
|
|
179
|
-
<input /* TODO(emily): don't use a hidden checkbox
|
|
180
|
-
for alignment */
|
|
181
|
-
type="checkbox"
|
|
182
|
-
style={{visibility: "hidden"}}
|
|
183
|
-
/>
|
|
184
|
-
Max error:{" "}
|
|
185
|
-
<input
|
|
186
|
-
type="text"
|
|
187
|
-
disabled={!this.props.inexact}
|
|
188
|
-
defaultValue={this.props.maxError}
|
|
189
|
-
aria-label="Max error"
|
|
190
|
-
onBlur={(e) => {
|
|
191
|
-
const ans =
|
|
192
|
-
"" +
|
|
193
|
-
(Util.firstNumericalParse(e.target.value) ||
|
|
194
|
-
0);
|
|
195
|
-
e.target.value = ans;
|
|
196
|
-
this.props.onChange({maxError: ans});
|
|
197
|
-
}}
|
|
198
|
-
/>
|
|
199
|
-
</label>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<div>
|
|
203
|
-
Answer type:{" "}
|
|
204
|
-
<select
|
|
205
|
-
value={this.props.answerType}
|
|
206
|
-
onChange={(e) => {
|
|
207
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type '"number" | "integer" | "mixed" | "decimal" | "improper" | "percent" | "pi" | "rational" | undefined'.
|
|
208
|
-
this.props.onChange({answerType: e.target.value});
|
|
209
|
-
}}
|
|
210
|
-
aria-label="Answer type"
|
|
211
|
-
>
|
|
212
|
-
{answerTypeOptions}
|
|
213
|
-
</select>
|
|
214
|
-
<InfoTip>
|
|
215
|
-
<p>
|
|
216
|
-
Use the default "Numbers" unless the answer must be
|
|
217
|
-
in a specific form (e.g., question is about
|
|
218
|
-
converting decimals to fractions).
|
|
219
|
-
</p>
|
|
220
|
-
</InfoTip>
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
<div>
|
|
224
|
-
<label>
|
|
225
|
-
Width{" "}
|
|
226
|
-
<select
|
|
227
|
-
value={this.props.size}
|
|
228
|
-
onChange={(e) => {
|
|
229
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type '"small" | "normal" | undefined'.
|
|
230
|
-
this.props.onChange({size: e.target.value});
|
|
231
|
-
}}
|
|
232
|
-
>
|
|
233
|
-
<option value="normal">Normal (80px)</option>
|
|
234
|
-
<option value="small">Small (40px)</option>
|
|
235
|
-
</select>
|
|
236
|
-
</label>
|
|
237
|
-
<InfoTip>
|
|
238
|
-
<p>
|
|
239
|
-
Use size "Normal" for all text boxes, unless there
|
|
240
|
-
are multiple text boxes in one line and the answer
|
|
241
|
-
area is too narrow to fit them.
|
|
242
|
-
</p>
|
|
243
|
-
</InfoTip>
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
<div>
|
|
247
|
-
<label>
|
|
248
|
-
<input
|
|
249
|
-
type="checkbox"
|
|
250
|
-
checked={this.props.rightAlign}
|
|
251
|
-
onChange={(e) => {
|
|
252
|
-
this.props.onChange({
|
|
253
|
-
rightAlign: e.target.checked,
|
|
254
|
-
});
|
|
255
|
-
}}
|
|
256
|
-
/>{" "}
|
|
257
|
-
Right alignment
|
|
258
|
-
</label>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
focus: () => boolean = () => {
|
|
265
|
-
this.input.current?.focus();
|
|
266
|
-
return true;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
serialize: () => {
|
|
270
|
-
value: Props["value"];
|
|
271
|
-
simplify: Props["simplify"];
|
|
272
|
-
size: Props["size"];
|
|
273
|
-
inexact: Props["inexact"];
|
|
274
|
-
maxError: Props["maxError"];
|
|
275
|
-
answerType: Props["answerType"];
|
|
276
|
-
rightAlign: Props["rightAlign"];
|
|
277
|
-
} = () => ({
|
|
278
|
-
value: this.props.value,
|
|
279
|
-
simplify: this.props.simplify,
|
|
280
|
-
size: this.props.size,
|
|
281
|
-
inexact: this.props.inexact,
|
|
282
|
-
maxError: this.props.maxError,
|
|
283
|
-
answerType: this.props.answerType,
|
|
284
|
-
rightAlign: this.props.rightAlign,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export default InputNumberEditor;
|