diff --git a/client/app/bundles/course/assessment/submission/components/GetHelpChatPage/ConversationArea.tsx b/client/app/bundles/course/assessment/submission/components/GetHelpChatPage/ConversationArea.tsx index 240ed13e75d..039aad5572e 100644 --- a/client/app/bundles/course/assessment/submission/components/GetHelpChatPage/ConversationArea.tsx +++ b/client/app/bundles/course/assessment/submission/components/GetHelpChatPage/ConversationArea.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; import { Typography } from '@mui/material'; import LoadingEllipsis from 'lib/components/core/LoadingEllipsis'; @@ -11,13 +12,21 @@ import translations from '../../translations'; import { ChatSender } from '../../types'; interface ConversationAreaProps { - onFeedbackClick: (linenum: number) => void; + onFeedbackClick: (linenum: number, filename?: string) => void; answerId: number; } const ConversationArea: FC = (props) => { const { onFeedbackClick, answerId } = props; + const { control } = useFormContext(); + const currentAnswer = useWatch({ control }); + + const files = currentAnswer[answerId] + ? currentAnswer[answerId].files_attributes || + currentAnswer[`${answerId}`].files_attributes + : []; + const dispatch = useAppDispatch(); const liveFeedbackChats = useAppSelector((state) => getLiveFeedbackChatsForAnswerId(state, answerId), @@ -78,9 +87,14 @@ const ConversationArea: FC = (props) => { className="flex flex-col whitespace-pre-wrap ml-1" variant="body2" > - {t(translations.lineNumber, { - lineNumber: chat.lineNumber, - })} + {files.length === 1 + ? t(translations.lineNumber, { + lineNumber: chat.lineNumber, + }) + : t(translations.fileNameAndLineNumber, { + filename: chat.filename ?? '', + lineNumber: chat.lineNumber, + })} void; + onFeedbackClick: (linenum: number, filename?: string) => void; answerId: number | null; questionId: number; } diff --git a/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx b/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx index a5cc1ebc504..1dc2e456a18 100644 --- a/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx +++ b/client/app/bundles/course/assessment/submission/components/answers/Programming/index.jsx @@ -1,6 +1,5 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; -import { Box } from '@mui/material'; import PropTypes from 'prop-types'; import { workflowStates } from 'course/assessment/submission/constants'; @@ -23,21 +22,12 @@ import ProgrammingFile from './ProgrammingFile'; const ProgrammingFiles = ({ readOnly, answerId, - questionId, + editorRef, language, saveAnswerAndUpdateClientVersion, }) => { const { control } = useFormContext(); - const liveFeedbackChatForAnswer = useAppSelector((state) => - getLiveFeedbackChatsForAnswerId(state, answerId), - ); - const submission = useAppSelector(getSubmission); - const isAttempting = submission.workflowState === workflowStates.Attempting; - - const isLiveFeedbackChatOpen = - liveFeedbackChatForAnswer?.isLiveFeedbackChatOpen; - const { fields } = useFieldArray({ control, name: `${answerId}.files_attributes`, @@ -48,15 +38,6 @@ const ProgrammingFiles = ({ name: `${answerId}.files_attributes`, }); - const editorRef = useRef(null); - - const focusEditorOnFeedbackLine = (linenum) => { - editorRef.current?.editor?.gotoLine(linenum, 0); - editorRef.current?.editor?.selection?.setAnchor(linenum - 1, 0); - editorRef.current?.editor?.selection?.moveCursorTo(linenum - 1, 0); - editorRef.current?.editor?.focus(); - }; - const controlledProgrammingFields = fields.map((field, index) => ({ ...field, ...currentField[index], @@ -73,34 +54,17 @@ const ProgrammingFiles = ({ const keyString = `editor-container-${index}`; return ( -
- - - - {isLiveFeedbackChatOpen && isAttempting && ( -
- -
- )} +
+
); }); @@ -109,11 +73,45 @@ const ProgrammingFiles = ({ const Programming = (props) => { const { question, readOnly, answerId, saveAnswerAndUpdateClientVersion } = props; + + const { control } = useFormContext(); + const currentAnswer = useWatch({ control }); + + const liveFeedbackChatForAnswer = useAppSelector((state) => + getLiveFeedbackChatsForAnswerId(state, answerId), + ); + const submission = useAppSelector(getSubmission); + const isAttempting = submission.workflowState === workflowStates.Attempting; + + const isLiveFeedbackChatOpen = + liveFeedbackChatForAnswer?.isLiveFeedbackChatOpen; const fileSubmission = question.fileSubmission; const isSavingAnswer = useAppSelector((state) => getIsSavingAnswer(state, answerId), ); + const files = currentAnswer[answerId] + ? currentAnswer[answerId].files_attributes || + currentAnswer[`${answerId}`].files_attributes + : null; + + const [displayFileName, setDisplayFileName] = useState( + files && files.length > 0 ? files[0].filename : '', + ); + + const editorRef = useRef(null); + + const focusEditorOnFeedbackLine = (linenum, filename) => { + if (filename) { + setDisplayFileName(filename); + } + + editorRef.current?.editor?.gotoLine(linenum, 0); + editorRef.current?.editor?.selection?.setAnchor(linenum - 1, 0); + editorRef.current?.editor?.selection?.moveCursorTo(linenum - 1, 0); + editorRef.current?.editor?.focus(); + }; + const feedbackFiles = useAppSelector( (state) => state.assessments.submission.liveFeedback?.feedbackByQuestion?.[ @@ -122,29 +120,51 @@ const Programming = (props) => { ); return ( -
- {fileSubmission ? ( - - ) : ( - - )} + <> +
+
+ {fileSubmission ? ( + + ) : ( + + )} +
+ {isLiveFeedbackChatOpen && isAttempting && ( +
+ +
+ )} +
-
+ ); }; diff --git a/client/app/bundles/course/assessment/submission/containers/ProgrammingImport/ProgrammingImportEditor.jsx b/client/app/bundles/course/assessment/submission/containers/ProgrammingImport/ProgrammingImportEditor.jsx index 4cffe76455b..0d4adbcf160 100644 --- a/client/app/bundles/course/assessment/submission/containers/ProgrammingImport/ProgrammingImportEditor.jsx +++ b/client/app/bundles/course/assessment/submission/containers/ProgrammingImport/ProgrammingImportEditor.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { Component } from 'react'; import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -17,6 +17,7 @@ import ImportedFileView from './ImportedFileView'; const SelectProgrammingFileEditor = ({ answerId, + editorRef, readOnly, language, displayFileName, @@ -54,6 +55,7 @@ const SelectProgrammingFileEditor = ({ return ( { @@ -119,11 +125,14 @@ const handleStageFiles = async (filesToImport) => { const VisibleProgrammingImportEditor = (props) => { const { answerId, + editorRef, disabled, dispatch, historyAnswers, question, readOnly, + displayFileName, + setDisplayFileName, saveAnswerAndUpdateClientVersion, viewHistory, } = props; @@ -136,10 +145,6 @@ const VisibleProgrammingImportEditor = (props) => { answers[`${answerId}`].files_attributes : null; - const [displayFileName, setDisplayFileName] = useState( - files && files.length > 0 ? files[0].filename : '', - ); - // When an assessment is submitted/unsubmitted, // the form is somehow not reset yet and the answers for the new answerId // can't be found. @@ -198,6 +203,7 @@ const VisibleProgrammingImportEditor = (props) => { { return { sender: ChatSender.codaveri, + filename: line.path, lineNumber: line.line, lineContent: answerLines[line.path][line.line - 1].trim() ?? null, message: line.content, diff --git a/client/app/bundles/course/assessment/submission/translations.ts b/client/app/bundles/course/assessment/submission/translations.ts index 9faddd0e7aa..8a0e812a11d 100644 --- a/client/app/bundles/course/assessment/submission/translations.ts +++ b/client/app/bundles/course/assessment/submission/translations.ts @@ -370,6 +370,10 @@ const translations = defineMessages({ id: 'course.assessment.submission.GetHelpChatPage.ConversationArea.lineNumber', defaultMessage: 'Line {lineNumber}', }, + fileNameAndLineNumber: { + id: 'course.assessment.submission.GetHelpChatPage.ConversationArea.fileNameAndLineNumber', + defaultMessage: '{filename}:{lineNumber}', + }, threadExpired: { id: 'course.assessment.submission.GetHelpChatPage.ConversationArea.threadExpired', defaultMessage: 'The chat above has ended. Start a new chat?', diff --git a/client/app/bundles/course/assessment/submission/types.ts b/client/app/bundles/course/assessment/submission/types.ts index 2165e34e2de..db392189abb 100644 --- a/client/app/bundles/course/assessment/submission/types.ts +++ b/client/app/bundles/course/assessment/submission/types.ts @@ -164,6 +164,7 @@ export enum ChatSender { export interface ChatShape { sender: ChatSender; + filename?: string; lineNumber: number | null; lineContent: string | null; message: string[];