1
1
import * as vscode from "vscode" ;
2
2
// Allow using JSX within this file by overriding our own createElement function
3
3
import React from "../utils/FakeReact" ;
4
+ import ReactDOMServer from "react-dom/server" ;
4
5
5
6
import Messages , {
6
7
MessageType ,
@@ -14,6 +15,7 @@ import { Editor } from "../utils/editor";
14
15
import { FRONTEND_ELEMENT_ID } from "../constants" ;
15
16
import { client } from "../extension" ;
16
17
import _ from "lodash" ;
18
+ import { McqPanelWithLogging as McqPanel } from "../webview/components/McqPanel" ;
17
19
18
20
let mcqPanel : vscode . WebviewPanel | null = null ;
19
21
@@ -44,37 +46,49 @@ async function handleMessage(
44
46
case MessageTypeNames . ExtensionPing :
45
47
sendToFrontend ( panel , Messages . ExtensionPong ( null ) ) ;
46
48
break ;
47
- case MessageTypeNames . MCQQuestion :
48
- {
49
- if ( mcqPanel === null ) {
50
- mcqPanel = vscode . window . createWebviewPanel (
51
- "mcq-question-panel" ,
52
- `Question ${ message . questionId + 1 } ` ,
53
- vscode . ViewColumn . One ,
54
- { enableScripts : true , retainContextWhenHidden : true } ,
55
- ) ;
56
- mcqPanel . onDidDispose ( ( ) => {
57
- mcqPanel = null ;
58
- } ) ;
59
- }
60
- mcqPanel . title = `Question ${ message . questionId + 1 } ` ;
61
- mcqPanel . iconPath = vscode . Uri . joinPath (
62
- context . extensionUri ,
63
- "assets" ,
64
- "icon.png" ,
49
+ case MessageTypeNames . MCQQuestion : {
50
+ if ( mcqPanel === null ) {
51
+ mcqPanel = vscode . window . createWebviewPanel (
52
+ "mcq-question-panel" ,
53
+ `Question ${ message . questionId + 1 } ` ,
54
+ vscode . ViewColumn . One ,
55
+ { enableScripts : true , retainContextWhenHidden : true } ,
65
56
) ;
66
57
67
- // Cast message to ensure properties exist
68
- const mcqMsg = message as any ;
69
- mcqPanel . webview . html = getMcqHtml (
70
- mcqPanel . webview ,
71
- mcqMsg . question ,
72
- mcqMsg . options ,
73
- mcqMsg . questionId ,
74
- ) ;
75
- mcqPanel . reveal ( vscode . ViewColumn . One ) ;
58
+ mcqPanel . onDidDispose ( ( ) => {
59
+ mcqPanel = null ;
60
+ } ) ;
76
61
}
62
+ mcqPanel . title = `Question ${ message . questionId + 1 } ` ;
63
+ mcqPanel . iconPath = vscode . Uri . joinPath (
64
+ context . extensionUri ,
65
+ "assets" ,
66
+ "icon.png" ,
67
+ ) ;
68
+ let activePanel = await McqPanel ( {
69
+ data : {
70
+ assessmentName : message . assessmentName ?? "" ,
71
+ questionId : message . questionId ,
72
+ question : message . question ,
73
+ choices : message . options ,
74
+ workspaceLocation : message . workspaceLocation ?? "assessment" ,
75
+ } ,
76
+ } ) ;
77
+ setWebviewContent (
78
+ mcqPanel ,
79
+ context ,
80
+ < div
81
+ // @ts -ignore: Our FakeReact doesn't modify the style attribute
82
+ style = "width: 100%; height: calc(100vh - 10px)"
83
+ id = "mcq-panel"
84
+ >
85
+ { ReactDOMServer . renderToString ( activePanel ) }
86
+ </ div > ,
87
+ ) ;
88
+ mcqPanel . reveal ( vscode . ViewColumn . One ) ;
89
+
77
90
break ;
91
+ }
78
92
79
93
case MessageTypeNames . NewEditor :
80
94
// console.log(message.questionType + " questionType \n");
@@ -172,7 +186,6 @@ export async function showPanel(context: vscode.ExtensionContext) {
172
186
173
187
const frontendUrl = new URL ( "/playground" , config . frontendBaseUrl ) . href ;
174
188
175
- // equivalent to panel.webview.html = ...
176
189
setWebviewContent (
177
190
panel ,
178
191
context ,
@@ -201,141 +214,6 @@ export async function showPanel(context: vscode.ExtensionContext) {
201
214
) ;
202
215
}
203
216
204
- // TODO: Move this to a util file
205
- function getMcqHtml (
206
- _webview : vscode . Webview ,
207
- question : string ,
208
- options : string [ ] ,
209
- questionId : string ,
210
- ) : string {
211
- return `<!DOCTYPE html>
212
- <html lang="en">
213
- <head>
214
- <meta charset="UTF-8" />
215
- <meta name="viewport" content="width=device-width, initial-scale=1" />
216
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-eval' 'unsafe-inline' https://unpkg.com; style-src 'unsafe-inline' https://unpkg.com;" />
217
- <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
218
- <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
219
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
220
- <script src="https://unpkg.com/marked@4.0.0/marked.min.js"></script>
221
- <script>window.marked.setOptions({ breaks: true });</script>
222
- <style>
223
- .mcq-option {
224
- color: black !important;
225
- }
226
- .mcq-option p {
227
- margin: 0;
228
- display: inline;
229
- }
230
- </style>
231
- </head>
232
- <body>
233
- <div id="root"></div>
234
- <script type="text/babel" data-type="module">
235
- const { useState } = React;
236
-
237
- function McqPanel({ question, options, questionId }) {
238
- const [selected, setSelected] = useState(null);
239
-
240
- const handleSubmit = (e) => {
241
- e.preventDefault();
242
- if (selected === null) return;
243
- const vscode = acquireVsCodeApi();
244
- vscode.postMessage({ type: 'answer', answer: selected });
245
- };
246
-
247
- return (
248
- <div style={{
249
- padding: '1rem',
250
- fontFamily: 'sans-serif',
251
- maxWidth: '800px',
252
- margin: '0 auto'
253
- }}>
254
- <h3>Question {parseInt(questionId) + 1}</h3>
255
- <div dangerouslySetInnerHTML={{ __html: window.marked.parse(question) }} />
256
- <form onSubmit={handleSubmit}>
257
- <ul style={{
258
- listStyle: 'none',
259
- padding: 0,
260
- margin: '1rem 0 1.5rem 0'
261
- }}>
262
- {options.map((option, index) => (
263
- <li
264
- key={index}
265
- style={{
266
- margin: '0.5rem 0',
267
- padding: '0.75rem',
268
- border: '1px solid #e1e4e8',
269
- borderRadius: '6px',
270
- backgroundColor: selected === index ? '#f6f8fa' : 'white',
271
- cursor: 'pointer',
272
- transition: 'background-color 0.2s'
273
- }}
274
- onClick={() => setSelected(index)}
275
- >
276
- <label style={{
277
- display: 'flex',
278
- alignItems: 'center',
279
- cursor: 'pointer',
280
- margin: 0
281
- }}>
282
- <input
283
- type="radio"
284
- name="mcq-option"
285
- checked={selected === index}
286
- onChange={() => setSelected(index)}
287
- style={{
288
- marginRight: '0.75rem',
289
- width: '1.25rem',
290
- height: '1.25rem',
291
- cursor: 'pointer'
292
- }}
293
- />
294
- <span
295
- className="mcq-option"
296
- dangerouslySetInnerHTML={{ __html: window.marked.parse(option) }}
297
- />
298
- </label>
299
- </li>
300
- ))}
301
- </ul>
302
- <button
303
- type="submit"
304
- disabled={selected === null}
305
- style={{
306
- padding: '0.5rem 1.5rem',
307
- backgroundColor: selected !== null ? '#2ea043' : '#94d3a2',
308
- color: 'white',
309
- border: 'none',
310
- borderRadius: '6px',
311
- cursor: selected !== null ? 'pointer' : 'not-allowed',
312
- fontSize: '1rem',
313
- fontWeight: '500',
314
- transition: 'background-color 0.2s',
315
- opacity: selected !== null ? 1 : 0.7
316
- }}
317
- >
318
- Submit Answer
319
- </button>
320
- </form>
321
- </div>
322
- );
323
- }
324
-
325
- // Render the component
326
- const root = ReactDOM.createRoot(document.getElementById('root'));
327
- root.render(
328
- <McqPanel
329
- question="${ question . replace ( / " / g, """ ) } "
330
- questionId="${ questionId } "
331
- options={${ JSON . stringify ( options ) } }
332
- />
333
- );
334
- </script>
335
- </body>
336
- </html>` ;
337
- }
338
-
339
217
export async function sendToFrontendWrapped ( message : MessageType ) {
340
218
if ( ! panel ) {
341
219
console . error ( "ERROR: panel is not set" ) ;
0 commit comments