diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx index 61af7a3eeb..88f7c52a39 100644 --- a/src/commons/application/Application.tsx +++ b/src/commons/application/Application.tsx @@ -88,7 +88,7 @@ const Application: React.FC = () => { }; } - const message = Messages.ExtensionPing(); + const message = Messages.ExtensionPing(window.origin); sendToWebview(message); window.addEventListener('message', event => { @@ -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; } }); diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index 71ec517059..9b213f5097 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -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'; @@ -60,6 +61,23 @@ const Assessment: React.FC = () => { const { courseId, role, assessmentOverviews: assessmentOverviewsUnfiltered } = useSession(); const dispatch = useDispatch(); + useEffect(() => { + if (assessmentOverviewsUnfiltered && courseId) { + sendToWebview( + 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); diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index 20138018e5..23412daa51 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -96,6 +96,7 @@ const AssessmentWorkspace: React.FC = 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); @@ -244,10 +245,10 @@ const AssessmentWorkspace: React.FC = 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 @@ -391,7 +392,38 @@ const AssessmentWorkspace: React.FC = 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); @@ -416,6 +448,18 @@ const AssessmentWorkspace: React.FC = 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[] = [ @@ -736,9 +780,10 @@ const AssessmentWorkspace: React.FC = props => { ); return { - editorButtons: !isMobileBreakpoint - ? [runButton, saveButton, resetButton, chapterSelect] - : [saveButton, resetButton], + editorButtons: + !isMobileBreakpoint || isVscode + ? [runButton, saveButton, resetButton, chapterSelect] + : [saveButton, resetButton], flowButtons: [previousButton, questionView, nextButton] }; }; @@ -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 }} /> @@ -968,7 +1021,7 @@ It is safe to close this window.`} {submissionOverlay} {overlay} {resetTemplateOverlay} - {!isMobileBreakpoint ? ( + {isVscode || !isMobileBreakpoint ? ( ) : ( diff --git a/src/commons/workspace/Workspace.tsx b/src/commons/workspace/Workspace.tsx index fa2e63c235..3f940b7b45 100644 --- a/src/commons/workspace/Workspace.tsx +++ b/src/commons/workspace/Workspace.tsx @@ -227,17 +227,19 @@ const Workspace: React.FC = props => { {createWorkspaceInput(props)} )}
- -
{isTestStudent && (
diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index 02bd08ee99..d9c06bd46f 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -41,6 +41,7 @@ import { shortenURL, updateShortURL } from 'src/features/playground/PlaygroundActions'; +import Messages, { sendToWebview } from 'src/features/vscode/messages'; import { getDefaultFilePath, @@ -193,6 +194,7 @@ const Playground: React.FC = props => { const { isSicpEditor } = props; const workspaceLocation: WorkspaceLocation = isSicpEditor ? 'sicp' : 'playground'; const { isMobileBreakpoint } = useResponsive(); + const isVscode = useTypedSelector(state => state.vscode.isVscode); const [deviceSecret, setDeviceSecret] = useState(); const location = useLocation(); @@ -358,12 +360,12 @@ const Playground: React.FC = props => { useEffect(() => { if (!selectedTab) return; - if (isMobileBreakpoint && desktopOnlyTabIds.includes(selectedTab)) { + if (!isVscode && isMobileBreakpoint && desktopOnlyTabIds.includes(selectedTab)) { setSelectedTab(SideContentType.mobileEditor); } else if (!isMobileBreakpoint && mobileOnlyTabIds.includes(selectedTab)) { setSelectedTab(SideContentType.introduction); } - }, [isMobileBreakpoint, selectedTab, setSelectedTab]); + }, [isMobileBreakpoint, isVscode, selectedTab, setSelectedTab]); const onEditorValueChange = React.useCallback( (editorTabIndex: number, newEditorValue: string) => { @@ -373,6 +375,26 @@ const Playground: React.FC = props => { [handleEditorValueChange] ); + useEffect(() => { + // Only the playground is expected to work with VSC for now + if (workspaceLocation === 'sicp') { + return; + } + const initialCode = editorTabs[0]?.value ?? ''; + sendToWebview( + Messages.NewEditor( + workspaceLocation, + 'playground', + 1, + playgroundSourceChapter, + '', + initialCode + ) + ); + // We don't want to re-send this message even when the variables change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // const onChangeTabs = useCallback( // ( // newTabId: SideContentType, @@ -489,6 +511,7 @@ const Playground: React.FC = props => { pushLog(input); + sendToWebview(Messages.ChangeChapter('playground', 1, chapter, variant)); handleChapterSelect(chapter, variant); // Hardcoded for Playground only for now, while we await workspace refactoring // to decouple the SicpWorkspace from the Playground. @@ -1029,7 +1052,7 @@ const Playground: React.FC = props => { } }; - return isMobileBreakpoint ? ( + return !isVscode && isMobileBreakpoint ? (