Skip to content

feat: various features for VS Code #3146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c594962
fix(deps): update dependency react-ace to v14 (#3136)
renovate[bot] Jun 16, 2025
1f7f36e
chore(deps): update dependency npm-run-all2 to v8 (#3137)
renovate[bot] Jun 16, 2025
1e1bf1d
fix(deps): update dependency i18next to v25 (#3138)
renovate[bot] Jun 16, 2025
f91764b
chore: update yarn.lock
heyzec Jun 16, 2025
5ccd67f
feat: make editor work with playground again
heyzec Mar 26, 2025
9090706
fix: don't trigger mobile breakpoints when in VSC
heyzec Mar 26, 2025
bc5a2cd
fix: hide unnecessary buttons when in VSC
heyzec Mar 26, 2025
8654d75
refactor: make api abstraction clearer
heyzec Apr 1, 2025
040d53f
fix: don't hardcode constants and join paths properly
heyzec Apr 1, 2025
e07539a
fix: make editor less buggy in VS Code
heyzec Apr 14, 2025
18c4597
temp
heyzec Jun 16, 2025
c02d153
feat: Send MCQ questions to VSC extension
shirsho-12 Jun 16, 2025
2ab1c33
Revert "temp"
heyzec Jun 19, 2025
75fd859
fix: server changes never get sent to extension
heyzec Jun 19, 2025
ea11ea8
Added message passing to vscode on chapter select
mug1wara26 Jun 20, 2025
9bb54a9
added more info in the message for chapterSelect
mug1wara26 Jun 20, 2025
3de5cee
Update mcq handling functionality
shirsho-12 Jun 23, 2025
a799a18
Merge branch 'vscode/next' of https://github.com/source-academy/front…
shirsho-12 Jun 23, 2025
37ad18f
Fix: Merge issues
shirsho-12 Jun 23, 2025
705b31a
Feat: basic voting question type support
shirsho-12 Jun 24, 2025
d13ebaf
Feat: basic voting question type support
shirsho-12 Jun 24, 2025
30e712d
Merge branch 'vscode/next' of https://github.com/source-academy/front…
shirsho-12 Jun 24, 2025
1fa93ba
Merge branch 'vscode/next' of https://github.com/source-academy/front…
shirsho-12 Jun 24, 2025
6010f1a
Merge branch 'vscode/next' of https://github.com/source-academy/front…
shirsho-12 Jun 24, 2025
8b47473
Simplify vscode mcq message
shirsho-12 Jun 24, 2025
03b83cb
Contest code view functionality
shirsho-12 Jun 25, 2025
631195c
feat: update VSC message to support sidepanel
heyzec Jun 19, 2025
1047df1
Fix: run button vanishes on zoom
shirsho-12 Jun 26, 2025
b855ffa
Merge branch 'vscode/next' of https://github.com/source-academy/front…
shirsho-12 Jun 26, 2025
31b6461
Fix: MCQ id
shirsho-12 Jun 26, 2025
7b5d34d
Feat: reset functionality for vsc
shirsho-12 Jun 26, 2025
ec5c9a3
re-add changes removed during merge; lint
heyzec Jun 19, 2025
7067588
fix: try using window.location.pathname
heyzec Jun 27, 2025
c122be5
Added message to trigger save assessment
mug1wara26 Jun 27, 2025
179e4f4
Merge branch 'vscode/next' of github.com:source-academy/frontend into…
mug1wara26 Jun 27, 2025
772d98a
Merge branch 'master' into vscode/next
heyzec Jun 28, 2025
230e689
fix oopsie with isVscode
heyzec Jun 28, 2025
f204b4a
Revert "fix oopsie with isVscode"
heyzec Jun 28, 2025
db441df
update yarn.lock
heyzec Jun 28, 2025
6beecc0
Reapply "fix oopsie with isVscode"
heyzec Jun 28, 2025
be3be99
fix: add isVscode to useEffect deps (eslint)
heyzec Jun 28, 2025
e7595df
fix: squelch warning (eslint)
heyzec Jun 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions src/commons/application/Application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const Application: React.FC = () => {
};
}

const message = Messages.ExtensionPing();
const message = Messages.ExtensionPing(window.origin);
sendToWebview(message);

window.addEventListener('message', event => {
Expand Down Expand Up @@ -116,17 +116,27 @@ const Application: React.FC = () => {
}
break;
case MessageTypeNames.Text:
const code = message.code;
const { workspaceLocation, code } = message;
console.log(`FRONTEND: TextMessage: ${code}`);
// TODO: Don't change ace editor directly
// const elements = document.getElementsByClassName('react-ace');
// if (elements.length === 0) {
// return;
// }
// // @ts-expect-error: ace is not available at compile time
// const editor = ace.edit(elements[0]);
// editor.setValue(code);
dispatch(WorkspaceActions.updateEditorValue('assessment', 0, code));
dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, code));
break;
case MessageTypeNames.EvalEditor:
dispatch(WorkspaceActions.evalEditor(message.workspaceLocation));
break;
case MessageTypeNames.Navigate:
window.location.pathname = message.route;
// TODO: Figure out why this doesn't work, this is faster in theory
// navigate(message.route);
break;
case MessageTypeNames.McqQuestion:
dispatch(WorkspaceActions.showMcqPane(message.workspaceLocation, message.options));
break;
case MessageTypeNames.McqAnswer:
console.log(`FRONTEND: MCQAnswerMessage: ${message}`);
dispatch(SessionActions.submitAnswer(message.questionId, message.choice));
break;
case MessageTypeNames.AssessmentAnswer:
dispatch(SessionActions.submitAnswer(message.questionId, message.answer));
break;
}
});
Expand Down
20 changes: 19 additions & 1 deletion src/commons/assessment/Assessment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import {
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import { sortBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Navigate, NavLink, useLoaderData, useParams } from 'react-router';
import { numberRegExp } from 'src/features/academy/AcademyTypes';
import Messages, { sendToWebview } from 'src/features/vscode/messages';
import classes from 'src/styles/Academy.module.scss';

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

useEffect(() => {
if (assessmentOverviewsUnfiltered && courseId) {
sendToWebview(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this why we are sending everything (including upcoming ones)? Looks good for now but can always make this minor change in the future.

Messages.NotifyAssessmentsOverview(
assessmentOverviewsUnfiltered.map(oa => ({
type: oa.type,
closeAt: oa.closeAt,
id: oa.id,
isPublished: oa.isPublished,
title: oa.title
})),
courseId
)
);
}
}, [assessmentOverviewsUnfiltered, courseId]);

const toggleClosedAssessments = () => setShowClosedAssessments(!showClosedAssessments);
const toggleOpenAssessments = () => setShowOpenedAssessments(!showOpenedAssessments);
const toggleUpcomingAssessments = () => setShowUpcomingAssessments(!showUpcomingAssessments);
Expand Down
67 changes: 60 additions & 7 deletions src/commons/assessmentWorkspace/AssessmentWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
const [sessionId, setSessionId] = useState('');
const [isSubmitted, setIsSubmitted] = useState(false);
const { isMobileBreakpoint } = useResponsive();
const isVscode = useTypedSelector(state => state.vscode.isVscode);

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

if (!isMobileBreakpoint && mobileOnlyTabIds.includes(selectedTab)) {
if ((!isMobileBreakpoint || isVscode) && mobileOnlyTabIds.includes(selectedTab)) {
setSelectedTab(SideContentType.questionOverview);
}
}, [isMobileBreakpoint, props, selectedTab, setSelectedTab]);
}, [isMobileBreakpoint, isVscode, props, selectedTab, setSelectedTab]);

/* ==================
onChange handlers
Expand Down Expand Up @@ -391,7 +392,38 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
);
handleClearContext(question.library, true);
handleUpdateHasUnsavedChanges(false);
sendToWebview(Messages.NewEditor(`assessment${assessment.id}`, props.questionId, ''));

const chapter = question.library.chapter;
const questionType = question.type;

switch (questionType) {
case QuestionTypes.mcq:
const mcqQuestionData = question;
sendToWebview(
Messages.McqQuestion(
workspaceLocation,
`assessment${assessment.id}`,
mcqQuestionData.id,
chapter,
mcqQuestionData.choices.map(choice => choice.content)
)
);
break;
case QuestionTypes.programming || QuestionTypes.voting:
const prepend = question.prepend;
const code = question.answer ?? question.solutionTemplate;
sendToWebview(
Messages.NewEditor(
workspaceLocation,
`assessment${assessment.id}`,
props.questionId,
chapter,
prepend,
code
)
);
break;
}
if (options.editorValue) {
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
handleEditorValueChange(0, options.editorValue);
Expand All @@ -416,6 +448,18 @@ const AssessmentWorkspace: React.FC<AssessmentWorkspaceProps> = props => {
const handleContestEntryClick = (_submissionId: number, answer: string) => {
// TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode.
handleEditorValueChange(0, answer);
// Hacky way to view the editor, might cause issues
sendToWebview(
Messages.NewEditor(
workspaceLocation,
`submission${_submissionId}`,
questionId,
question.library.chapter,
'',
answer
)
);
//
};

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

return {
editorButtons: !isMobileBreakpoint
? [runButton, saveButton, resetButton, chapterSelect]
: [saveButton, resetButton],
editorButtons:
!isMobileBreakpoint || isVscode
? [runButton, saveButton, resetButton, chapterSelect]
: [saveButton, resetButton],
flowButtons: [previousButton, questionView, nextButton]
};
};
Expand Down Expand Up @@ -885,6 +930,14 @@ It is safe to close this window.`}
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
);
handleUpdateHasUnsavedChanges(true);
if (isVscode) {
sendToWebview(
Messages.ResetEditor(
workspaceLocation,
(assessment!.questions[questionId] as IProgrammingQuestion).solutionTemplate
)
);
}
}}
options={{ minimal: false, intent: Intent.DANGER }}
/>
Expand Down Expand Up @@ -968,7 +1021,7 @@ It is safe to close this window.`}
{submissionOverlay}
{overlay}
{resetTemplateOverlay}
{!isMobileBreakpoint ? (
{isVscode || !isMobileBreakpoint ? (
<Workspace {...workspaceProps} />
) : (
<MobileWorkspace {...mobileWorkspaceProps} />
Expand Down
24 changes: 13 additions & 11 deletions src/commons/workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,19 @@ const Workspace: React.FC<WorkspaceProps> = props => {
<Resizable {...editorResizableProps()}>{createWorkspaceInput(props)}</Resizable>
)}
<div className="right-parent" ref={setFullscreenRefs}>
<Tooltip
className="fullscreen-button"
content={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
portalContainer={fullscreenContainerRef.current || undefined}
>
<Button
minimal
icon={isFullscreen ? IconNames.MINIMIZE : IconNames.MAXIMIZE}
onClick={toggleFullscreen}
/>
</Tooltip>
{!isVscode && (
<Tooltip
className="fullscreen-button"
content={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
portalContainer={fullscreenContainerRef.current || undefined}
>
<Button
minimal
icon={isFullscreen ? IconNames.MINIMIZE : IconNames.MAXIMIZE}
onClick={toggleFullscreen}
/>
</Tooltip>
)}
{props.sideContentIsResizeable === undefined || props.sideContentIsResizeable
? resizableSideContent
: sideContent}
Expand Down
9 changes: 6 additions & 3 deletions src/commons/workspace/WorkspaceActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,8 @@ const newActions = createActions('workspace', {
updateEditorValue: (
workspaceLocation: WorkspaceLocation,
editorTabIndex: number,
newEditorValue: string,
isFromVscode: boolean = false
) => ({ workspaceLocation, editorTabIndex, newEditorValue, isFromVscode }),
newEditorValue: string
) => ({ workspaceLocation, editorTabIndex, newEditorValue }),
setEditorBreakpoint: (
workspaceLocation: WorkspaceLocation,
editorTabIndex: number,
Expand Down Expand Up @@ -241,6 +240,10 @@ const newActions = createActions('workspace', {
workspaceLocation: WorkspaceLocation,
storyEnv?: string
) => ({ errorMsg, workspaceLocation, storyEnv }),
showMcqPane: (workspaceLocation: WorkspaceLocation, options: string[]) => ({
workspaceLocation,
options
}),
toggleUsingCse: (usingCse: boolean, workspaceLocation: WorkspaceLocationsWithTools) => ({
usingCse,
workspaceLocation
Expand Down
3 changes: 1 addition & 2 deletions src/commons/workspace/__tests__/WorkspaceActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ test('updateEditorValue generates correct action object', () => {
payload: {
workspaceLocation: assessmentWorkspace,
editorTabIndex,
newEditorValue,
isFromVscode: false
newEditorValue
}
});
});
Expand Down
4 changes: 0 additions & 4 deletions src/commons/workspace/reducers/editorReducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import Messages, { sendToWebview } from 'src/features/vscode/messages';

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

state[workspaceLocation].editorTabs[editorTabIndex].value = newEditorValue;
if (!action.payload.isFromVscode) {
sendToWebview(Messages.Text(newEditorValue));
}
})
.addCase(WorkspaceActions.setEditorBreakpoint, (state, action) => {
const workspaceLocation = getWorkspaceLocation(action);
Expand Down
Loading
Loading