Skip to content

Commit 39e40f1

Browse files
committed
WIP
1 parent 9e66968 commit 39e40f1

File tree

3 files changed

+20
-116
lines changed

3 files changed

+20
-116
lines changed

packages/perseus-editor/src/editor-page.tsx

+14-70
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
ImageUploader,
1717
Version,
1818
PerseusItem,
19+
PerseusRenderer,
1920
} from "@khanacademy/perseus";
2021
import type {KEScore} from "@khanacademy/perseus-core";
2122

@@ -30,14 +31,14 @@ type Props = {
3031
developerMode: boolean;
3132
// Source HTML for the iframe to render
3233
frameSource: string;
33-
hints?: ReadonlyArray<Hint>; // related to the question,
34+
hints: ReadonlyArray<Hint>; // related to the question,
3435
// A function which takes a file object (guaranteed to be an image) and
3536
// a callback, then calls the callback with the url where the image
3637
// will be hosted. Image drag and drop is disabled when imageUploader
3738
// is null.
3839
imageUploader?: ImageUploader;
3940
// Part of the question
40-
itemDataVersion?: Version;
41+
itemDataVersion: Version;
4142
// The content ID of the AssessmentItem being edited.
4243
itemId: string;
4344
// Whether the question is displaying as JSON or if it is
@@ -49,7 +50,7 @@ type Props = {
4950
onPreviewDeviceChange: (arg1: DeviceType) => unknown;
5051
previewDevice: DeviceType;
5152
// Initial value of the question being edited
52-
question?: any;
53+
question: PerseusRenderer;
5354
// URL of the route to show on initial load of the preview frames.
5455
previewURL: string;
5556
};
@@ -82,14 +83,16 @@ class EditorPage extends React.Component<Props, State> {
8283
super(props);
8384

8485
this.state = {
85-
// @ts-expect-error - TS2322 - Type 'Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "hints" | "question" | "answerArea" | "itemDataVersion">' is not assignable to type 'PerseusJson'.
86-
json: _.pick(
87-
this.props,
88-
"question",
89-
"answerArea",
90-
"hints",
91-
"itemDataVersion",
92-
),
86+
json: {
87+
question: this.props.question,
88+
answerArea: this.props.answerArea,
89+
hints: this.props.hints,
90+
itemDataVersion: this.props.itemDataVersion,
91+
92+
// Deprecated keys
93+
_multi: null,
94+
answer: null,
95+
},
9396
gradeMessage: "",
9497
wasAnswered: false,
9598
highlightLint: true,
@@ -102,21 +105,6 @@ class EditorPage extends React.Component<Props, State> {
102105
// TODO(scottgrant): This is a hack to remove the deprecated call to
103106
// this.isMounted() but is still considered an anti-pattern.
104107
this._isMounted = true;
105-
106-
this.updateRenderer();
107-
}
108-
109-
componentDidUpdate() {
110-
// NOTE: It is required to delay the preview update until after the
111-
// current frame, to allow for ItemEditor to render its widgets.
112-
// This then enables to serialize the widgets properties correctly,
113-
// in order to send data to the preview iframe (IframeContentRenderer).
114-
// Otherwise, widgets will render in an "empty" state in the preview.
115-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
116-
// eslint-disable-next-line no-restricted-syntax
117-
setTimeout(() => {
118-
this.updateRenderer();
119-
});
120108
}
121109

122110
componentWillUnmount() {
@@ -136,50 +124,6 @@ class EditorPage extends React.Component<Props, State> {
136124
);
137125
};
138126

139-
updateRenderer() {
140-
// Some widgets (namely the image widget) like to call onChange before
141-
// anything has actually been mounted, which causes problems here. We
142-
// just ensure don't update until we've mounted
143-
const hasEditor = !this.props.developerMode || !this.props.jsonMode;
144-
if (!this._isMounted || !hasEditor) {
145-
return;
146-
}
147-
148-
const touch =
149-
this.props.previewDevice === "phone" ||
150-
this.props.previewDevice === "tablet";
151-
const deviceBasedApiOptions: APIOptionsWithDefaults = {
152-
...this.getApiOptions(),
153-
customKeypad: touch,
154-
isMobile: touch,
155-
};
156-
157-
this.itemEditor.current?.triggerPreviewUpdate({
158-
type: "question",
159-
data: _({
160-
item: this.serialize(),
161-
apiOptions: deviceBasedApiOptions,
162-
initialHintsVisible: 0,
163-
device: this.props.previewDevice,
164-
linterContext: {
165-
contentType: "exercise",
166-
highlightLint: this.state.highlightLint,
167-
// TODO(CP-4838): is it okay to use [] as a default?
168-
paths: this.props.contentPaths || [],
169-
},
170-
reviewMode: true,
171-
legacyPerseusLint: this.itemEditor.current?.getSaveWarnings(),
172-
}).extend(
173-
_(this.props).pick(
174-
"workAreaSelector",
175-
"solutionAreaSelector",
176-
"hintsAreaSelector",
177-
"problemNum",
178-
),
179-
),
180-
});
181-
}
182-
183127
getApiOptions(): APIOptionsWithDefaults {
184128
return {
185129
...ApiOptions.defaults,

packages/perseus-editor/src/hint-editor.tsx

+5-45
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import * as React from "react";
99
import _ from "underscore";
1010

1111
import DeviceFramer from "./components/device-framer";
12+
import ContentRenderer from "./content-renderer";
1213
import Editor from "./editor";
13-
import IframeContentRenderer from "./iframe-content-renderer";
1414

1515
import type {
1616
APIOptions,
@@ -188,35 +188,6 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
188188
};
189189

190190
editor = React.createRef<HintEditor>();
191-
frame = React.createRef<IframeContentRenderer>();
192-
193-
componentDidMount() {
194-
this.updatePreview();
195-
}
196-
197-
componentDidUpdate() {
198-
this.updatePreview();
199-
}
200-
201-
updatePreview = () => {
202-
const shouldBold =
203-
this.props.isLast && !/\*\*/.test(this.props.hint.content);
204-
205-
this.frame.current?.sendNewData({
206-
type: "hint",
207-
data: {
208-
hint: this.props.hint,
209-
bold: shouldBold,
210-
pos: this.props.pos,
211-
apiOptions: this.props.apiOptions,
212-
linterContext: {
213-
contentType: "hint",
214-
highlightLint: this.props.highlightLint,
215-
paths: this.props.contentPaths,
216-
},
217-
},
218-
});
219-
};
220191

221192
getSaveWarnings = () => {
222193
return this.editor.current?.getSaveWarnings();
@@ -262,12 +233,9 @@ class CombinedHintEditor extends React.Component<CombinedHintEditorProps> {
262233
deviceType={this.props.deviceType}
263234
nochrome={true}
264235
>
265-
<IframeContentRenderer
266-
ref={this.frame}
267-
datasetKey="mobile"
268-
datasetValue={isMobile}
269-
seamless={true}
270-
url={this.props.previewURL}
236+
<ContentRenderer
237+
question={this.props.hint}
238+
apiOptions={{isMobile}}
271239
/>
272240
</DeviceFramer>
273241
</div>
@@ -409,7 +377,7 @@ class CombinedHintsEditor extends React.Component<CombinedHintsEditorProps> {
409377
const {itemId, hints} = this.props;
410378
const hintElems = _.map(
411379
hints,
412-
function (hint, i) {
380+
(hint, i) => {
413381
return (
414382
<CombinedHintEditor
415383
ref={"hintEditor" + i}
@@ -419,24 +387,16 @@ class CombinedHintsEditor extends React.Component<CombinedHintsEditorProps> {
419387
itemId={itemId}
420388
hint={hint}
421389
pos={i}
422-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
423390
imageUploader={this.props.imageUploader}
424391
// eslint-disable-next-line react/jsx-no-bind
425-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
426392
onChange={this.handleHintChange.bind(this, i)}
427393
// eslint-disable-next-line react/jsx-no-bind
428-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
429394
onRemove={this.handleHintRemove.bind(this, i)}
430395
// eslint-disable-next-line react/jsx-no-bind
431-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
432396
onMove={this.handleHintMove.bind(this, i)}
433-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
434397
deviceType={this.props.deviceType}
435-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
436398
apiOptions={this.props.apiOptions}
437-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
438399
highlightLint={this.props.highlightLint}
439-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
440400
previewURL={this.props.previewURL}
441401
// TODO(CP-4838): what should be passed here?
442402
contentPaths={[]}

packages/perseus/src/perseus-types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export type PerseusItem = {
9595
// A collection of hints to be offered to the user that support answering the question.
9696
hints: ReadonlyArray<PerseusRenderer>;
9797
// Details about the tools the user might need to answer the question
98-
answerArea: PerseusAnswerArea | null | undefined;
98+
answerArea: PerseusAnswerArea;
9999
// Multi-item should only show up in Test Prep content and it is a variant of a PerseusItem
100100
_multi: any;
101101
// The version of the item. Not used by Perseus

0 commit comments

Comments
 (0)