diff --git a/client/app/bundles/course/assessment/submission/components/WarningDialog.tsx b/client/app/bundles/course/assessment/submission/components/WarningDialog.tsx index f3f8048ee7..90343b56cf 100644 --- a/client/app/bundles/course/assessment/submission/components/WarningDialog.tsx +++ b/client/app/bundles/course/assessment/submission/components/WarningDialog.tsx @@ -8,39 +8,43 @@ import { Typography, } from '@mui/material'; +import { useAppSelector } from 'lib/hooks/store'; import useTranslation from 'lib/hooks/useTranslation'; -import { BUFFER_TIME_TO_FORCE_SUBMIT_MS } from '../constants'; +import { TIME_LAPSE_NEW_SUBMISSION_MS, workflowStates } from '../constants'; import { remainingTimeDisplay } from '../pages/SubmissionEditIndex/TimeLimitBanner'; +import { getAssessment } from '../selectors/assessments'; +import { getSubmission } from '../selectors/submissions'; import translations from '../translations'; -interface Props { - submissionTimeLimitAt: number | null; - isExamMode: boolean; - isTimedMode: boolean; - isAttempting: boolean; -} - -const WarningDialog: FC = (props) => { +const WarningDialog: FC = () => { const { t } = useTranslation(); - const { isExamMode, isTimedMode, isAttempting, submissionTimeLimitAt } = - props; - const [examNotice, setExamNotice] = useState(isExamMode); - const [timedNotice, setTimedNotice] = useState(isTimedMode); + const assessment = useAppSelector(getAssessment); + const submission = useAppSelector(getSubmission); + const { timeLimit, passwordProtected: isExamMode } = assessment; + const { workflowState, attemptedAt } = submission; + + const isAttempting = workflowState === workflowStates.Attempting; + const isTimedMode = isAttempting && !!timeLimit; + + const startTime = new Date(attemptedAt).getTime(); const currentTime = new Date().getTime(); - const remainingTime = - submissionTimeLimitAt && submissionTimeLimitAt > currentTime - ? submissionTimeLimitAt - currentTime - : null; + const submissionTimeLimitAt = isTimedMode + ? startTime + timeLimit * 60 * 1000 + : null; + + const isNewSubmission = + currentTime - startTime < TIME_LAPSE_NEW_SUBMISSION_MS; + + const [examNotice, setExamNotice] = useState(isExamMode); + const [timedNotice, setTimedNotice] = useState(isTimedMode); - const remainingBufferTime = - submissionTimeLimitAt && - submissionTimeLimitAt <= currentTime && - currentTime < submissionTimeLimitAt + BUFFER_TIME_TO_FORCE_SUBMIT_MS - ? submissionTimeLimitAt + BUFFER_TIME_TO_FORCE_SUBMIT_MS - currentTime + const remainingTime = + isTimedMode && submissionTimeLimitAt! > currentTime + ? submissionTimeLimitAt! - currentTime : null; let dialogTitle: string = ''; @@ -48,24 +52,28 @@ const WarningDialog: FC = (props) => { if (examNotice && timedNotice) { dialogTitle = t(translations.timedExamDialogTitle, { - remainingTime: remainingTimeDisplay(remainingTime ?? 0), + isNewSubmission, + remainingTime: remainingTimeDisplay( + isNewSubmission ? timeLimit! * 60 * 1000 : remainingTime ?? 0, + ), stillSomeTimeRemaining: !!remainingTime, }); dialogMessage = t(translations.timedExamDialogMessage, { stillSomeTimeRemaining: !!remainingTime, - remainingBufferTime: remainingTimeDisplay(remainingBufferTime ?? 0), }); } else if (examNotice) { dialogTitle = t(translations.examDialogTitle); dialogMessage = t(translations.examDialogMessage); } else if (timedNotice) { dialogTitle = t(translations.timedAssessmentDialogTitle, { - remainingTime: remainingTimeDisplay(remainingTime ?? 0), + isNewSubmission, + remainingTime: remainingTimeDisplay( + isNewSubmission ? timeLimit! * 60 * 1000 : remainingTime ?? 0, + ), stillSomeTimeRemaining: !!remainingTime, }); dialogMessage = t(translations.timedAssessmentDialogMessage, { stillSomeTimeRemaining: !!remainingTime, - remainingBufferTime: remainingTimeDisplay(remainingBufferTime ?? 0), }); } diff --git a/client/app/bundles/course/assessment/submission/constants.ts b/client/app/bundles/course/assessment/submission/constants.ts index 7644f6e047..9c4c2f44e4 100644 --- a/client/app/bundles/course/assessment/submission/constants.ts +++ b/client/app/bundles/course/assessment/submission/constants.ts @@ -16,7 +16,11 @@ export const questionTypes = mirrorCreator([ export const MEGABYTES_TO_BYTES = 1024 * 1024; -export const BUFFER_TIME_TO_FORCE_SUBMIT_MS = 2 * 60 * 1000; +export const BUFFER_TIME_TO_FORCE_SUBMIT_MS = 5 * 1000; + +// calculate how long has it passed since the student starts the submission +// to still be considered a "newly created" submission +export const TIME_LAPSE_NEW_SUBMISSION_MS = 10 * 1000; export const POLL_INTERVAL_MILLISECONDS = 2000; diff --git a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionForm.tsx b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionForm.tsx index 27458dbba0..c75e7df475 100644 --- a/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionForm.tsx +++ b/client/app/bundles/course/assessment/submission/pages/SubmissionEditIndex/SubmissionForm.tsx @@ -55,14 +55,11 @@ const SubmissionForm: FC = (props) => { const liveFeedbacks = useAppSelector(getLiveFeedbacks); const initialValues = useAppSelector(getInitialAnswer); - const { autograded, timeLimit, tabbedView, questionIds, passwordProtected } = - assessment; + const { autograded, timeLimit, tabbedView, questionIds } = assessment; const { workflowState, attemptedAt } = submission; const maxInitialStep = submission.maxStep ?? questionIds.length - 1; - const attempting = workflowState === workflowStates.Attempting; - const submissionId = getSubmissionId(); const hasSubmissionTimeLimit = @@ -218,12 +215,7 @@ const SubmissionForm: FC = (props) => { - + ); }; diff --git a/client/app/bundles/course/assessment/submission/translations.ts b/client/app/bundles/course/assessment/submission/translations.ts index 33badc1017..c2674f46b6 100644 --- a/client/app/bundles/course/assessment/submission/translations.ts +++ b/client/app/bundles/course/assessment/submission/translations.ts @@ -445,21 +445,19 @@ const translations = defineMessages({ timedAssessmentDialogTitle: { id: 'course.assessment.submission.timedAssessmentDialogTitle', defaultMessage: - '{stillSomeTimeRemaining, select, true {{remainingTime} remaining to \ + '{stillSomeTimeRemaining, select, true {{remainingTime} {isNewSubmission, select, true {} other {remaining}} to \ complete this assessment.} other {The assessment has ended!}}', }, timedAssessmentDialogMessage: { id: 'course.assessment.submission.timedAssessmentDialogMessage', defaultMessage: '{stillSomeTimeRemaining, select, true {Once the time is up, \ - the assessment will be automatically finalised.} other {You have \ - {remainingBufferTime} before your submission is automatically finalised by the system. \ - Alternatively, you may choose to finalise it on your own within this time.}}', + the assessment will be automatically finalised.} other {Finalising the submission now!}}', }, timedExamDialogTitle: { id: 'course.assessment.submission.timedExamDialogTitle', defaultMessage: - '{stillSomeTimeRemaining, select, true {{remainingTime} remaining to \ + '{stillSomeTimeRemaining, select, true {{remainingTime} {isNewSubmission, select, true {} other {remaining}} to \ complete this exam.} other {The exam has ended!}}', }, timedExamDialogMessage: { @@ -467,9 +465,7 @@ const translations = defineMessages({ defaultMessage: '{stillSomeTimeRemaining, select, true {Please \ do not sign out or close the browser while attempting this exam. Once the time is up, \ - the assessment will be automatically finalised.} other {You have \ - {remainingBufferTime} before your submission is automatically finalised by the system. \ - Alternatively, you may choose to finalise it on your own within this time.}}', + the assessment will be automatically finalised.} other {Finalising the submission now!}}', }, emptyAssessment: { id: 'course.assessment.submission.emptyAssessment', @@ -594,14 +590,14 @@ const translations = defineMessages({ id: 'course.assessment.submission.SubmissionEditIndex.TimeLimitBanner.hoursMinutesSeconds', defaultMessage: '{hrs, plural, one {# hour} other {# hours}} \ - {mins, plural, one {# minute} other {# minutes}} \ - {secs, plural, one {# second} other {# seconds}}', + {mins, plural, =0 {} one {# minute} other {# minutes}} \ + {secs, plural, =0 {} one {# second} other {# seconds}}', }, minutesSeconds: { id: 'course.assessment.submission.SubmissionEditIndex.TimeLimitBanner.minutesSeconds', defaultMessage: '{mins, plural, one {# minute} other {# minutes}} \ - {secs, plural, one {# second} other {# seconds}}', + {secs, plural, =0 {} one {# second} other {# seconds}}', }, seconds: { id: 'course.assessment.submission.SubmissionEditIndex.TimeLimitBanner.minutesSeconds',