Skip to content

Commit 16386df

Browse files
heyzecrenovate[bot]shirsho-12mug1wara26
authored
feat: various features for VS Code (#3146)
* fix(deps): update dependency react-ace to v14 (#3136) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency npm-run-all2 to v8 (#3137) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update dependency i18next to v25 (#3138) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: update yarn.lock * feat: make editor work with playground again * fix: don't trigger mobile breakpoints when in VSC * fix: hide unnecessary buttons when in VSC * refactor: make api abstraction clearer * fix: don't hardcode constants and join paths properly * fix: make editor less buggy in VS Code by not sending Text messages from frontend to VSC * temp * feat: Send MCQ questions to VSC extension - New MCQQuestion message type * Revert "temp" This reverts commit 18c4597. * fix: server changes never get sent to extension * Added message passing to vscode on chapter select * added more info in the message for chapterSelect * Update mcq handling functionality * Fix: Merge issues * Feat: basic voting question type support * Feat: basic voting question type support * Simplify vscode mcq message Remove the question description from the McqQuestion payload as it is not used. * Contest code view functionality * feat: update VSC message to support sidepanel * Fix: run button vanishes on zoom * Fix: MCQ id * Feat: reset functionality for vsc * re-add changes removed during merge; lint * fix: try using window.location.pathname * Added message to trigger save assessment * fix oopsie with isVscode * Revert "fix oopsie with isVscode" This reverts commit 230e689. * update yarn.lock * Reapply "fix oopsie with isVscode" This reverts commit f204b4a. * fix: add isVscode to useEffect deps (eslint) * fix: squelch warning (eslint) --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Shirshajit Sen Gupta <shirshajit@gmail.com> Co-authored-by: mug1wara26 <aloysiusgsq@gmail.com>
1 parent 40278af commit 16386df

File tree

10 files changed

+268
-69
lines changed

10 files changed

+268
-69
lines changed

src/commons/application/Application.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const Application: React.FC = () => {
8888
};
8989
}
9090

91-
const message = Messages.ExtensionPing();
91+
const message = Messages.ExtensionPing(window.origin);
9292
sendToWebview(message);
9393

9494
window.addEventListener('message', event => {
@@ -116,17 +116,27 @@ const Application: React.FC = () => {
116116
}
117117
break;
118118
case MessageTypeNames.Text:
119-
const code = message.code;
119+
const { workspaceLocation, code } = message;
120120
console.log(`FRONTEND: TextMessage: ${code}`);
121-
// TODO: Don't change ace editor directly
122-
// const elements = document.getElementsByClassName('react-ace');
123-
// if (elements.length === 0) {
124-
// return;
125-
// }
126-
// // @ts-expect-error: ace is not available at compile time
127-
// const editor = ace.edit(elements[0]);
128-
// editor.setValue(code);
129-
dispatch(WorkspaceActions.updateEditorValue('assessment', 0, code));
121+
dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, code));
122+
break;
123+
case MessageTypeNames.EvalEditor:
124+
dispatch(WorkspaceActions.evalEditor(message.workspaceLocation));
125+
break;
126+
case MessageTypeNames.Navigate:
127+
window.location.pathname = message.route;
128+
// TODO: Figure out why this doesn't work, this is faster in theory
129+
// navigate(message.route);
130+
break;
131+
case MessageTypeNames.McqQuestion:
132+
dispatch(WorkspaceActions.showMcqPane(message.workspaceLocation, message.options));
133+
break;
134+
case MessageTypeNames.McqAnswer:
135+
console.log(`FRONTEND: MCQAnswerMessage: ${message}`);
136+
dispatch(SessionActions.submitAnswer(message.questionId, message.choice));
137+
break;
138+
case MessageTypeNames.AssessmentAnswer:
139+
dispatch(SessionActions.submitAnswer(message.questionId, message.answer));
130140
break;
131141
}
132142
});

src/commons/assessment/Assessment.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import {
2020
import { IconNames } from '@blueprintjs/icons';
2121
import classNames from 'classnames';
2222
import { sortBy } from 'lodash';
23-
import React, { useMemo, useState } from 'react';
23+
import React, { useEffect, useMemo, useState } from 'react';
2424
import { useDispatch } from 'react-redux';
2525
import { Navigate, NavLink, useLoaderData, useParams } from 'react-router';
2626
import { numberRegExp } from 'src/features/academy/AcademyTypes';
27+
import Messages, { sendToWebview } from 'src/features/vscode/messages';
2728
import classes from 'src/styles/Academy.module.scss';
2829

2930
import defaultCoverImage from '../../assets/default_cover_image.jpg';
@@ -60,6 +61,23 @@ const Assessment: React.FC = () => {
6061
const { courseId, role, assessmentOverviews: assessmentOverviewsUnfiltered } = useSession();
6162
const dispatch = useDispatch();
6263

64+
useEffect(() => {
65+
if (assessmentOverviewsUnfiltered && courseId) {
66+
sendToWebview(
67+
Messages.NotifyAssessmentsOverview(
68+
assessmentOverviewsUnfiltered.map(oa => ({
69+
type: oa.type,
70+
closeAt: oa.closeAt,
71+
id: oa.id,
72+
isPublished: oa.isPublished,
73+
title: oa.title
74+
})),
75+
courseId
76+
)
77+
);
78+
}
79+
}, [assessmentOverviewsUnfiltered, courseId]);
80+
6381
const toggleClosedAssessments = () => setShowClosedAssessments(!showClosedAssessments);
6482
const toggleOpenAssessments = () => setShowOpenedAssessments(!showOpenedAssessments);
6583
const toggleUpcomingAssessments = () => setShowUpcomingAssessments(!showUpcomingAssessments);

src/commons/assessmentWorkspace/AssessmentWorkspace.tsx

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
9696
const [sessionId, setSessionId] = useState('');
9797
const [isSubmitted, setIsSubmitted] = useState(false);
9898
const { isMobileBreakpoint } = useResponsive();
99+
const isVscode = useTypedSelector(state => state.vscode.isVscode);
99100

100101
const assessment = useTypedSelector(state => state.session.assessments[props.assessmentId]);
101102
const assessmentOverviews = useTypedSelector(state => state.session.assessmentOverviews);
@@ -244,10 +245,10 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
244245
useEffect(() => {
245246
if (!selectedTab) return;
246247

247-
if (!isMobileBreakpoint && mobileOnlyTabIds.includes(selectedTab)) {
248+
if ((!isMobileBreakpoint || isVscode) && mobileOnlyTabIds.includes(selectedTab)) {
248249
setSelectedTab(SideContentType.questionOverview);
249250
}
250-
}, [isMobileBreakpoint, props, selectedTab, setSelectedTab]);
251+
}, [isMobileBreakpoint, isVscode, props, selectedTab, setSelectedTab]);
251252

252253
/* ==================
253254
onChange handlers
@@ -391,7 +392,38 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
391392
);
392393
handleClearContext(question.library, true);
393394
handleUpdateHasUnsavedChanges(false);
394-
sendToWebview(Messages.NewEditor(`assessment${assessment.id}`, props.questionId, ''));
395+
396+
const chapter = question.library.chapter;
397+
const questionType = question.type;
398+
399+
switch (questionType) {
400+
case QuestionTypes.mcq:
401+
const mcqQuestionData = question;
402+
sendToWebview(
403+
Messages.McqQuestion(
404+
workspaceLocation,
405+
`assessment${assessment.id}`,
406+
mcqQuestionData.id,
407+
chapter,
408+
mcqQuestionData.choices.map(choice => choice.content)
409+
)
410+
);
411+
break;
412+
case QuestionTypes.programming || QuestionTypes.voting:
413+
const prepend = question.prepend;
414+
const code = question.answer ?? question.solutionTemplate;
415+
sendToWebview(
416+
Messages.NewEditor(
417+
workspaceLocation,
418+
`assessment${assessment.id}`,
419+
props.questionId,
420+
chapter,
421+
prepend,
422+
code
423+
)
424+
);
425+
break;
426+
}
395427
if (options.editorValue) {
396428
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
397429
handleEditorValueChange(0, options.editorValue);
@@ -416,6 +448,18 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
416448
const handleContestEntryClick = (_submissionId: number, answer: string) => {
417449
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
418450
handleEditorValueChange(0, answer);
451+
// Hacky way to view the editor, might cause issues
452+
sendToWebview(
453+
Messages.NewEditor(
454+
workspaceLocation,
455+
`submission${_submissionId}`,
456+
questionId,
457+
question.library.chapter,
458+
'',
459+
answer
460+
)
461+
);
462+
//
419463
};
420464

421465
const tabs: SideContentTab[] = [
@@ -736,9 +780,10 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
736780
);
737781

738782
return {
739-
editorButtons: !isMobileBreakpoint
740-
? [runButton, saveButton, resetButton, chapterSelect]
741-
: [saveButton, resetButton],
783+
editorButtons:
784+
!isMobileBreakpoint || isVscode
785+
? [runButton, saveButton, resetButton, chapterSelect]
786+
: [saveButton, resetButton],
742787
flowButtons: [previousButton, questionView, nextButton]
743788
};
744789
};
@@ -885,6 +930,14 @@ It is safe to close this window.`}
885930
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
886931
);
887932
handleUpdateHasUnsavedChanges(true);
933+
if (isVscode) {
934+
sendToWebview(
935+
Messages.ResetEditor(
936+
workspaceLocation,
937+
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
938+
)
939+
);
940+
}
888941
}}
889942
options={{ minimal: false, intent: Intent.DANGER }}
890943
/>
@@ -968,7 +1021,7 @@ It is safe to close this window.`}
9681021
{submissionOverlay}
9691022
{overlay}
9701023
{resetTemplateOverlay}
971-
{!isMobileBreakpoint ? (
1024+
{isVscode || !isMobileBreakpoint ? (
9721025
<Workspace {...workspaceProps} />
9731026
) : (
9741027
<MobileWorkspace {...mobileWorkspaceProps} />

src/commons/workspace/Workspace.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,19 @@ const Workspace: React.FC<WorkspaceProps> = props => {
227227
<Resizable {...editorResizableProps()}>{createWorkspaceInput(props)}</Resizable>
228228
)}
229229
<div className="right-parent" ref={setFullscreenRefs}>
230-
<Tooltip
231-
className="fullscreen-button"
232-
content={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
233-
portalContainer={fullscreenContainerRef.current || undefined}
234-
>
235-
<Button
236-
minimal
237-
icon={isFullscreen ? IconNames.MINIMIZE : IconNames.MAXIMIZE}
238-
onClick={toggleFullscreen}
239-
/>
240-
</Tooltip>
230+
{!isVscode && (
231+
<Tooltip
232+
className="fullscreen-button"
233+
content={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
234+
portalContainer={fullscreenContainerRef.current || undefined}
235+
>
236+
<Button
237+
minimal
238+
icon={isFullscreen ? IconNames.MINIMIZE : IconNames.MAXIMIZE}
239+
onClick={toggleFullscreen}
240+
/>
241+
</Tooltip>
242+
)}
241243
{props.sideContentIsResizeable === undefined || props.sideContentIsResizeable
242244
? resizableSideContent
243245
: sideContent}

src/commons/workspace/WorkspaceActions.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,8 @@ const newActions = createActions('workspace', {
111111
updateEditorValue: (
112112
workspaceLocation: WorkspaceLocation,
113113
editorTabIndex: number,
114-
newEditorValue: string,
115-
isFromVscode: boolean = false
116-
) => ({ workspaceLocation, editorTabIndex, newEditorValue, isFromVscode }),
114+
newEditorValue: string
115+
) => ({ workspaceLocation, editorTabIndex, newEditorValue }),
117116
setEditorBreakpoint: (
118117
workspaceLocation: WorkspaceLocation,
119118
editorTabIndex: number,
@@ -241,6 +240,10 @@ const newActions = createActions('workspace', {
241240
workspaceLocation: WorkspaceLocation,
242241
storyEnv?: string
243242
) => ({ errorMsg, workspaceLocation, storyEnv }),
243+
showMcqPane: (workspaceLocation: WorkspaceLocation, options: string[]) => ({
244+
workspaceLocation,
245+
options
246+
}),
244247
toggleUsingCse: (usingCse: boolean, workspaceLocation: WorkspaceLocationsWithTools) => ({
245248
usingCse,
246249
workspaceLocation

src/commons/workspace/__tests__/WorkspaceActions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ test('updateEditorValue generates correct action object', () => {
258258
payload: {
259259
workspaceLocation: assessmentWorkspace,
260260
editorTabIndex,
261-
newEditorValue,
262-
isFromVscode: false
261+
newEditorValue
263262
}
264263
});
265264
});

src/commons/workspace/reducers/editorReducer.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
2-
import Messages, { sendToWebview } from 'src/features/vscode/messages';
32

43
import WorkspaceActions from '../WorkspaceActions';
54
import { getWorkspaceLocation } from '../WorkspaceReducer';
@@ -53,9 +52,6 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder<WorkspaceMa
5352
}
5453

5554
state[workspaceLocation].editorTabs[editorTabIndex].value = newEditorValue;
56-
if (!action.payload.isFromVscode) {
57-
sendToWebview(Messages.Text(newEditorValue));
58-
}
5955
})
6056
.addCase(WorkspaceActions.setEditorBreakpoint, (state, action) => {
6157
const workspaceLocation = getWorkspaceLocation(action);

0 commit comments

Comments
 (0)