@khanacademy/perseus-editor 4.1.5 → 4.1.7
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 -1155
- 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,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Direct image labeling widget editor.
|
|
3
|
-
*
|
|
4
|
-
* Label on image widget enables creating more natural, conceptual questions
|
|
5
|
-
* that involve the use of images, and enable learners to demonstrate their
|
|
6
|
-
* knowledge by directly interacting with the image.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {EditorJsonify, Util} from "@khanacademy/perseus";
|
|
10
|
-
import {StyleSheet, css} from "aphrodite";
|
|
11
|
-
import * as React from "react";
|
|
12
|
-
|
|
13
|
-
import FormWrappedTextField from "../components/form-wrapped-text-field";
|
|
14
|
-
|
|
15
|
-
import AnswerChoices from "./label-image/answer-choices";
|
|
16
|
-
import Behavior from "./label-image/behavior";
|
|
17
|
-
import QuestionMarkers from "./label-image/question-markers";
|
|
18
|
-
import SelectImage from "./label-image/select-image";
|
|
19
|
-
|
|
20
|
-
import type {MarkerType} from "@khanacademy/perseus";
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
// List of answer choices to label question image with.
|
|
24
|
-
choices: ReadonlyArray<string>;
|
|
25
|
-
// The question image properties.
|
|
26
|
-
imageAlt: string;
|
|
27
|
-
imageUrl: string;
|
|
28
|
-
imageWidth: number;
|
|
29
|
-
imageHeight: number;
|
|
30
|
-
// The list of label markers on the question image.
|
|
31
|
-
markers: ReadonlyArray<MarkerType>;
|
|
32
|
-
// Whether multiple answer choices may be selected for markers.
|
|
33
|
-
multipleAnswers: boolean;
|
|
34
|
-
// Whether to hide answer choices from user instructions.
|
|
35
|
-
hideChoicesFromInstructions: boolean;
|
|
36
|
-
// Callback for when a widget prop is changed.
|
|
37
|
-
onChange: (options: any) => void;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
class LabelImageEditor extends React.Component<Props> {
|
|
41
|
-
_questionMarkers: QuestionMarkers | null | undefined;
|
|
42
|
-
|
|
43
|
-
static defaultProps: {
|
|
44
|
-
choices: ReadonlyArray<any>;
|
|
45
|
-
hideChoicesFromInstructions: boolean;
|
|
46
|
-
imageAlt: string;
|
|
47
|
-
imageHeight: number;
|
|
48
|
-
imageUrl: string;
|
|
49
|
-
imageWidth: number;
|
|
50
|
-
markers: ReadonlyArray<any>;
|
|
51
|
-
multipleAnswers: boolean;
|
|
52
|
-
} = {
|
|
53
|
-
choices: [],
|
|
54
|
-
imageAlt: "",
|
|
55
|
-
imageUrl: "",
|
|
56
|
-
imageWidth: 0,
|
|
57
|
-
imageHeight: 0,
|
|
58
|
-
markers: [],
|
|
59
|
-
multipleAnswers: false,
|
|
60
|
-
hideChoicesFromInstructions: false,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
static widgetName = "label-image" as const;
|
|
64
|
-
|
|
65
|
-
componentDidUpdate(prevProps: Props) {
|
|
66
|
-
const coordsToMarkers: Record<string, any> = {};
|
|
67
|
-
|
|
68
|
-
prevProps.markers.forEach(
|
|
69
|
-
(marker) => (coordsToMarkers[`${marker.x}.${marker.y}`] = marker),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Find the newly added marker indices.
|
|
73
|
-
const newIndices = this.props.markers
|
|
74
|
-
.map((marker, index) =>
|
|
75
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
76
|
-
coordsToMarkers.hasOwnProperty(`${marker.x}.${marker.y}`)
|
|
77
|
-
? -1
|
|
78
|
-
: index,
|
|
79
|
-
)
|
|
80
|
-
.filter((index) => index !== -1);
|
|
81
|
-
|
|
82
|
-
// Automatically reveal their dropdowns as a prompt to the content
|
|
83
|
-
// creator to select answers and set the ARIA label.
|
|
84
|
-
if (newIndices.length && this._questionMarkers) {
|
|
85
|
-
this._questionMarkers.openDropdownForMarkerIndices(newIndices);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getSaveWarnings: () => ReadonlyArray<any | string> = () => {
|
|
90
|
-
const {choices, imageAlt, imageUrl, markers} = this.props;
|
|
91
|
-
|
|
92
|
-
const warnings: Array<string> = [];
|
|
93
|
-
|
|
94
|
-
if (choices.length < 2) {
|
|
95
|
-
warnings.push("Question requires at least two answer choices");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!imageUrl) {
|
|
99
|
-
warnings.push("Image is not specified for question");
|
|
100
|
-
} else if (!imageAlt) {
|
|
101
|
-
warnings.push("Question image has no alt text");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!markers.length) {
|
|
105
|
-
warnings.push("Question has no markers, to label answers on image");
|
|
106
|
-
} else {
|
|
107
|
-
let numNoAnswers = 0;
|
|
108
|
-
let numNoLabels = 0;
|
|
109
|
-
|
|
110
|
-
for (const marker of markers) {
|
|
111
|
-
if (!marker.answers.length) {
|
|
112
|
-
numNoAnswers++;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!marker.label) {
|
|
116
|
-
numNoLabels++;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (numNoAnswers) {
|
|
121
|
-
warnings.push(
|
|
122
|
-
`Question has ${numNoAnswers} markers with no ` +
|
|
123
|
-
"answers selected",
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (numNoLabels) {
|
|
128
|
-
warnings.push(
|
|
129
|
-
`Question has ${numNoLabels} markers with no ` +
|
|
130
|
-
"ARIA label",
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return warnings;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
serialize(): any {
|
|
139
|
-
return EditorJsonify.serialize.call(this);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
handleImageChange: (url: string) => void = (url: string) => {
|
|
143
|
-
this.props.onChange({
|
|
144
|
-
imageUrl: url,
|
|
145
|
-
// Initially reset image size when URL is changed so it can be later
|
|
146
|
-
// measured.
|
|
147
|
-
imageWidth: 0,
|
|
148
|
-
imageHeight: 0,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (url) {
|
|
152
|
-
Util.getImageSize(url, (width, height) => {
|
|
153
|
-
this.props.onChange({
|
|
154
|
-
imageWidth: width,
|
|
155
|
-
imageHeight: height,
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
handleAltChange: (alt: string) => void = (alt: string) => {
|
|
162
|
-
this.props.onChange({imageAlt: alt});
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
handleChoicesChange: (choices: ReadonlyArray<string>) => void = (
|
|
166
|
-
choices: ReadonlyArray<string>,
|
|
167
|
-
) => {
|
|
168
|
-
this.props.onChange({choices});
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
handleMarkersChange: (markers: ReadonlyArray<MarkerType>) => void = (
|
|
172
|
-
markers: ReadonlyArray<MarkerType>,
|
|
173
|
-
) => {
|
|
174
|
-
this.props.onChange({markers});
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
handleBehaviorChange: (options?: any) => void = (options: any) => {
|
|
178
|
-
this.props.onChange(options);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
render(): React.ReactNode {
|
|
182
|
-
const {
|
|
183
|
-
choices,
|
|
184
|
-
imageAlt,
|
|
185
|
-
imageUrl,
|
|
186
|
-
imageWidth,
|
|
187
|
-
imageHeight,
|
|
188
|
-
markers,
|
|
189
|
-
multipleAnswers,
|
|
190
|
-
hideChoicesFromInstructions,
|
|
191
|
-
} = this.props;
|
|
192
|
-
|
|
193
|
-
const imageSelected = imageUrl && imageWidth > 0 && imageHeight > 0;
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<div>
|
|
197
|
-
<SelectImage onChange={this.handleImageChange} url={imageUrl} />
|
|
198
|
-
|
|
199
|
-
<div className={css(styles.smallSpacer)} />
|
|
200
|
-
|
|
201
|
-
{imageSelected && (
|
|
202
|
-
<FormWrappedTextField
|
|
203
|
-
placeholder="Alt text (for screen readers)"
|
|
204
|
-
onChange={(e) => this.handleAltChange(e.target.value)}
|
|
205
|
-
value={imageAlt}
|
|
206
|
-
width="100%"
|
|
207
|
-
/>
|
|
208
|
-
)}
|
|
209
|
-
|
|
210
|
-
<div className={css(styles.largeSpacer)} />
|
|
211
|
-
|
|
212
|
-
<QuestionMarkers
|
|
213
|
-
choices={choices}
|
|
214
|
-
imageUrl={imageSelected ? imageUrl : ""}
|
|
215
|
-
imageWidth={imageWidth}
|
|
216
|
-
imageHeight={imageHeight}
|
|
217
|
-
markers={markers}
|
|
218
|
-
onChange={this.handleMarkersChange}
|
|
219
|
-
ref={(node) => (this._questionMarkers = node)}
|
|
220
|
-
/>
|
|
221
|
-
|
|
222
|
-
<div className={css(styles.largeSpacer)} />
|
|
223
|
-
|
|
224
|
-
<AnswerChoices
|
|
225
|
-
choices={choices}
|
|
226
|
-
onChange={this.handleChoicesChange}
|
|
227
|
-
/>
|
|
228
|
-
|
|
229
|
-
<div className={css(styles.largeSpacer)} />
|
|
230
|
-
|
|
231
|
-
<Behavior
|
|
232
|
-
preferredPopoverDirection="NONE"
|
|
233
|
-
multipleAnswers={multipleAnswers}
|
|
234
|
-
hideChoicesFromInstructions={hideChoicesFromInstructions}
|
|
235
|
-
onChange={this.handleBehaviorChange}
|
|
236
|
-
/>
|
|
237
|
-
</div>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const styles = StyleSheet.create({
|
|
243
|
-
largeSpacer: {
|
|
244
|
-
height: 32,
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
smallSpacer: {
|
|
248
|
-
height: 16,
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
export default LabelImageEditor;
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/* eslint-disable react/sort-comp */
|
|
3
|
-
import {components, Changeable, EditorJsonify} from "@khanacademy/perseus";
|
|
4
|
-
import PropTypes from "prop-types";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import _ from "underscore";
|
|
7
|
-
|
|
8
|
-
const {InfoTip, NumberInput, PropCheckBox} = components;
|
|
9
|
-
|
|
10
|
-
const MAX_SIZE = 8;
|
|
11
|
-
|
|
12
|
-
// styling
|
|
13
|
-
const CELL_PADDING = 5;
|
|
14
|
-
|
|
15
|
-
const TABLE_STYLE = {
|
|
16
|
-
display: "table",
|
|
17
|
-
tableLayout: "fixed",
|
|
18
|
-
} as const;
|
|
19
|
-
|
|
20
|
-
const ROW_STYLE = {
|
|
21
|
-
display: "table-row",
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
const CELL_STYLE = {
|
|
25
|
-
display: "table-cell",
|
|
26
|
-
padding: CELL_PADDING,
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
const BASE_TILE_STYLE = {
|
|
30
|
-
borderRadius: 10,
|
|
31
|
-
cursor: "pointer",
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
const PATTERNS = {
|
|
35
|
-
plus: () => [
|
|
36
|
-
[false, true, false],
|
|
37
|
-
[true, true, true],
|
|
38
|
-
[false, true, false],
|
|
39
|
-
],
|
|
40
|
-
x: () => [
|
|
41
|
-
[true, false, true],
|
|
42
|
-
[false, true, false],
|
|
43
|
-
[true, false, true],
|
|
44
|
-
],
|
|
45
|
-
"plus/x": (iter) => {
|
|
46
|
-
return iter % 2 ? PATTERNS.x() : PATTERNS.plus();
|
|
47
|
-
},
|
|
48
|
-
} as const;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Clamps value to an integer in the range [min, max]
|
|
52
|
-
*/
|
|
53
|
-
const clampToInt = function (value: number, min: number, max: number) {
|
|
54
|
-
value = Math.floor(value);
|
|
55
|
-
value = Math.max(value, min);
|
|
56
|
-
value = Math.min(value, max);
|
|
57
|
-
return value;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Returns a copy of the tiles, with tiles flipped according to
|
|
61
|
-
// whether or not their y, x position satisfies the predicate
|
|
62
|
-
const flipTilesPredicate = (
|
|
63
|
-
oldCells: any,
|
|
64
|
-
predicate: (y?: any, x?: any) => boolean,
|
|
65
|
-
) => {
|
|
66
|
-
return _.map(oldCells, (row, y) => {
|
|
67
|
-
return _.map(row, (cell, x) => {
|
|
68
|
-
return predicate(y, x) ? !cell : cell;
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
type TileProps = any;
|
|
74
|
-
|
|
75
|
-
// A single glowy cell
|
|
76
|
-
class Tile extends React.Component<TileProps> {
|
|
77
|
-
static propTypes = {
|
|
78
|
-
value: PropTypes.bool.isRequired,
|
|
79
|
-
size: PropTypes.number.isRequired,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
render(): React.ReactNode {
|
|
83
|
-
const color = this.props.value ? "#55dd55" : "#115511";
|
|
84
|
-
const style = _.extend({}, BASE_TILE_STYLE, {
|
|
85
|
-
width: this.props.size,
|
|
86
|
-
height: this.props.size,
|
|
87
|
-
backgroundColor: color,
|
|
88
|
-
});
|
|
89
|
-
return <div style={style} onClick={this._flip} />;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
_flip = () => {
|
|
93
|
-
this.props.onChange(!this.props.value);
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type TileGridProps = any;
|
|
98
|
-
|
|
99
|
-
// A grid of glowy cells
|
|
100
|
-
class TileGrid extends React.Component<TileGridProps> {
|
|
101
|
-
static propTypes = {
|
|
102
|
-
cells: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.bool)).isRequired,
|
|
103
|
-
size: PropTypes.number.isRequired,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
render(): React.ReactNode {
|
|
107
|
-
return (
|
|
108
|
-
<div style={TABLE_STYLE} className="no-select">
|
|
109
|
-
{_.map(this.props.cells, (row, y) => {
|
|
110
|
-
return (
|
|
111
|
-
<div key={y} style={ROW_STYLE}>
|
|
112
|
-
{_.map(row, (cell, x) => {
|
|
113
|
-
return (
|
|
114
|
-
<div key={x} style={CELL_STYLE}>
|
|
115
|
-
<Tile
|
|
116
|
-
value={cell}
|
|
117
|
-
size={this.props.size}
|
|
118
|
-
onChange={_.partial(
|
|
119
|
-
this.props.onChange,
|
|
120
|
-
y,
|
|
121
|
-
x,
|
|
122
|
-
)}
|
|
123
|
-
/>
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
126
|
-
})}
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
})}
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
type LightsPuzzleEditorProps = any;
|
|
136
|
-
|
|
137
|
-
// The widget editor
|
|
138
|
-
class LightsPuzzleEditor extends React.Component<LightsPuzzleEditorProps> {
|
|
139
|
-
static propTypes = {
|
|
140
|
-
...Changeable.propTypes,
|
|
141
|
-
startCells: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.bool)),
|
|
142
|
-
flipPattern: PropTypes.string.isRequired,
|
|
143
|
-
gradeIncompleteAsWrong: PropTypes.bool.isRequired,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
static widgetName = "lights-puzzle" as const;
|
|
147
|
-
|
|
148
|
-
static defaultProps: LightsPuzzleEditorProps = {
|
|
149
|
-
startCells: [
|
|
150
|
-
[false, false, false],
|
|
151
|
-
[false, false, false],
|
|
152
|
-
[false, false, false],
|
|
153
|
-
],
|
|
154
|
-
flipPattern: "plus",
|
|
155
|
-
gradeIncompleteAsWrong: false,
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
_height: () => number = () => {
|
|
159
|
-
return this.props.startCells.length;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
_width: () => number = () => {
|
|
163
|
-
if (this.props.startCells.length !== 0) {
|
|
164
|
-
return this.props.startCells[0].length;
|
|
165
|
-
}
|
|
166
|
-
return 0; // default to 0
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
change: (arg1: any, arg2: any, arg3: any) => any = (...args) => {
|
|
170
|
-
return Changeable.change.apply(this, args);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
render(): React.ReactNode {
|
|
174
|
-
return (
|
|
175
|
-
<div>
|
|
176
|
-
<div>
|
|
177
|
-
Width:
|
|
178
|
-
<NumberInput
|
|
179
|
-
value={this._width()}
|
|
180
|
-
placeholder={5}
|
|
181
|
-
onChange={this._changeWidth}
|
|
182
|
-
/>
|
|
183
|
-
{", "}
|
|
184
|
-
Height:
|
|
185
|
-
<NumberInput
|
|
186
|
-
value={this._height()}
|
|
187
|
-
placeholder={5}
|
|
188
|
-
onChange={this._changeHeight}
|
|
189
|
-
/>
|
|
190
|
-
</div>
|
|
191
|
-
<div>
|
|
192
|
-
Flip pattern:
|
|
193
|
-
<select
|
|
194
|
-
value={this.props.flipPattern}
|
|
195
|
-
// @ts-expect-error - TS2322 - Type '(arg1: ChangeEvent<HTMLInputElement>) => void' is not assignable to type 'ChangeEventHandler<HTMLSelectElement>'.
|
|
196
|
-
onChange={this._handlePatternChange}
|
|
197
|
-
>
|
|
198
|
-
{_.map(_.keys(PATTERNS), (pattern, i) => {
|
|
199
|
-
return (
|
|
200
|
-
<option value={pattern} key={i}>
|
|
201
|
-
{pattern}
|
|
202
|
-
</option>
|
|
203
|
-
);
|
|
204
|
-
})}
|
|
205
|
-
</select>
|
|
206
|
-
</div>
|
|
207
|
-
<div>
|
|
208
|
-
Grade incomplete puzzles as wrong:{" "}
|
|
209
|
-
<PropCheckBox
|
|
210
|
-
gradeIncompleteAsWrong={
|
|
211
|
-
this.props.gradeIncompleteAsWrong
|
|
212
|
-
}
|
|
213
|
-
onChange={this.props.onChange}
|
|
214
|
-
/>
|
|
215
|
-
<InfoTip>
|
|
216
|
-
By default, incomplete puzzles are graded as empty.
|
|
217
|
-
</InfoTip>
|
|
218
|
-
</div>
|
|
219
|
-
<div>Starting configuration:</div>
|
|
220
|
-
<div style={{overflowX: "auto"}}>
|
|
221
|
-
<TileGrid
|
|
222
|
-
cells={this.props.startCells}
|
|
223
|
-
size={50}
|
|
224
|
-
onChange={this._switchTile}
|
|
225
|
-
/>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
_handlePatternChange: (arg1: React.ChangeEvent<HTMLInputElement>) => void =
|
|
232
|
-
(e) => {
|
|
233
|
-
// @ts-expect-error - TS2554 - Expected 3 arguments, but got 2.
|
|
234
|
-
this.change("flipPattern", e.target.value);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
_changeWidth: (arg1: number) => void = (newWidth) => {
|
|
238
|
-
newWidth = clampToInt(newWidth, 1, MAX_SIZE);
|
|
239
|
-
this._truncateCells(newWidth, this._height());
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
_changeHeight: (arg1: number) => void = (newHeight) => {
|
|
243
|
-
newHeight = clampToInt(newHeight, 1, MAX_SIZE);
|
|
244
|
-
this._truncateCells(this._width(), newHeight);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
_truncateCells: (arg1: number, arg2: number) => void = (
|
|
248
|
-
newWidth,
|
|
249
|
-
newHeight,
|
|
250
|
-
) => {
|
|
251
|
-
const newCells = _.times(newHeight, (y) => {
|
|
252
|
-
return _.times(newWidth, (x) => {
|
|
253
|
-
// explicitly cast the result to a boolean with !!
|
|
254
|
-
return !!(
|
|
255
|
-
this.props.startCells[y] && this.props.startCells[y][x]
|
|
256
|
-
);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
|
|
261
|
-
this.change({startCells: newCells});
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
_switchTile: (arg1: number, arg2: number) => void = (tileY, tileX) => {
|
|
265
|
-
const newCells = flipTilesPredicate(this.props.startCells, (y, x) => {
|
|
266
|
-
return y === tileY && x === tileX;
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// @ts-expect-error - TS2554 - Expected 3 arguments, but got 1.
|
|
270
|
-
this.change({startCells: newCells});
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
serialize: () => any = () => {
|
|
274
|
-
return EditorJsonify.serialize.call(this);
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export default LightsPuzzleEditor;
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/forbid-prop-types, react/sort-comp */
|
|
2
|
-
import {components} from "@khanacademy/perseus";
|
|
3
|
-
import PropTypes from "prop-types";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import _ from "underscore";
|
|
6
|
-
|
|
7
|
-
const {InfoTip, PropCheckBox, TextListEditor} = components;
|
|
8
|
-
|
|
9
|
-
type Props = any;
|
|
10
|
-
|
|
11
|
-
class MatcherEditor extends React.Component<Props> {
|
|
12
|
-
static propTypes = {
|
|
13
|
-
left: PropTypes.array,
|
|
14
|
-
right: PropTypes.array,
|
|
15
|
-
labels: PropTypes.array,
|
|
16
|
-
orderMatters: PropTypes.bool,
|
|
17
|
-
padding: PropTypes.bool,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
static widgetName = "matcher" as const;
|
|
21
|
-
|
|
22
|
-
static defaultProps: Props = {
|
|
23
|
-
left: ["$x$", "$y$", "$z$"],
|
|
24
|
-
right: ["$1$", "$2$", "$3$"],
|
|
25
|
-
labels: ["test", "label"],
|
|
26
|
-
orderMatters: false,
|
|
27
|
-
padding: true,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
render(): React.ReactNode {
|
|
31
|
-
return (
|
|
32
|
-
<div className="perseus-matcher-editor">
|
|
33
|
-
<div>
|
|
34
|
-
{" "}
|
|
35
|
-
Correct answer:{" "}
|
|
36
|
-
<InfoTip>
|
|
37
|
-
<p>
|
|
38
|
-
Enter the correct answers here. The preview on the
|
|
39
|
-
right will show the cards in a randomized order,
|
|
40
|
-
which is how the student will see them.
|
|
41
|
-
</p>
|
|
42
|
-
</InfoTip>
|
|
43
|
-
</div>
|
|
44
|
-
<div className="perseus-clearfix">
|
|
45
|
-
<TextListEditor
|
|
46
|
-
options={this.props.left}
|
|
47
|
-
onChange={(options, cb) => {
|
|
48
|
-
this.props.onChange({left: options}, cb);
|
|
49
|
-
}}
|
|
50
|
-
layout="vertical"
|
|
51
|
-
/>
|
|
52
|
-
<TextListEditor
|
|
53
|
-
options={this.props.right}
|
|
54
|
-
onChange={(options, cb) => {
|
|
55
|
-
this.props.onChange({right: options}, cb);
|
|
56
|
-
}}
|
|
57
|
-
layout="vertical"
|
|
58
|
-
/>
|
|
59
|
-
</div>
|
|
60
|
-
<span>
|
|
61
|
-
{" "}
|
|
62
|
-
Labels:{" "}
|
|
63
|
-
<InfoTip>
|
|
64
|
-
<p>These are entirely optional.</p>
|
|
65
|
-
</InfoTip>
|
|
66
|
-
</span>
|
|
67
|
-
<div>
|
|
68
|
-
<input
|
|
69
|
-
type="text"
|
|
70
|
-
defaultValue={this.props.labels[0]}
|
|
71
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
72
|
-
onChange={this.onLabelChange.bind(this, 0)}
|
|
73
|
-
/>
|
|
74
|
-
<input
|
|
75
|
-
type="text"
|
|
76
|
-
defaultValue={this.props.labels[1]}
|
|
77
|
-
// eslint-disable-next-line react/jsx-no-bind
|
|
78
|
-
onChange={this.onLabelChange.bind(this, 1)}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
<div>
|
|
82
|
-
<PropCheckBox
|
|
83
|
-
label="Order of the matched pairs matters:"
|
|
84
|
-
orderMatters={this.props.orderMatters}
|
|
85
|
-
onChange={this.props.onChange}
|
|
86
|
-
/>
|
|
87
|
-
<InfoTip>
|
|
88
|
-
<p>
|
|
89
|
-
With this option enabled, only the order provided
|
|
90
|
-
above will be treated as correct. This is useful
|
|
91
|
-
when ordering is significant, such as in the context
|
|
92
|
-
of a proof.
|
|
93
|
-
</p>
|
|
94
|
-
<p>
|
|
95
|
-
If disabled, pairwise matching is sufficient. To
|
|
96
|
-
make this clear, the left column becomes fixed in
|
|
97
|
-
the provided order and only the cards in the right
|
|
98
|
-
column can be moved.
|
|
99
|
-
</p>
|
|
100
|
-
</InfoTip>
|
|
101
|
-
</div>
|
|
102
|
-
<div>
|
|
103
|
-
<PropCheckBox
|
|
104
|
-
label="Padding:"
|
|
105
|
-
padding={this.props.padding}
|
|
106
|
-
onChange={this.props.onChange}
|
|
107
|
-
/>
|
|
108
|
-
<InfoTip>
|
|
109
|
-
<p>
|
|
110
|
-
Padding is good for text, but not needed for images.
|
|
111
|
-
</p>
|
|
112
|
-
</InfoTip>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
onLabelChange: (
|
|
119
|
-
arg1: number,
|
|
120
|
-
arg2: React.ChangeEvent<HTMLInputElement>,
|
|
121
|
-
) => void = (index, e) => {
|
|
122
|
-
const labels = _.clone(this.props.labels);
|
|
123
|
-
labels[index] = e.target.value;
|
|
124
|
-
this.props.onChange({labels: labels});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
getSaveWarnings: () => ReadonlyArray<string> = () => {
|
|
128
|
-
if (this.props.left.length !== this.props.right.length) {
|
|
129
|
-
return [
|
|
130
|
-
"The two halves of the matcher have different numbers" +
|
|
131
|
-
" of cards.",
|
|
132
|
-
];
|
|
133
|
-
}
|
|
134
|
-
return [];
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
serialize: any = () => {
|
|
138
|
-
return _.pick(
|
|
139
|
-
this.props,
|
|
140
|
-
"left",
|
|
141
|
-
"right",
|
|
142
|
-
"labels",
|
|
143
|
-
"orderMatters",
|
|
144
|
-
"padding",
|
|
145
|
-
);
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export default MatcherEditor;
|