@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,290 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
2
|
-
/* eslint-disable react/no-unsafe */
|
|
3
|
-
/**
|
|
4
|
-
* Controlled editor image marker on the question image.
|
|
5
|
-
*
|
|
6
|
-
* Allows to select which label choices are the answers for each marker, using
|
|
7
|
-
* the dropdown component.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {globalConstants, globalStyles} from "@khanacademy/perseus";
|
|
11
|
-
import {StyleSheet, css} from "aphrodite";
|
|
12
|
-
import * as React from "react";
|
|
13
|
-
|
|
14
|
-
import Option, {OptionGroup} from "../../components/dropdown-option";
|
|
15
|
-
import FormWrappedTextField from "../../components/form-wrapped-text-field";
|
|
16
|
-
|
|
17
|
-
import type {MarkerType} from "@khanacademy/perseus";
|
|
18
|
-
|
|
19
|
-
const {colors, borderRadius} = globalStyles;
|
|
20
|
-
|
|
21
|
-
type Props = MarkerType & {
|
|
22
|
-
// The list of possible answer choices.
|
|
23
|
-
choices: ReadonlyArray<string>;
|
|
24
|
-
// Callback for when any of the marker props are changed.
|
|
25
|
-
onChange: (marker: MarkerType) => void;
|
|
26
|
-
// Callback to remove marker from the question image.
|
|
27
|
-
onRemove: () => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type State = {
|
|
31
|
-
// Whether answer choices dropdown is shown, controlled by the user clicking
|
|
32
|
-
// on the marker icon.
|
|
33
|
-
showDropdown: boolean;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default class Marker extends React.Component<Props, State> {
|
|
37
|
-
_marker: HTMLElement | null | undefined;
|
|
38
|
-
|
|
39
|
-
constructor(props: Props) {
|
|
40
|
-
super(props);
|
|
41
|
-
|
|
42
|
-
this.state = {
|
|
43
|
-
showDropdown: false,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
componentDidMount() {
|
|
48
|
-
document.addEventListener("click", this.handleClick, true);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
52
|
-
const {answers} = this.props;
|
|
53
|
-
|
|
54
|
-
// Exclude those answers that are no longer present in choices.
|
|
55
|
-
const filteredAnswers = answers.filter((answer) =>
|
|
56
|
-
nextProps.choices.includes(answer),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (JSON.stringify(answers) !== JSON.stringify(filteredAnswers)) {
|
|
60
|
-
// Update marker on the next frame when these props take affect.
|
|
61
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
62
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
63
|
-
setTimeout(() => this.updateMarker({answers: filteredAnswers}));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
componentWillUnmount() {
|
|
68
|
-
document.removeEventListener("click", this.handleClick, true);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
openDropdown() {
|
|
72
|
-
this.setState({showDropdown: true});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
updateMarker(props: any) {
|
|
76
|
-
const {answers, label, onChange, x, y} = this.props;
|
|
77
|
-
|
|
78
|
-
onChange({
|
|
79
|
-
answers,
|
|
80
|
-
label,
|
|
81
|
-
x,
|
|
82
|
-
y,
|
|
83
|
-
// Overwrite with updated prop(s).
|
|
84
|
-
...props,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
handleClick: (e: MouseEvent) => void = (e: MouseEvent) => {
|
|
89
|
-
const {showDropdown} = this.state;
|
|
90
|
-
|
|
91
|
-
if (this._marker === e.target) {
|
|
92
|
-
this.setState({showDropdown: !showDropdown});
|
|
93
|
-
} else if (showDropdown) {
|
|
94
|
-
// Close dropdown if click event was registered anywhere outside it.
|
|
95
|
-
if (this._marker && !this._marker.contains(e.target as any)) {
|
|
96
|
-
// Ensure other listeners are not triggered on click event that
|
|
97
|
-
// closes the dropdown. A specific case this addresses is when
|
|
98
|
-
// user clicks on question image to close dropdown, a new marker
|
|
99
|
-
// will be created, this is bad for usability.
|
|
100
|
-
e.stopPropagation();
|
|
101
|
-
|
|
102
|
-
this.setState({showDropdown: false});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
handleLabelChange: (e: React.ChangeEvent<HTMLInputElement>) => void = (
|
|
108
|
-
e,
|
|
109
|
-
) => {
|
|
110
|
-
this.updateMarker({label: e.target.value});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
handleSelectAnswer: (toggleAnswer: string) => void = (
|
|
114
|
-
toggleAnswer: string,
|
|
115
|
-
) => {
|
|
116
|
-
let {answers} = this.props;
|
|
117
|
-
|
|
118
|
-
if (answers.includes(toggleAnswer)) {
|
|
119
|
-
answers = answers.filter((answer) => answer !== toggleAnswer);
|
|
120
|
-
} else {
|
|
121
|
-
answers = [...answers, toggleAnswer];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.updateMarker({answers});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
render(): React.ReactNode {
|
|
128
|
-
const {answers, choices, label, onRemove, x, y} = this.props;
|
|
129
|
-
|
|
130
|
-
const {showDropdown} = this.state;
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<div
|
|
134
|
-
className={css(
|
|
135
|
-
styles.marker,
|
|
136
|
-
answers.length > 0 && styles.markerWithAnswers,
|
|
137
|
-
showDropdown && styles.markerSelected,
|
|
138
|
-
)}
|
|
139
|
-
ref={(node) => (this._marker = node)}
|
|
140
|
-
style={{
|
|
141
|
-
left: `${x}%`,
|
|
142
|
-
top: `${y}%`,
|
|
143
|
-
}}
|
|
144
|
-
title={
|
|
145
|
-
"Click to select marker answers or to delete marker. " +
|
|
146
|
-
"Repositioning marker is not implemented."
|
|
147
|
-
}
|
|
148
|
-
>
|
|
149
|
-
{showDropdown && (
|
|
150
|
-
<div>
|
|
151
|
-
<div
|
|
152
|
-
className={css(
|
|
153
|
-
styles.dropdownBody,
|
|
154
|
-
styles.dropdownPositionWithArrow,
|
|
155
|
-
)}
|
|
156
|
-
>
|
|
157
|
-
<Option value="" onClick={() => onRemove()}>
|
|
158
|
-
Delete marker
|
|
159
|
-
</Option>
|
|
160
|
-
|
|
161
|
-
<hr className={css(styles.dividerHorizontal)} />
|
|
162
|
-
|
|
163
|
-
<OptionGroup
|
|
164
|
-
onSelected={this.handleSelectAnswer}
|
|
165
|
-
// TODO(WB-1096): make selectedValues immutable in wonder-blocks
|
|
166
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
167
|
-
selectedValues={answers}
|
|
168
|
-
>
|
|
169
|
-
{choices.map((choice) => (
|
|
170
|
-
<Option key={choice} value={choice}>
|
|
171
|
-
{choice}
|
|
172
|
-
</Option>
|
|
173
|
-
))}
|
|
174
|
-
</OptionGroup>
|
|
175
|
-
|
|
176
|
-
<div className={css(styles.labelContainer)}>
|
|
177
|
-
<FormWrappedTextField
|
|
178
|
-
placeholder="ARIA label (for screen readers)"
|
|
179
|
-
onChange={this.handleLabelChange}
|
|
180
|
-
value={label}
|
|
181
|
-
width="100%"
|
|
182
|
-
/>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const styles = StyleSheet.create({
|
|
193
|
-
marker: {
|
|
194
|
-
position: "absolute",
|
|
195
|
-
|
|
196
|
-
boxSizing: "content-box",
|
|
197
|
-
|
|
198
|
-
width: 16,
|
|
199
|
-
height: 16,
|
|
200
|
-
marginLeft: -8,
|
|
201
|
-
marginTop: -8,
|
|
202
|
-
|
|
203
|
-
cursor: "pointer",
|
|
204
|
-
|
|
205
|
-
background:
|
|
206
|
-
"linear-gradient(to bottom, rgba(33, 36, 44, 0.2), rgba(33, 36, 44, 0.5))",
|
|
207
|
-
|
|
208
|
-
border: "solid 2px #ffffff",
|
|
209
|
-
borderRadius: 16,
|
|
210
|
-
|
|
211
|
-
boxShadow: "0 2px 10px 0 rgba(33, 36, 44, 0.1)",
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
markerSelected: {
|
|
215
|
-
width: 28,
|
|
216
|
-
height: 28,
|
|
217
|
-
marginLeft: -12,
|
|
218
|
-
marginTop: -12,
|
|
219
|
-
|
|
220
|
-
border: "none",
|
|
221
|
-
borderRadius: 28,
|
|
222
|
-
|
|
223
|
-
// Render selected marker border as inset.
|
|
224
|
-
"::before": {
|
|
225
|
-
content: "''",
|
|
226
|
-
display: "block",
|
|
227
|
-
|
|
228
|
-
width: 20,
|
|
229
|
-
height: 20,
|
|
230
|
-
marginLeft: 2,
|
|
231
|
-
marginTop: 2,
|
|
232
|
-
|
|
233
|
-
border: "solid 2px #ffffff",
|
|
234
|
-
borderRadius: 20,
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
markerWithAnswers: {
|
|
239
|
-
background: "#1865f2",
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
dropdownPositionWithArrow: {
|
|
243
|
-
// Position dropdown to the top right of the marker.
|
|
244
|
-
left: 46,
|
|
245
|
-
bottom: -12,
|
|
246
|
-
|
|
247
|
-
// With an arrow pointing left towards the marker.
|
|
248
|
-
"::before": {
|
|
249
|
-
content: "''",
|
|
250
|
-
display: "block",
|
|
251
|
-
position: "absolute",
|
|
252
|
-
|
|
253
|
-
width: 0,
|
|
254
|
-
height: 0,
|
|
255
|
-
left: -16,
|
|
256
|
-
bottom: 8,
|
|
257
|
-
|
|
258
|
-
borderRight: `solid 16px ${colors.gray98}`,
|
|
259
|
-
borderTop: "solid 16px transparent",
|
|
260
|
-
borderBottom: "solid 16px transparent",
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
labelContainer: {
|
|
265
|
-
padding: 4,
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
dividerHorizontal: {
|
|
269
|
-
height: 0,
|
|
270
|
-
margin: 0,
|
|
271
|
-
|
|
272
|
-
border: `solid ${colors.gray85}`,
|
|
273
|
-
borderWidth: "0 0 1px",
|
|
274
|
-
|
|
275
|
-
boxShadow: "none",
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
// TODO (josh): This should probably be its own component, rather than
|
|
279
|
-
// sharing styles this way.
|
|
280
|
-
dropdownBody: {
|
|
281
|
-
position: "absolute",
|
|
282
|
-
border: "solid 1px rgba(0, 0, 0, 0.1)",
|
|
283
|
-
zIndex: globalConstants.zindexDropdown,
|
|
284
|
-
color: colors.gray17,
|
|
285
|
-
backgroundColor: colors.gray98,
|
|
286
|
-
borderRadius: borderRadius,
|
|
287
|
-
maxHeight: 320,
|
|
288
|
-
cursor: "pointer",
|
|
289
|
-
},
|
|
290
|
-
});
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Controlled component to display question image and create markers to
|
|
3
|
-
* label image with answers from list of choices.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {Util, Dependencies, globalStyles} from "@khanacademy/perseus";
|
|
7
|
-
import {StyleSheet, css} from "aphrodite";
|
|
8
|
-
import * as React from "react";
|
|
9
|
-
|
|
10
|
-
import Marker from "./marker";
|
|
11
|
-
|
|
12
|
-
import type {MarkerType} from "@khanacademy/perseus";
|
|
13
|
-
|
|
14
|
-
const {colors, typography} = globalStyles;
|
|
15
|
-
|
|
16
|
-
type Props = {
|
|
17
|
-
// The list of possible answers in a specific order.
|
|
18
|
-
choices: ReadonlyArray<string>;
|
|
19
|
-
// The question image properties.
|
|
20
|
-
imageUrl: string;
|
|
21
|
-
imageWidth: number;
|
|
22
|
-
imageHeight: number;
|
|
23
|
-
// The list of markers placed on the question image.
|
|
24
|
-
markers: ReadonlyArray<MarkerType>;
|
|
25
|
-
// Callback for when any of markers change.
|
|
26
|
-
onChange: (markers: ReadonlyArray<MarkerType>) => void;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export default class QuestionMarkers extends React.Component<Props> {
|
|
30
|
-
_markers: Array<Marker | null | undefined> = [];
|
|
31
|
-
|
|
32
|
-
openDropdownForMarkerIndices(indices: ReadonlyArray<number>) {
|
|
33
|
-
// Open answer selection dropdown for each of the specified markers.
|
|
34
|
-
indices.forEach((index) => {
|
|
35
|
-
if (this._markers[index]) {
|
|
36
|
-
// @ts-expect-error - TS2533 - Object is possibly 'null' or 'undefined'.
|
|
37
|
-
this._markers[index].openDropdown();
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
handleImageDoubleClick: (e: React.MouseEvent) => void = (
|
|
43
|
-
e: React.MouseEvent,
|
|
44
|
-
) => {
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
|
|
47
|
-
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
48
|
-
|
|
49
|
-
// Calculate marker position on the question image as percentage offset
|
|
50
|
-
// rounded to one decimal place.
|
|
51
|
-
const x =
|
|
52
|
-
Math.round(((e.clientX - rect.left) / rect.width) * 1000) / 10;
|
|
53
|
-
const y =
|
|
54
|
-
Math.round(((e.clientY - rect.top) / rect.height) * 1000) / 10;
|
|
55
|
-
|
|
56
|
-
const {markers, onChange} = this.props;
|
|
57
|
-
|
|
58
|
-
// Add the new marker to the end of the list.
|
|
59
|
-
onChange([
|
|
60
|
-
...markers,
|
|
61
|
-
{
|
|
62
|
-
answers: [],
|
|
63
|
-
label: "",
|
|
64
|
-
x,
|
|
65
|
-
y,
|
|
66
|
-
},
|
|
67
|
-
]);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
render(): React.ReactNode {
|
|
71
|
-
const {choices, imageUrl, imageWidth, imageHeight, markers, onChange} =
|
|
72
|
-
this.props;
|
|
73
|
-
|
|
74
|
-
const staticUrl = Dependencies.getDependencies().staticUrl;
|
|
75
|
-
|
|
76
|
-
// TODO(michaelpolyak): If a Graphie image is specified, only its SVG
|
|
77
|
-
// layer will be rendered, as when rendering the full Graphie its
|
|
78
|
-
// dynamic content may overflow the widget editor, CP-253
|
|
79
|
-
return (
|
|
80
|
-
<div>
|
|
81
|
-
<div className={css(styles.title)}>Markers</div>
|
|
82
|
-
|
|
83
|
-
<div className={css(styles.subtitle)}>
|
|
84
|
-
{imageUrl ? (
|
|
85
|
-
<span>
|
|
86
|
-
Double-click on the image to add a marker.
|
|
87
|
-
<br />
|
|
88
|
-
<br />
|
|
89
|
-
Markers are read by screen readers in the order that
|
|
90
|
-
you add them here, so add in a logical order for the
|
|
91
|
-
learner (e.g. sequentially, clockwise). You can test
|
|
92
|
-
order by using keyboard tabbing.
|
|
93
|
-
</span>
|
|
94
|
-
) : (
|
|
95
|
-
"Upload an image to place markers."
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
{imageUrl && (
|
|
100
|
-
<div
|
|
101
|
-
className={css(styles.markersCanvas)}
|
|
102
|
-
style={{
|
|
103
|
-
maxWidth: imageWidth,
|
|
104
|
-
maxHeight: imageHeight,
|
|
105
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
<img
|
|
108
|
-
alt=""
|
|
109
|
-
className={css(styles.image)}
|
|
110
|
-
src={staticUrl(Util.getRealImageUrl(imageUrl))}
|
|
111
|
-
onDoubleClick={this.handleImageDoubleClick}
|
|
112
|
-
/>
|
|
113
|
-
|
|
114
|
-
{markers.map((marker, index) => (
|
|
115
|
-
<Marker
|
|
116
|
-
{...marker}
|
|
117
|
-
choices={choices}
|
|
118
|
-
key={`${marker.x}.${marker.y}`}
|
|
119
|
-
// Update marker in the list.
|
|
120
|
-
onChange={(marker) =>
|
|
121
|
-
onChange([
|
|
122
|
-
...markers.slice(0, index),
|
|
123
|
-
marker,
|
|
124
|
-
...markers.slice(index + 1),
|
|
125
|
-
])
|
|
126
|
-
}
|
|
127
|
-
// Remove marker from the list.
|
|
128
|
-
onRemove={() =>
|
|
129
|
-
onChange([
|
|
130
|
-
...markers.slice(0, index),
|
|
131
|
-
...markers.slice(index + 1),
|
|
132
|
-
])
|
|
133
|
-
}
|
|
134
|
-
ref={(node) => (this._markers[index] = node)}
|
|
135
|
-
/>
|
|
136
|
-
))}
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const styles = StyleSheet.create({
|
|
145
|
-
title: {
|
|
146
|
-
...typography.bodyXsmallBold,
|
|
147
|
-
|
|
148
|
-
marginBottom: 6,
|
|
149
|
-
|
|
150
|
-
color: colors.gray17,
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
subtitle: {
|
|
154
|
-
...typography.labelSmall,
|
|
155
|
-
|
|
156
|
-
marginBottom: 12,
|
|
157
|
-
|
|
158
|
-
color: colors.gray68,
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
markersCanvas: {
|
|
162
|
-
position: "relative",
|
|
163
|
-
|
|
164
|
-
border: "solid 1px rgba(33, 36, 44, 0.16)",
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
image: {
|
|
168
|
-
// This makes `markersCanvas` have the same height as the image,
|
|
169
|
-
// enabling to place markers precisely as they're positioned using
|
|
170
|
-
// percentage offsets.
|
|
171
|
-
display: "block",
|
|
172
|
-
|
|
173
|
-
// Contain question image to the widget editor.
|
|
174
|
-
maxWidth: "100%",
|
|
175
|
-
},
|
|
176
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Controlled component for selecting the question image, for answer labeling.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {globalStyles} from "@khanacademy/perseus";
|
|
6
|
-
import Button from "@khanacademy/wonder-blocks-button";
|
|
7
|
-
import {StyleSheet, css} from "aphrodite";
|
|
8
|
-
import * as React from "react";
|
|
9
|
-
|
|
10
|
-
import FormWrappedTextField from "../../components/form-wrapped-text-field";
|
|
11
|
-
|
|
12
|
-
const {colors, typography} = globalStyles;
|
|
13
|
-
|
|
14
|
-
type Props = {
|
|
15
|
-
// Callback for when image URL is changed.
|
|
16
|
-
onChange: (url: string) => void;
|
|
17
|
-
// The selected image URL.
|
|
18
|
-
url: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const SelectImage = ({onChange, url}: Props): React.ReactElement => (
|
|
22
|
-
<div>
|
|
23
|
-
<div className={css(styles.title)}>Image</div>
|
|
24
|
-
|
|
25
|
-
<div className={css(styles.components)}>
|
|
26
|
-
<FormWrappedTextField
|
|
27
|
-
placeholder="URL"
|
|
28
|
-
grow={1}
|
|
29
|
-
onChange={(e) => onChange(e.target.value)}
|
|
30
|
-
value={url}
|
|
31
|
-
/>
|
|
32
|
-
|
|
33
|
-
<div className={css(styles.spacer)} />
|
|
34
|
-
|
|
35
|
-
<Button
|
|
36
|
-
// TODO(michaelpolyak): Design calls for uploading an image from
|
|
37
|
-
// the widget. This functionality already exists in
|
|
38
|
-
// `perseus-one-package/item-controls.jsx`, it's not obvious if
|
|
39
|
-
// it can be triggered from within the widget, or requires
|
|
40
|
-
// a similar implementation locally, in which case consider
|
|
41
|
-
// refactoring `perseus-admin-package/image-upload-dialog.jsx`
|
|
42
|
-
// as common utility, CP-118
|
|
43
|
-
disabled={!url}
|
|
44
|
-
aria-label={
|
|
45
|
-
url
|
|
46
|
-
? ""
|
|
47
|
-
: "Not implemented. Use the 'Add Image' button in " +
|
|
48
|
-
"the editor to upload image, then copy the URL here."
|
|
49
|
-
}
|
|
50
|
-
onClick={() => onChange("")}
|
|
51
|
-
style={styles.btn}
|
|
52
|
-
>
|
|
53
|
-
{url ? "Remove" : "Upload"}
|
|
54
|
-
</Button>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const styles = StyleSheet.create({
|
|
60
|
-
title: {
|
|
61
|
-
...typography.bodyXsmallBold,
|
|
62
|
-
|
|
63
|
-
marginBottom: 6,
|
|
64
|
-
|
|
65
|
-
color: colors.gray17,
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
components: {
|
|
69
|
-
display: "flex",
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
spacer: {
|
|
73
|
-
width: 16,
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
btn: {
|
|
77
|
-
minWidth: 90,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export default SelectImage;
|