@tinyweb_dev/oe-exam-sdk 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -4
- package/dist/api/contest-rooms.d.ts +13 -0
- package/dist/api/contest-rooms.js +1 -0
- package/dist/api/contest-rooms.types.d.ts +47 -0
- package/dist/api/contest-rooms.types.js +1 -0
- package/dist/api/exam-entry.d.ts +14 -0
- package/dist/api/exam-entry.js +1 -0
- package/dist/api/exam-entry.types.d.ts +93 -0
- package/dist/api/exam-entry.types.js +1 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +1 -1
- package/dist/components/exams/entry/EntryRestartDialog.d.ts +12 -0
- package/dist/components/exams/entry/EntryRestartDialog.js +1 -0
- package/dist/components/exams/entry/StudentExamEntryPageContainer.d.ts +31 -0
- package/dist/components/exams/entry/StudentExamEntryPageContainer.js +1 -0
- package/dist/components/exams/entry/exam-entry.states.d.ts +22 -0
- package/dist/components/exams/entry/exam-entry.states.js +1 -0
- package/dist/components/exams/entry/exam-entry.utils.d.ts +16 -0
- package/dist/components/exams/entry/exam-entry.utils.js +1 -0
- package/dist/components/exams/entry/fullscreen-loading.d.ts +5 -0
- package/dist/components/exams/entry/fullscreen-loading.js +1 -0
- package/dist/components/exams/entry/index.d.ts +7 -0
- package/dist/components/exams/entry/index.js +1 -0
- package/dist/components/exams/index.d.ts +8 -0
- package/dist/components/exams/index.js +1 -1
- package/dist/components/exams/rooms/StudentContestRoomsPageContainer.d.ts +18 -0
- package/dist/components/exams/rooms/StudentContestRoomsPageContainer.js +1 -0
- package/dist/components/exams/rooms/contest-rooms.utils.d.ts +32 -0
- package/dist/components/exams/rooms/contest-rooms.utils.js +1 -0
- package/dist/components/exams/rooms/index.d.ts +3 -0
- package/dist/components/exams/rooms/index.js +1 -0
- package/dist/components/exams/take/ExamTakingApiContext.d.ts +10 -0
- package/dist/components/exams/take/ExamTakingApiContext.js +1 -0
- package/dist/components/exams/take/ExamTakingPageContainer.d.ts +28 -4
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -1
- package/dist/components/exams/take/components/QuestionRenderer.d.ts +25 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +1 -0
- package/dist/components/exams/take/components/index.d.ts +10 -0
- package/dist/components/exams/take/components/index.js +1 -0
- package/dist/components/exams/take/components/question-renderers/ArticlesViewer.d.ts +7 -0
- package/dist/components/exams/take/components/question-renderers/ArticlesViewer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.d.ts +22 -0
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.d.ts +14 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.d.ts +3 -0
- package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.d.ts +21 -0
- package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.d.ts +19 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.d.ts +20 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.d.ts +32 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.d.ts +18 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.d.ts +19 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.d.ts +22 -0
- package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +18 -0
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +1 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +19 -0
- package/dist/components/exams/take/components/question-renderers/index.js +1 -0
- package/dist/components/exams/take/components/speaking/FindObjectInPicture.module.css +133 -0
- package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.d.ts +9 -0
- package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingCompareImages.module.css +153 -0
- package/dist/components/exams/take/components/speaking/SpeakingContentFrame.d.ts +7 -0
- package/dist/components/exams/take/components/speaking/SpeakingContentFrame.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingDragObjects.module.css +238 -0
- package/dist/components/exams/take/components/speaking/SpeakingExamLayout.d.ts +27 -0
- package/dist/components/exams/take/components/speaking/SpeakingExamLayout.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingHeader.d.ts +6 -0
- package/dist/components/exams/take/components/speaking/SpeakingHeader.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingInfoExchange.module.css +193 -0
- package/dist/components/exams/take/components/speaking/SpeakingInfoExchangeSummerSky.module.css +238 -0
- package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.d.ts +5 -0
- package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingMicButton.d.ts +14 -0
- package/dist/components/exams/take/components/speaking/SpeakingMicButton.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingNavButton.d.ts +7 -0
- package/dist/components/exams/take/components/speaking/SpeakingNavButton.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingPartBadge.d.ts +6 -0
- package/dist/components/exams/take/components/speaking/SpeakingPartBadge.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingReadDisplayedContent.module.css +115 -0
- package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.d.ts +11 -0
- package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.d.ts +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.js +1 -0
- package/dist/components/exams/take/components/speaking/SpeakingStoryImages.module.css +231 -0
- package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.d.ts +13 -0
- package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.js +1 -0
- package/dist/components/exams/take/components/speaking/index.d.ts +1 -0
- package/dist/components/exams/take/components/speaking/index.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.d.ts +17 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.d.ts +17 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.d.ts +18 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.d.ts +26 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.d.ts +32 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.d.ts +34 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.d.ts +17 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.d.ts +17 -0
- package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.js +1 -0
- package/dist/components/exams/take/components/speaking/renderers/index.d.ts +13 -0
- package/dist/components/exams/take/components/speaking/renderers/index.js +1 -0
- package/dist/components/exams/take/hooks/index.d.ts +2 -0
- package/dist/components/exams/take/hooks/index.js +1 -0
- package/dist/components/exams/take/hooks/useAnswerAutosave.d.ts +27 -0
- package/dist/components/exams/take/hooks/useAnswerAutosave.js +1 -0
- package/dist/components/exams/take/hooks/useAttemptSubmit.d.ts +29 -0
- package/dist/components/exams/take/hooks/useAttemptSubmit.js +1 -0
- package/dist/components/exams/take/hooks/useAutoSave.d.ts +20 -0
- package/dist/components/exams/take/hooks/useAutoSave.js +1 -0
- package/dist/components/exams/take/hooks/useExamCountdown.d.ts +22 -0
- package/dist/components/exams/take/hooks/useExamCountdown.js +1 -0
- package/dist/components/exams/take/hooks/useExamTimer.d.ts +21 -0
- package/dist/components/exams/take/hooks/useExamTimer.js +1 -0
- package/dist/components/exams/take/hooks/useProctoringCamera.d.ts +19 -0
- package/dist/components/exams/take/hooks/useProctoringCamera.js +1 -0
- package/dist/components/exams/take/hooks/useProctoringSnapshot.d.ts +25 -0
- package/dist/components/exams/take/hooks/useProctoringSnapshot.js +1 -0
- package/dist/components/exams/take/hooks/useSpeakingConversationAudio.d.ts +19 -0
- package/dist/components/exams/take/hooks/useSpeakingConversationAudio.js +1 -0
- package/dist/components/exams/take/hooks/useSpeakingNavigation.d.ts +37 -0
- package/dist/components/exams/take/hooks/useSpeakingNavigation.js +1 -0
- package/dist/components/exams/take/hooks/useSpeakingRecorder.d.ts +45 -5
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -1
- package/dist/components/exams/take/hooks/useSpeakingRendererSetup.d.ts +39 -0
- package/dist/components/exams/take/hooks/useSpeakingRendererSetup.js +1 -0
- package/dist/components/exams/take/hooks/useToeicAudio.d.ts +17 -0
- package/dist/components/exams/take/hooks/useToeicAudio.js +1 -0
- package/dist/components/exams/take/index.d.ts +9 -1
- package/dist/components/exams/take/index.js +1 -1
- package/dist/components/exams/take/types/speaking-cue-card.types.d.ts +10 -0
- package/dist/components/exams/take/types/speaking-cue-card.types.js +1 -0
- package/dist/components/exams/take/types/speaking.types.d.ts +153 -0
- package/dist/components/exams/take/types/speaking.types.js +1 -0
- package/dist/components/exams/take/types.d.ts +286 -1
- package/dist/components/exams/take/utils/answer-transformers.d.ts +26 -0
- package/dist/components/exams/take/utils/answer-transformers.js +1 -0
- package/dist/components/exams/take/utils/coloring-assignments.d.ts +5 -0
- package/dist/components/exams/take/utils/coloring-assignments.js +1 -0
- package/dist/components/exams/take/utils/question-transformers.d.ts +362 -0
- package/dist/components/exams/take/utils/question-transformers.js +1 -0
- package/dist/components/features/exam-entry/entry-state-cards.d.ts +19 -0
- package/dist/components/features/exam-entry/entry-state-cards.js +1 -0
- package/dist/components/features/exam-entry/index.d.ts +2 -0
- package/dist/components/features/exam-entry/index.js +1 -0
- package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.d.ts +12 -0
- package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.js +1 -0
- package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.d.ts +14 -0
- package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.js +1 -0
- package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.d.ts +16 -0
- package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.js +1 -0
- package/dist/components/features/exam-entry/themed/components/CloseButton.d.ts +9 -0
- package/dist/components/features/exam-entry/themed/components/CloseButton.js +1 -0
- package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.d.ts +13 -0
- package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.js +1 -0
- package/dist/components/features/exam-entry/themed/components/ModeSelector.d.ts +7 -0
- package/dist/components/features/exam-entry/themed/components/ModeSelector.js +1 -0
- package/dist/components/features/exam-entry/themed/components/StartButton.d.ts +11 -0
- package/dist/components/features/exam-entry/themed/components/StartButton.js +1 -0
- package/dist/components/features/exam-entry/themed/components/index.d.ts +4 -0
- package/dist/components/features/exam-entry/themed/components/index.js +1 -0
- package/dist/components/features/exam-entry/themed/index.d.ts +1 -0
- package/dist/components/features/exam-entry/themed/index.js +1 -0
- package/dist/components/results/ResultReviewPageContainer.d.ts +3 -1
- package/dist/components/results/ResultReviewPageContainer.js +1 -1
- package/dist/components/results/hooks/useResultReviewPolling.d.ts +27 -0
- package/dist/components/results/hooks/useResultReviewPolling.js +1 -0
- package/dist/components/results/index.d.ts +3 -0
- package/dist/components/results/index.js +1 -1
- package/dist/components/results/review-status.utils.d.ts +12 -0
- package/dist/components/results/review-status.utils.js +1 -0
- package/dist/components/shared/audio/SpeakingAudioPlayer.d.ts +12 -0
- package/dist/components/shared/audio/SpeakingAudioPlayer.js +1 -0
- package/dist/components/shared/audio/SpeakingAudioPlayer.module.css +115 -0
- package/dist/components/shared/audio/assets/fr-image-part1-border.png +0 -0
- package/dist/components/shared/audio/assets/ic-speaker.webp +0 -0
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.d.ts +11 -3
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -1
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.d.ts +5 -1
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -1
- package/dist/components/themes/index.d.ts +11 -0
- package/dist/components/themes/index.js +1 -1
- package/dist/components/themes/spaceship/SpaceshipAudioPlayer.d.ts +8 -0
- package/dist/components/themes/spaceship/SpaceshipAudioPlayer.js +1 -0
- package/dist/components/themes/spaceship/SpaceshipLayout.d.ts +2 -0
- package/dist/components/themes/spaceship/SpaceshipLayout.js +1 -0
- package/dist/components/themes/spaceship/assets/images/X-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/X.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/audio-bar.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/background.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/bg_sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/big-blue-badge-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/big-blue-badge.png +0 -0
- package/dist/components/themes/spaceship/assets/images/big-blue-badge.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/bingo.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/blue-badge-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/blue-badge.png +0 -0
- package/dist/components/themes/spaceship/assets/images/blue-wrapper-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/blue-wrapper.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/board_red.png +0 -0
- package/dist/components/themes/spaceship/assets/images/button_feedback_vi.png +0 -0
- package/dist/components/themes/spaceship/assets/images/circle-btn-right-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/circle-btn-right.png +0 -0
- package/dist/components/themes/spaceship/assets/images/circle-btn-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/circle-btn.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/focus-vector.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/head-phone_red.png +0 -0
- package/dist/components/themes/spaceship/assets/images/ic-mute-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/ic-mute.png +0 -0
- package/dist/components/themes/spaceship/assets/images/ic-play-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/ic-play-violet.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/ic-play.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/micro.png +0 -0
- package/dist/components/themes/spaceship/assets/images/next-small.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/next-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/next.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/purple-circle.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/question-badge-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/question-badge.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/red-badge-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/red-badge.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/red-wrapper-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/red-wrapper.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/robot-avatar.png +0 -0
- package/dist/components/themes/spaceship/assets/images/sound_red.png +0 -0
- package/dist/components/themes/spaceship/assets/images/student-avatar.png +0 -0
- package/dist/components/themes/spaceship/assets/images/teacher-play-sunset.png +0 -0
- package/dist/components/themes/spaceship/assets/images/teacher-play.webp +0 -0
- package/dist/components/themes/spaceship/assets/images/timer-badge.png +0 -0
- package/dist/components/themes/spaceship/assets/styles/SpaceshipLayout.module.css +347 -0
- package/dist/components/themes/spaceship/assets/styles/SpaceshipWaitingRoom.module.css +395 -0
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/shared/lib/stores/examTakingStore.d.ts +54 -0
- package/dist/shared/lib/stores/examTakingStore.js +1 -0
- package/dist/shared/lib/stores/gradingTransientStore.d.ts +23 -0
- package/dist/shared/lib/stores/gradingTransientStore.js +1 -0
- package/dist/shared/lib/themes/config/starters.config.d.ts +3 -0
- package/dist/shared/lib/themes/config/starters.config.js +1 -0
- package/dist/shared/lib/themes/index.d.ts +18 -0
- package/dist/shared/lib/themes/index.js +1 -0
- package/dist/shared/lib/themes/types.d.ts +39 -0
- package/dist/shared/lib/themes/types.js +1 -0
- package/dist/shared/lib/toast.d.ts +19 -0
- package/dist/shared/lib/toast.js +1 -0
- package/package.json +10 -2
package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingDescribeImageRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
waitForTeacherVideo?: boolean;
|
|
15
|
+
currentQuestionIndex?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function SpeakingDescribeImageRenderer(props: SpeakingDescribeImageRendererProps): import("react").JSX.Element;
|
|
18
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as r}from"react/jsx-runtime";import{usePresignedFileUrl as t}from"../../../../../../shared/lib/hooks";import{useSpeakingRendererSetup as a}from"../../../hooks/useSpeakingRendererSetup";import n from"../../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import{getSpeakingLayout as i}from"../../../../../../components/themes";import{SpeakingAudioPlayer as o}from"../../../../../../components/shared/audio/SpeakingAudioPlayer";import{useEffect as s,useRef as d,useState as l}from"react";import c from"next/image";import{useExamTakingStore as m}from"../../../../../../shared/lib/stores/examTakingStore";import u from"../../../../../../components/themes/summer-sky/assets/images/mic_ver2_icon.png";import h from"../../../../../../components/themes/summer-sky/assets/images/stop_audio_ver2_icon.png";import p from"../../../../../../components/themes/summer-sky/assets/images/audio_ver2_icon.png";import g from"../../../../../../components/themes/summer-sky/assets/images/pause_audio_ver2_icon.png";import x from"../../../../../../components/themes/summer-sky/assets/images/lock_mic_ver2_icon.png";function f({total:t,currentIndex:a,answeredIds:n,questionIds:i}){return e("div",{className:"flex items-center gap-2 font-bold text-[#78350f] font-['Quicksand'] z-10",children:[e("span",{className:"text-sm",children:["Question ",a+1,"/ ",t]}),r("div",{className:"flex gap-1.5",children:Array.from({length:t},(e,t)=>{const o=t===a,s=n.has(i[t]);return r("div",{className:"w-2.5 h-2.5 rounded-full "+(o?"bg-[#FF9900] scale-125 shadow-sm":s?"bg-[#16CA3B]":"bg-white/60")},t)})})]})}const b=new Set(["/speaking/video/read_displayed_content.webm","/speaking/video/speaking_describe_image.webm","/speaking/video/spontaneous_qa.webm"]);function w(e){const r=Math.floor(e/60),t=Math.floor(e%60);return`${r.toString().padStart(2,"0")}:${t.toString().padStart(2,"0")}`}export function SpeakingDescribeImageRenderer(x){const{currentQuestionIndex:v}=x,y=a({...x,maxDurationMs:6e4,currentQuestionIndex:v}),N=m(e=>e.examTitle),k=N?.toLowerCase().includes("placement test")||!1,_=y.currentQuestion?.content,{previewUrl:C,isLoading:E}=t(_?.imageUrl||""),P=d(null),[S,F]=l(!!x.skipTeacherVideo),I=d(null),[T,U]=l(!1),[j,q]=l(0),[A,z]=l(0);if(s(()=>{const e=P.current;e&&x.teacherVideoUrl&&(e.playbackRate=b.has(x.teacherVideoUrl)?1.25:1)},[x.teacherVideoUrl]),s(()=>{const e=I.current;if(!e)return;const r=()=>{U(!0),y.onAudioPlayingChange?.(!0)},t=()=>{U(!1),y.onAudioPlayingChange?.(!1)},a=()=>{U(!1),q(0),y.onAudioPlayingChange?.(!1)},n=()=>{q(e.currentTime),e.duration&&isFinite(e.duration)&&z(e.duration)},i=()=>{e.duration&&isFinite(e.duration)&&z(e.duration)};return e.addEventListener("play",r),e.addEventListener("pause",t),e.addEventListener("ended",a),e.addEventListener("timeupdate",n),e.addEventListener("loadedmetadata",i),e.addEventListener("durationchange",i),()=>{e.removeEventListener("play",r),e.removeEventListener("pause",t),e.removeEventListener("ended",a),e.removeEventListener("timeupdate",n),e.removeEventListener("loadedmetadata",i),e.removeEventListener("durationchange",i),y.onAudioPlayingChange?.(!1)}},[y.recorder?.previewUrl,y.onAudioPlayingChange]),!y.currentQuestion||!_)return null;const L=new Set(x.part.questions.map(e=>e.id).filter(e=>!!x.answers[e])),V=x.part.questions.map(e=>e.id);if(x.speakingTheme&&"CAMBRIDGE_YLE"!==x.speakingTheme){const t=i(x.speakingTheme),a=r(f,{total:x.part.questions.length,currentIndex:y.nav.currentIndex,answeredIds:L,questionIds:V});return r(t,{onExit:x.onExit,partName:x.part.name||`Part ${x.part.partNo}`,instruction:x.part.instructions||"DESCRIBE THE IMAGE",audioUrl:"",questionNumber:y.nav.questionNumberInPart,onPrev:y.handlePrev,onNext:y.handleNext,showPrev:y.nav.showPrev,showNext:y.nav.showNext,canGoPrev:y.nav.canGoPrev,canGoNext:y.nav.canGoNext,onAudioPlayingChange:y.onAudioPlayingChange,recorder:x.isReviewMode?null:y.recorder,showExistingBadge:y.showExistingBadge,headerRight:a,teacherVideoUrl:x.teacherVideoUrl,waitForTeacherVideo:x.waitForTeacherVideo,onTeacherVideoEnded:x.onTeacherIntroPlayed,skipTeacherVideo:x.skipTeacherVideo,children:r("div",{className:"w-full h-full flex flex-col items-center relative",children:r("div",{className:"w-full flex-1",style:{minHeight:0},children:r("div",{className:"mx-auto",style:{width:"900px",maxWidth:"95%",height:"calc(100% - 40px)"},children:e("div",{className:"w-full h-full flex flex-col items-center justify-center",style:{paddingTop:"10px"},children:[_.isBackup&&r("div",{className:"bg-amber-500 text-white px-3 py-1 rounded-md text-xs font-bold border border-white shadow-md z-10 shrink-0 mb-4",children:"Câu hỏi dự phòng"}),_.question&&r("div",{className:"z-10 mx-auto px-8 py-2.5 bg-white/80 backdrop-blur-md rounded-full shadow-sm border border-slate-100 w-fit shrink-0 mb-4",children:r("h3",{className:"text-[17px] md:text-lg font-bold text-[#343C55] font-['Quicksand'] text-center whitespace-pre-wrap leading-tight",children:_.question})}),_.audioUrl&&r("div",{className:"z-10 mx-auto w-full max-w-[280px] shrink-0 mb-4",children:r(o,{audioUrl:_.audioUrl,onPlayingChange:y.onAudioPlayingChange,autoPlay:!x.waitForTeacherVideo})}),y.showExistingBadge&&e("div",{className:"absolute right-12 top-12 z-10 rounded-full bg-green-500 px-4 py-1.5 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[r("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),r("div",{className:"flex-1 flex justify-center items-center min-h-0 w-full",children:r("div",E?{className:"flex justify-center items-center w-full max-w-[60%] h-full max-h-[300px] border-4 border-dashed border-gray-300 rounded-xl bg-gray-50/50",children:r("div",{className:"animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"})}:C?{className:"bg-white rounded-2xl shadow-[0_4px_15px_rgba(0,0,0,0.08)] p-2 h-full max-w-[90%] flex justify-center items-center",children:r("img",{src:C,alt:"Scene to describe",className:"max-h-full max-w-full object-contain rounded-xl",draggable:!1})}:{className:"flex justify-center items-center text-orange-800 font-semibold opacity-70 w-full h-full max-h-[300px] border-4 border-dashed border-orange-900/30 rounded-xl bg-orange-900/10",children:"Chưa có hình ảnh mô tả"})})]})})})})})}const B=x.teacherVideoUrl?r("div",{className:"fixed bottom-6 left-6 z-40 flex flex-col items-start w-[12vw] min-w-[150px] pointer-events-none",children:e("div",{className:"relative w-full",children:[r("video",{ref:P,src:x.teacherVideoUrl,autoPlay:!x.skipTeacherVideo,playsInline:!0,onEnded:()=>{F(!0),x.onTeacherIntroPlayed?.()},className:"w-full h-auto drop-shadow-[0_4px_16px_rgba(0,0,0,0.2)] transition-opacity duration-700",style:{opacity:S?0:1}}),!k&&"CAMBRIDGE_YLE"!==x.speakingTheme&&r("img",{src:"/speaking/image/teacher_ai_smile.png",alt:"AI Teacher",className:"absolute top-0 left-0 w-full h-auto drop-shadow-[0_4px_16px_rgba(0,0,0,0.2)] transition-opacity duration-700",style:{opacity:S?1:0}})]})}):null;return e("div",{className:"flex flex-col w-full h-full relative",style:{background:"linear-gradient(180deg, #E6F3FF 0%, #FFFFFF 100%)"},children:[e("div",{className:"relative w-full flex justify-between items-start pt-6 px-6 z-20 shrink-0",children:[r("div",{className:"flex-1"}),r("div",{className:"flex-none drop-shadow-md",children:r(n,{partNumber:x.part.partNo,questionCount:x.part.questions.length,partName:x.part.name||`Part ${x.part.partNo}`})}),r("div",{className:"flex-1 flex justify-end mt-2 z-20",children:r(f,{total:x.part.questions.length,currentIndex:y.nav.currentIndex,answeredIds:L,questionIds:V})})]}),e("div",{className:"flex-1 w-full flex flex-col items-center min-h-0 px-4 pb-6 pt-2",children:[_.isBackup&&r("div",{className:"bg-amber-500 text-white px-3 py-1 rounded-md text-xs font-bold border border-white shadow-md z-10 shrink-0 mb-4",children:"Câu hỏi dự phòng"}),_.question&&r("div",{className:"z-10 mx-auto px-8 py-2.5 bg-white backdrop-blur-md rounded-full shadow-[0_2px_10px_rgba(0,0,0,0.05)] border border-slate-100 w-fit shrink-0 mb-4",children:r("h3",{className:"text-[17px] md:text-lg font-bold text-[#343C55] font-['Quicksand'] text-center whitespace-pre-wrap leading-tight",children:_.question})}),_.audioUrl&&r("div",{className:"z-10 mx-auto w-full max-w-[280px] shrink-0 mb-4",children:r(o,{audioUrl:_.audioUrl,onPlayingChange:y.onAudioPlayingChange,autoPlay:!x.waitForTeacherVideo})}),e("div",{className:"flex-1 w-full flex justify-center items-center min-h-[250px] relative mb-6",children:[y.showExistingBadge&&e("div",{className:"absolute right-4 top-0 z-10 rounded-full bg-green-500 px-4 py-1.5 flex items-center gap-2 text-sm font-bold text-white shadow-lg border-2 border-white transform -translate-y-1/2",children:[r("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),r("div",E?{className:"flex justify-center items-center w-full max-w-[60%] h-full max-h-[300px] border-4 border-dashed border-gray-300 rounded-xl bg-gray-50/50",children:r("div",{className:"animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"})}:C?{className:"bg-white rounded-2xl shadow-[0_4px_15px_rgba(0,0,0,0.08)] p-2 h-full max-w-[90%] flex justify-center items-center",children:r("img",{src:C,alt:"Scene to describe",className:"max-h-full max-w-full object-contain rounded-xl",draggable:!1})}:{className:"flex justify-center items-center text-orange-800 font-semibold opacity-70 w-full h-full max-h-[300px] border-4 border-dashed border-orange-900/30 rounded-xl bg-orange-900/10",children:"Chưa có hình ảnh mô tả"})]}),y.recorder&&!x.isReviewMode&&e("div",{className:"shrink-0 flex flex-col items-center z-50 gap-3",children:[r("div",{className:"bg-white rounded-[100px] shadow-[0_4px_10px_rgba(0,0,0,0.1)] whitespace-nowrap flex items-center justify-center",style:{padding:"10px 30px",color:"recording"===y.recorder.status?"#FF4D4D":["recorded","uploading","uploaded"].includes(y.recorder.status)?"#16CA3B":"#343C55",fontSize:"18px",fontFamily:'"Quicksand", sans-serif',fontWeight:700,lineHeight:"100%",letterSpacing:"-0.01em"},children:"recording"===y.recorder.status?"Ấn lần nữa để lưu kết quả":["recorded","uploading","uploaded"].includes(y.recorder.status)?"Câu trả lời đã được ghi nhận !":"Ấn biểu tượng Micro để trả lời"}),e("div",{className:"relative flex flex-col items-center",children:["recording"===y.recorder.status?e("div",{className:"relative flex flex-col justify-center items-center cursor-pointer drop-shadow-[0_0_15px_rgba(255,100,100,0.8)] transition-transform hover:scale-[1.05] active:scale-[0.95]",onClick:y.recorder.stopRecording,children:[r(c,{src:h,alt:"stop recording",className:"animate-pulse",style:{width:"120px",height:"120px"}}),e("span",{className:"absolute text-white text-[16px] font-bold font-['Quicksand']",style:{bottom:"30px"},children:[Math.floor(y.recorder.remainingSeconds/60),":",(y.recorder.remainingSeconds%60).toString().padStart(2,"0")]})]}):r(c,{src:["recorded","uploading","uploaded"].includes(y.recorder.status)?T?g:p:u,alt:"mic",className:"transition-transform cursor-pointer hover:scale-[1.05] active:scale-[0.95]",style:{width:"120px",height:"120px"},onClick:()=>{if("recording"===y.recorder?.status)y.recorder.stopRecording();else if("idle"===y.recorder?.status||"error"===y.recorder?.status)y.recorder.startRecording();else if("uploaded"===y.recorder?.status||"recorded"===y.recorder?.status){const e=I.current;e&&(T?e.pause():e.play().catch(console.error))}}}),y.recorder?.previewUrl&&r("audio",{ref:I,src:y.recorder.previewUrl}),"recording"===y.recorder.status&&r("div",{className:"absolute whitespace-nowrap text-center",style:{top:"100%",marginTop:"16px",color:"#FF4D4D",fontSize:"18px",fontFamily:'"Quicksand", sans-serif',fontWeight:700,lineHeight:"100%",letterSpacing:"-0.01em",textShadow:"0 1px 3px rgba(0,0,0,0.15)"},children:"Đang ghi âm câu hỏi . . ."}),["recorded","uploading","uploaded"].includes(y.recorder.status)&&r("div",{className:"absolute whitespace-nowrap text-center flex flex-col items-center",style:{top:"100%",marginTop:"16px",color:"#343C55",fontSize:"18px",fontFamily:'"Quicksand", sans-serif',fontWeight:700,lineHeight:"100%",letterSpacing:"-0.01em"},children:T?`${w(j)}/${A?w(A):"--:--"}`:"Nghe đáp án"})]})]})]}),B]})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingDragObjectsRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function SpeakingDragObjectsRenderer(props: SpeakingDragObjectsRendererProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t,Fragment as r}from"react/jsx-runtime";import{useMemo as a,useState as n}from"react";import{X as o}from"lucide-react";import{usePresignedFileUrl as i}from"../../../../../../shared/lib/hooks";import{useSpeakingRendererSetup as s}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as d}from"../../../../../../components/themes";import l from"../SpeakingDragObjects.module.css";import c from"../SpeakingReadDisplayedContent.module.css";function u({total:r,currentIndex:a,answeredIds:n,questionIds:o}){return e("div",{className:c.taskIndicator,style:{color:"#1e3a8a",textShadow:"none",fontWeight:800},children:[e("span",{children:["Question ",a+1,"/ ",r]}),t("div",{className:"flex gap-2",children:Array.from({length:r},(e,r)=>{const i=r===a,s=n.has(o[r]);return t("div",{className:`${c.taskDot} ${i?c.taskDotActive:""} ${s&&!i?c.taskDotCompleted:""}`,style:i||s?void 0:{background:"#bfdbfe"}},r)})})]})}function m({node:e,isPlaced:r,onDragStart:a}){const{previewUrl:n}=i(e.imageUrl||"");return t("div",{className:l.dragItemContainer,children:t("div",{id:e.id,draggable:!r,className:l.dragItem,style:{cursor:r?"not-allowed":"move",position:"relative",zIndex:2,opacity:r?.3:1},onDragStart:t=>{r?t.preventDefault():(t.dataTransfer.setData("text/plain",e.id),t.dataTransfer.effectAllowed="move",a(e.id))},children:n?t("img",{alt:e.label||"icon",src:n,style:{maxHeight:"100%",maxWidth:"100%",height:"auto",width:"auto",objectFit:"contain"},draggable:!1}):t("div",{className:"flex h-full w-full items-center justify-center text-[10px] text-center px-1 text-gray-500 font-medium",children:e.label})})})}function p({zone:a,droppedObject:n,isDragOver:s,isReviewMode:d,onDragOver:c,onDrop:u,onDragLeave:m,onRemove:p}){const{previewUrl:h}=i(n?.imageUrl??"");return t("div",{onDragOver:e=>c(e,a.id),onDrop:e=>u(e,a.id),onDragLeave:m,"data-testid":`(${a.x}%,${a.y}%)`,style:{position:"absolute",zIndex:1,top:`${a.y}%`,left:`${a.x}%`,height:`${a.height}%`,width:`${a.width}%`,backgroundColor:n?"transparent":s?"rgba(245, 158, 11, 0.4)":"rgba(255, 255, 255, 0.2)",border:n?"none":s?"2px dashed #f59e0b":"1px dashed rgba(255,255,255,0.5)",borderRadius:"8px",transition:"all 0.2s"},children:n&&h&&e(r,{children:[t("img",{src:h,alt:n.label,className:l.objectImageMoved,style:{position:"absolute",height:"100%",width:"100%",inset:0,objectFit:"contain"},draggable:!1}),!d&&t("button",{type:"button",onClick:e=>{e.stopPropagation(),p(a.id)},className:"absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-white shadow-md hover:bg-red-600 z-10 border border-white",title:"Remove",children:t(o,{className:"h-3 w-3"})})]})})}export function SpeakingDragObjectsRenderer(r){const o=s({...r,hasMic:!1}),c=d(r.speakingTheme),h=o.currentQuestion?.content,g=o.currentQuestion,v=a(()=>{let e=g?.questionGroup?.payload||g?.question_group?.payload;if("string"==typeof e)try{e=JSON.parse(e)}catch{e={}}return e||{}},[g?.questionGroup?.payload,g?.question_group?.payload]),f=h?.audioUrl??"",b=v?.backgroundImageUrl??"",x=v?.objectNodes??[],w=v?.dropZones??[],{previewUrl:N}=i(b),[D,y]=a(()=>{const e=Math.ceil(x.length/2);return[x.slice(0,e),x.slice(e)]},[x]),[I,j]=n(null),k=o.currentQuestion?r.answers[o.currentQuestion.id]:void 0,C=k?.dropZoneId??null,R=k?.objectId??null,P=e=>{if(C===e&&R)return t=R,x.find(e=>e.id===t);var t},T=e=>{},O=(e,t)=>{e.preventDefault(),e.dataTransfer.dropEffect="move",j(t)},S=(e,t)=>{if(e.preventDefault(),j(null),!o.currentQuestion||r.isReviewMode)return;const a=e.dataTransfer.getData("text/plain");a&&r.onAnswerChange(o.currentQuestion.id,{objectId:a,dropZoneId:t})},$=()=>j(null),q=e=>{o.currentQuestion&&!r.isReviewMode&&C===e&&r.onAnswerChange(o.currentQuestion.id,void 0)};if(!o.currentQuestion)return null;const E=new Set(r.part.questions.map(e=>e.id).filter(e=>!!r.answers[e])),A=r.part.questions.map(e=>e.id),Q=t(u,{total:r.part.questions.length,currentIndex:o.nav.currentIndex,answeredIds:E,questionIds:A});return t(c,{onExit:r.onExit,partName:r.part.name||`Part ${r.part.partNo}`,instruction:r.part.instructions||"LISTEN AND DRAG THE CORRECT OBJECT INTO THE SCENE.",audioUrl:f,questionNumber:o.nav.questionNumberInPart,onPrev:o.handlePrev,onNext:o.handleNext,showPrev:o.nav.showPrev,showNext:o.nav.showNext,canGoPrev:o.nav.canGoPrev,canGoNext:o.nav.canGoNext,onAudioPlayingChange:o.onAudioPlayingChange,recorder:null,headerRight:Q,teacherVideoUrl:r.teacherVideoUrl,onTeacherVideoEnded:r.onTeacherIntroPlayed,skipTeacherVideo:r.skipTeacherVideo,contentClassName:"!p-0 !gap-0",children:e("div",{className:l.moveObjectContainer,children:[t("div",{className:l.objectChoices,children:D.map(e=>t(m,{node:e,isPlaced:R===e.id,onDragStart:T},e.id))}),t("div",{className:l.objectBackground,children:t("div",{className:`relative flex items-center justify-center w-full h-full ${l.imageBackground}`,children:t("div",{className:"relative w-full max-w-[950px] mx-auto rounded-xl drop-shadow-md bg-white border-2 border-stone-200 overflow-hidden mb-6",children:N?e("div",{className:l.imageAspectWrapper,children:[t("img",{alt:"scene",src:N,className:l.mainImg,draggable:!1}),w.map(e=>t(p,{zone:e,droppedObject:P(e.id),isDragOver:I===e.id,isReviewMode:r.isReviewMode||!1,onDragOver:O,onDrop:S,onDragLeave:$,onRemove:q},e.id))]}):t("div",{className:"flex h-full w-full items-center justify-center text-sm text-stone-400",children:"Đang tải ảnh..."})})})}),t("div",{className:`${l.objectChoices} ${l.objectChoicesRight}`,children:y.map(e=>t(m,{node:e,isPlaced:R===e.id,onDragStart:T},e.id))})]})})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingGNInterviewRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function SpeakingGNInterviewRenderer(props: SpeakingGNInterviewRendererProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as i,useEffect as s,useRef as n,useCallback as a,useMemo as r}from"react";import l from"next/image";import{usePresignedFileUrl as o}from"../../../../../../shared/lib/hooks";import{useSpeakingRendererSetup as c}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as d}from"../../../../../../components/themes";import p from"../../../../../../components/themes/spaceship/assets/images/question-badge-sunset.png";import h from"../../../../../../components/themes/spaceship/assets/images/red-badge-sunset.png";import m from"../../../../../../components/themes/spaceship/assets/images/blue-badge-sunset.png";import u from"../../../../../../components/themes/spaceship/assets/images/red-wrapper-sunset.png";import x from"../../../../../../components/themes/spaceship/assets/images/blue-wrapper-sunset.png";import f from"../../../../../../components/themes/spaceship/assets/images/robot-avatar.png";import g from"../../../../../../components/themes/spaceship/assets/images/student-avatar.png";import y from"../../../../../../components/themes/spaceship/assets/images/ic-play-sunset.png";import v from"../../../../../../components/themes/spaceship/assets/images/ic-play-violet.webp";import w from"../../../../../../components/themes/spaceship/assets/images/next-small.webp";function N({total:t,currentIndex:i,answeredIds:s,questionIds:n,virtualIndices:a}){return e("div",{className:"flex items-center gap-1.5 flex-wrap justify-center",children:Array.from({length:t},(t,r)=>{const l=r===i,o=s.has(n[r]),c=a.has(r);return e("div",{className:"h-2.5 w-2.5 rounded-full border-2 transition-all "+(l?"scale-125 border-yellow-300 bg-yellow-400 shadow-lg":o?"border-green-400 bg-green-400":c?"border-white/30 bg-white/10":"border-white/50 bg-white/20"),title:c?"Câu HS ảo":`Câu ${r+1}`},r)})})}export function SpeakingGNInterviewRenderer(b){const[S,R]=i(!0),[j,I]=i(!1),P=n(null),T=b.part.groupPayload,k=T?.virtualStudentName||"Học sinh AI",W=r(()=>{const e=new Set;return b.part.questions.forEach((t,i)=>{const s=t.content;"VIRTUAL"===s?.targetCandidate&&e.add(i)}),e},[b.part.questions]),q=c({...b,maxDurationMs:6e4,hasMic:!0}),A=d("SPACESHIP"),z=q.currentQuestion?.content,C="VIRTUAL"===z?.targetCandidate,U=!C,{previewUrl:E}=o(z?.virtualAnswer?.audioUrl??""),{previewUrl:B}=o(z?.imageUrl??""),L=!!z?.imageUrl&&!!B;s(()=>{P.current&&(P.current.pause(),P.current.currentTime=0),I(!1)},[q.currentQuestion?.id]);const V=a(()=>{if(E){if(j&&P.current)return P.current.pause(),void I(!1);P.current?P.current.src=E:(P.current=new Audio(E),P.current.onended=()=>I(!1),P.current.onerror=()=>I(!1)),P.current.play(),I(!0)}},[E,j]);s(()=>()=>{P.current&&(P.current.pause(),P.current=null)},[]);const H=a(()=>{if(!C||!E)return;const e=setTimeout(()=>{V()},1e3);return()=>clearTimeout(e)},[C,E,V]);if(!q.currentQuestion||!z)return null;const F=new Set(b.part.questions.map(e=>e.id).filter(e=>!!b.answers[e])),Y=b.part.questions.map(e=>e.id),D=e(N,{total:b.part.questions.length,currentIndex:q.nav.currentIndex,answeredIds:F,questionIds:Y,virtualIndices:W});return t(A,{onExit:b.onExit,partName:b.part.name||`Part ${b.part.partNo}`,instruction:b.part.instructions||"LISTEN AND ANSWER",audioUrl:z.audioUrl??"",questionNumber:q.nav.questionNumberInPart,onPrev:q.handlePrev,onNext:q.handleNext,showPrev:q.nav.showPrev,showNext:q.nav.showNext,canGoPrev:q.nav.canGoPrev,canGoNext:q.nav.canGoNext,onAudioPlayingChange:q.onAudioPlayingChange,recorder:b.isReviewMode||C?null:q.recorder,showExistingBadge:U&&q.showExistingBadge,teacherVideoUrl:b.teacherVideoUrl,onTeacherVideoEnded:b.onTeacherIntroPlayed,skipTeacherVideo:b.skipTeacherVideo,headerRight:D,onAudioEnded:H,children:[t("div",{className:"w-full h-full flex flex-col items-center relative",children:[e("div",{className:"w-full flex-1",style:{minHeight:0},children:e("div",{className:"mx-auto",style:{width:"850px",maxWidth:"90%",height:L?"auto":"calc(100% - 100px)"},children:t("div",{className:`w-full ${L?"":"h-full"} flex flex-col items-center`,style:L?{}:{paddingTop:"30px"},children:[S&&z.questionText&&t("div",{className:"relative flex flex-col justify-center items-center",style:{width:"882px",maxWidth:"90%",maxHeight:"80%",paddingLeft:"77px",paddingRight:"77px",paddingTop:"26px",paddingBottom:"26px",backgroundColor:"rgba(232, 136, 154, 0.15)",borderRadius:"20px"},children:[t("div",{className:"absolute cursor-pointer",style:{top:"-19px"},onClick:()=>R(!1),children:[e(l,{src:p,alt:"",style:{width:"169px",height:"38px"}}),e("div",{className:"absolute inset-0 flex justify-center items-center select-none",children:e("span",{className:"font-normal text-center leading-5 text-white",style:{fontSize:"18px"},children:"Ẩn câu hỏi"})})]}),e("div",{className:"max-w-full",children:e("div",{className:"text-black font-bold leading-[26px] text-center max-w-full",style:{fontSize:"18px",fontFamily:'"Open Sans", sans-serif'},children:e("p",{children:z.questionText})})})]}),!S&&t("div",{className:"cursor-pointer",onClick:()=>R(!0),children:[e(l,{src:p,alt:"",style:{width:"169px",height:"38px"}}),e("div",{className:"absolute inset-0 flex justify-center items-center select-none",style:{position:"relative",top:"-30px"},children:e("span",{className:"font-normal text-center leading-5 text-white",style:{fontSize:"18px"},children:"Hiện câu hỏi"})})]}),L&&e("div",{className:"flex justify-center",style:{marginTop:"12px"},children:e("div",{className:"relative overflow-hidden",style:{maxWidth:"100%",borderRadius:"16px",border:"3px solid rgba(230, 99, 93, 0.3)",boxShadow:"0 4px 15px rgba(0,0,0,0.08)"},children:e("img",{src:B,alt:"Booklet",style:{width:"100%",height:"100%",objectFit:"cover"}})})})]})})}),t("div",{className:"flex justify-between select-none items-end",style:{width:"990px",maxWidth:"95%",paddingBottom:"40px",paddingLeft:"20px",paddingRight:"20px"},children:[t("div",{className:"relative select-none transition-opacity duration-300 "+(C?"opacity-100":"opacity-50 grayscale"),children:[e("div",{children:t("div",{className:"relative flex",children:[e(l,{src:h,alt:"",style:{height:"57px",width:"258px"}}),e("div",{className:"absolute inset-0 flex justify-center items-center",style:{paddingTop:"12px"},children:e("div",{className:"text-white leading-5 font-normal text-center m-0 w-full select-none",children:e("p",{className:"m-0 truncate select-none",style:{fontSize:"18px",paddingLeft:"60px",paddingRight:"60px"},children:k})})})]})}),t("div",{className:"absolute",style:{top:"7px",right:"-58px"},children:[e(l,{src:u,alt:"",className:"relative",style:{width:"115px",height:"84px",zIndex:0}}),e("div",{className:"absolute inset-0 flex justify-center items-center w-full h-full",children:t("div",{className:"relative",style:{width:"68px",height:"68px"},children:[e(l,{src:f,alt:k,width:68,height:68,className:"object-cover rounded-full select-none",style:{width:"68px",height:"68px"},draggable:!1}),e("div",{className:"flex items-center justify-center absolute w-full h-full top-0 rounded-full text-white cursor-pointer",onClick:C?V:void 0,children:e(l,{src:y,alt:"play",width:16,height:16,className:"opacity-80"})})]})})]}),j&&e("div",{className:"absolute pointer-events-none",style:{top:"0px",right:"-130px",width:"70px",height:"84px",display:"flex",alignItems:"center"},children:t("div",{className:"relative",style:{width:"70px",height:"70px"},children:[e("div",{style:{position:"absolute",left:"0px",top:"50%",transform:"translateY(-50%)",width:"20px",height:"34px",borderRight:"3px solid rgba(30, 64, 175, 0.95)",borderRadius:"0 50% 50% 0",animation:"soundWavePulse 1s ease-in-out infinite",animationDelay:"0s"}}),e("div",{style:{position:"absolute",left:"12px",top:"50%",transform:"translateY(-50%)",width:"28px",height:"50px",borderRight:"3px solid rgba(30, 64, 175, 0.7)",borderRadius:"0 50% 50% 0",animation:"soundWavePulse 1s ease-in-out infinite",animationDelay:"0.2s"}}),e("div",{style:{position:"absolute",left:"24px",top:"50%",transform:"translateY(-50%)",width:"36px",height:"66px",borderRight:"3px solid rgba(30, 64, 175, 0.45)",borderRadius:"0 50% 50% 0",animation:"soundWavePulse 1s ease-in-out infinite",animationDelay:"0.4s"}})]})})]}),j&&e("div",{className:"flex-1 flex justify-center items-center text-center",style:{paddingLeft:"40px",paddingRight:"40px",paddingBottom:"40px"},children:t("p",{className:"m-0 select-none",style:{fontSize:"16px",fontWeight:700,fontStyle:"italic",color:"#1a1a2e",lineHeight:"1.5",fontFamily:'"Open Sans", sans-serif'},children:["Người chơi khác đang trả lời.",e("br",{}),"Vui lòng nghe xong để tiếp tục."]})}),C&&!j&&t("div",{className:"flex-1 flex flex-col justify-center items-center text-center",style:{paddingLeft:"40px",paddingRight:"40px",paddingBottom:"40px"},children:[t("p",{className:"m-0 whitespace-nowrap",style:{color:"#1a1a2e",fontSize:"16px",fontWeight:700,fontStyle:"italic",fontFamily:'"Open Sans", sans-serif'},children:["Ấn biểu tượng"," ",e(l,{src:v,alt:"",width:20,height:20,className:"inline-block"})," ","trên avatar để nghe lại."]}),t("div",{className:"flex items-center",style:{gap:"4px"},children:[e("p",{className:"m-0",style:{color:"#1a1a2e",fontSize:"16px",fontWeight:700,fontStyle:"italic",fontFamily:'"Open Sans", sans-serif'},children:"Ấn nút"}),e(l,{src:w,alt:"next",width:34,height:34}),e("p",{className:"m-0",style:{color:"#1a1a2e",fontSize:"16px",fontWeight:700,fontStyle:"italic",fontFamily:'"Open Sans", sans-serif'},children:"để tiếp tục."})]})]}),t("div",{className:"relative select-none transition-opacity duration-300 "+(U?"opacity-100":"opacity-50 grayscale"),children:[e("div",{children:t("div",{className:"relative flex",children:[e(l,{src:m,alt:"",style:{height:"57px",width:"258px"}}),e("div",{className:"absolute inset-0 flex justify-center items-center",style:{paddingTop:"12px"},children:e("div",{className:"text-white leading-5 font-normal text-center m-0 w-full select-none",children:e("p",{className:"m-0 truncate select-none",style:{fontSize:"18px",paddingLeft:"60px",paddingRight:"60px"},children:"Bạn"})})})]})}),t("div",{className:"absolute",style:{top:"7px",left:"-58px"},children:[e(l,{src:x,alt:"",className:"relative",style:{width:"115px",height:"84px",zIndex:0}}),e("div",{className:"absolute inset-0 flex justify-center items-center w-full h-full",children:e("div",{className:"relative",style:{width:"68px",height:"68px"},children:e(l,{src:g,alt:"Bạn",width:68,height:68,className:"object-cover rounded-full select-none",style:{width:"68px",height:"68px"},draggable:!1})})})]})]})]})]}),e("style",{jsx:!0,children:"\n @keyframes soundWavePulse {\n 0% {\n opacity: 0.2;\n transform: translateY(-50%) scaleX(0.7);\n }\n 50% {\n opacity: 1;\n transform: translateY(-50%) scaleX(1);\n }\n 100% {\n opacity: 0.2;\n transform: translateY(-50%) scaleX(0.7);\n }\n }\n "})]})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingInfoExchangeRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function SpeakingInfoExchangeRenderer(props: SpeakingInfoExchangeRendererProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{ResolvedImage as i}from"../../../../../../components/common/ResolvedImage";import{useSpeakingRendererSetup as n}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as r}from"../../../../../../components/themes";import t from"../SpeakingInfoExchange.module.css";import s from"../SpeakingInfoExchangeSummerSky.module.css";export function SpeakingInfoExchangeRenderer(l){const d=n({...l,maxDurationMs:3e4}),c=r(l.speakingTheme),m="SUMMER_SKY"===l.speakingTheme,h=m?s:t,f=d.currentQuestion?.content,u=d.currentQuestion?.questionGroup?.payload;if(!d.currentQuestion||!f)return null;const g=f.audioUrl??"",p=u?.title??"",N=u?.imageUrl??"",v=u?.candidateFields??[],x=u?.examinerFields??[];return e(c,{onExit:l.onExit,partName:l.part.name||`Part ${l.part.partNo}`,instruction:l.part.instructions||"INFORMATION EXCHANGE",audioUrl:g,questionNumber:d.nav.questionNumberInPart,onPrev:d.handlePrev,onNext:d.handleNext,showPrev:d.nav.showPrev,showNext:d.nav.showNext,canGoPrev:d.nav.canGoPrev,canGoNext:d.nav.canGoNext,onAudioPlayingChange:d.onAudioPlayingChange,recorder:l.isReviewMode?null:d.recorder,showExistingBadge:d.showExistingBadge,teacherVideoUrl:l.teacherVideoUrl,onTeacherVideoEnded:l.onTeacherIntroPlayed,skipTeacherVideo:l.skipTeacherVideo,contentClassName:"!gap-2",children:e("div",{className:"w-full h-full",children:e("div",{className:h.part2Container,children:a("div",{className:h.questionContainer,children:[N&&a("div",m?{className:h.imageWrapper,children:[d.showExistingBadge&&a("div",{className:h.recordedBadge,children:[e("span",{className:h.recordedBadgeDot}),"Đã ghi âm"]}),e("div",{className:h.imageFrame,children:e(i,{src:N,alt:p||"Info Exchange",className:"object-contain h-full w-full pointer-events-none",draggable:!1})})]}:{className:"relative h-full flex items-center justify-center",children:[d.showExistingBadge&&a("div",{className:"absolute right-4 top-4 z-10 rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[e("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),e("div",{className:t.imageBorder,children:e(i,{src:N,alt:p||"Info Exchange",className:"object-cover h-full w-full pointer-events-none",draggable:!1})})]}),a("div",{className:h.infoContainer,children:[e(o,{heading:"Candidate's Copy",title:p,fields:v,styles:h,isSummerSky:m}),e(o,{heading:"Examiner's Copy",title:p,fields:x,styles:h,isSummerSky:m})]})]})})})})}function o({heading:i,title:n,fields:r,styles:t,isSummerSky:s}){const o=r.map((i,n)=>{const r=!i.answer||"___"===i.answer||"?"===i.answer;return a("div",s?{className:t.infoDetailItem,children:[e("div",{className:t.infoDetailLabel,children:i.key}),e("div",{className:t.infoDetailDivide}),e("div",{className:[t.infoDetailValue,r?t.infoDetailValueBlank:""].join(" "),children:r?"?":i.answer})]}:{className:t.infoDetailItem,children:[e("div",{className:t.infoDetailItemValue,style:{textAlign:"right",flex:1,minWidth:0,wordBreak:"break-word",paddingRight:"10px",color:"#fff",fontSize:"18px",fontWeight:700},children:i.key}),e("div",{className:t.infoDetailItemDivide}),e("div",{className:t.infoDetailItemValue,style:{textAlign:"left",flex:1,minWidth:0,wordBreak:"break-word",paddingLeft:"10px",color:r?"#a8a29e":"#fff",fontStyle:r?"italic":"normal",fontSize:"18px",fontWeight:r?"normal":700},children:r?"?":i.answer})]},n)});return a("div",s?{className:t.infoItem,children:[e("div",{className:t.infoHeader,children:i}),a("div",{className:t.infoItemInner,children:[n&&e("div",{className:t.infoName,children:n}),o]})]}:{className:t.infoItem,children:[e("div",{className:t.infoHeader,children:i}),n&&e("div",{className:t.infoName,children:n}),o]})}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingListenAndSpeakAnswerRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function SpeakingListenAndSpeakAnswerRenderer(props: SpeakingListenAndSpeakAnswerRendererProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{usePresignedFileUrl as r}from"../../../../../../shared/lib/hooks";import{useSpeakingRendererSetup as n}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as a}from"../../../../../../components/themes";import o from"../SpeakingReadDisplayedContent.module.css";function s({total:r,currentIndex:n,answeredIds:a,questionIds:s}){return e("div",{className:o.taskIndicator,style:{color:"#1e3a8a",textShadow:"none",fontWeight:800},children:[e("span",{children:["Question ",n+1,"/ ",r]}),t("div",{className:"flex gap-2",children:Array.from({length:r},(e,r)=>{const i=r===n,l=a.has(s[r]);return t("div",{className:`${o.taskDot} ${i?o.taskDotActive:""} ${l&&!i?o.taskDotCompleted:""}`,style:i||l?void 0:{background:"#bfdbfe"}},r)})})]})}function i({backgroundImageUrl:n,arrowNode:a}){const{previewUrl:o}=r(n);return o?e("div",{className:"relative w-full flex justify-center items-center",children:[t("img",{src:o,alt:"Scene",className:"block w-full h-auto max-h-[calc(100vh-280px)] object-contain rounded-xl",draggable:!1}),a&&t("div",{className:"absolute",style:{left:`${a.x}%`,top:`${a.y}%`,width:`${a.width}%`,height:`${a.height}%`},"aria-hidden":"true",children:t("div",{className:"flex h-full w-full items-end justify-center pb-1",children:t("svg",{viewBox:"0 0 24 24",className:"w-full h-full text-red-500 drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]",fill:"currentColor",children:t("path",{d:"M12 21L4 12h5V3h6v9h5L12 21z"})})})})]}):null}export function SpeakingListenAndSpeakAnswerRenderer(r){const o=n({...r,maxDurationMs:3e4}),l=a(r.speakingTheme),d=o.currentQuestion?.content;if(!o.currentQuestion||!d)return null;const c=d.audioUrl??"",h=d.backgroundImageUrl??"",u=d.arrowNode,m=new Set(r.part.questions.map(e=>e.id).filter(e=>!!r.answers[e])),p=r.part.questions.map(e=>e.id),x=t(s,{total:r.part.questions.length,currentIndex:o.nav.currentIndex,answeredIds:m,questionIds:p});return t(l,{onExit:r.onExit,partName:r.part.name||`Part ${r.part.partNo}`,instruction:r.part.instructions||"LISTEN AND SPEAK YOUR ANSWER.",audioUrl:c,questionNumber:o.nav.questionNumberInPart,onPrev:o.handlePrev,onNext:o.handleNext,showPrev:o.nav.showPrev,showNext:o.nav.showNext,canGoPrev:o.nav.canGoPrev,canGoNext:o.nav.canGoNext,onAudioPlayingChange:o.onAudioPlayingChange,recorder:r.isReviewMode?null:o.recorder,showExistingBadge:o.showExistingBadge,headerRight:x,teacherVideoUrl:r.teacherVideoUrl,onTeacherVideoEnded:r.onTeacherIntroPlayed,skipTeacherVideo:r.skipTeacherVideo,contentClassName:"!p-0 !gap-0",children:t("div",{className:"w-full h-full flex items-center justify-center px-8 pb-[80px] pt-[70px]",children:e("div",{className:"relative w-full max-w-[850px] bg-white rounded-xl drop-shadow-md border-2 border-stone-200 overflow-hidden",children:[o.showExistingBadge&&e("div",{className:"absolute right-4 top-4 z-10 rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),h?t(i,{backgroundImageUrl:h,arrowNode:u}):t("div",{className:"flex h-[400px] w-full justify-center items-center text-stone-400 font-medium",children:"Đang tải ảnh..."})]})})})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingOddOneOutRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
onTeacherIntroPlayed?: () => void;
|
|
13
|
+
skipTeacherVideo?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function SpeakingOddOneOutRenderer(props: SpeakingOddOneOutRendererProps): import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{cn as n}from"../../../../../../shared/lib/utils";import{ResolvedImage as r}from"../../../../../../components/common/ResolvedImage";import{useSpeakingRendererSetup as a}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as i}from"../../../../../../components/themes";import s from"../SpeakingReadDisplayedContent.module.css";function o({total:n,currentIndex:r,answeredIds:a,questionIds:i}){return e("div",{className:s.taskIndicator,style:{color:"#1e3a8a",textShadow:"none",fontWeight:800},children:[e("span",{children:["Question ",r+1,"/ ",n]}),t("div",{className:"flex gap-2",children:Array.from({length:n},(e,n)=>{const o=n===r,d=a.has(i[n]);return t("div",{className:`${s.taskDot} ${o?s.taskDotActive:""} ${d&&!o?s.taskDotCompleted:""}`,style:o||d?void 0:{background:"#bfdbfe"}},n)})})]})}export function SpeakingOddOneOutRenderer(e){const n=a({...e,maxDurationMs:3e4}),r=i(e.speakingTheme),s=n.currentQuestion?.content,l=n.currentQuestion?.questionGroup?.payload;if(!n.currentQuestion||!s)return null;const c=s.audioUrl??"",m=l?.images??[],u=s.images,h=m.length>0?m:(u??[]).map((e,t)=>({id:`img-${t}`,url:e,label:String(t+1)})),g=s.activeImageId,p=new Set(e.part.questions.map(e=>e.id).filter(t=>!!e.answers[t])),x=e.part.questions.map(e=>e.id),v=t(o,{total:e.part.questions.length,currentIndex:n.nav.currentIndex,answeredIds:p,questionIds:x});return t(r,{onExit:e.onExit,partName:e.part.name||`Part ${e.part.partNo}`,instruction:e.part.instructions||"WHICH ONE IS DIFFERENT? LISTEN AND EXPLAIN.",audioUrl:c,questionNumber:n.nav.questionNumberInPart,onPrev:n.handlePrev,onNext:n.handleNext,showPrev:n.nav.showPrev,showNext:n.nav.showNext,canGoPrev:n.nav.canGoPrev,canGoNext:n.nav.canGoNext,onAudioPlayingChange:n.onAudioPlayingChange,recorder:e.isReviewMode?null:n.recorder,showExistingBadge:n.showExistingBadge,headerRight:v,teacherVideoUrl:e.teacherVideoUrl,onTeacherVideoEnded:e.onTeacherIntroPlayed,skipTeacherVideo:e.skipTeacherVideo,children:t("div",{className:"w-full flex justify-center items-center",style:{flex:"1 1 0",minHeight:0},children:t(d,{images:h,activeImageId:g,showExistingBadge:n.showExistingBadge})})})}function d({images:a,activeImageId:i,showExistingBadge:s}){return 0===a.length?t("div",{className:"flex h-40 items-center justify-center text-stone-400",children:t("span",{className:"text-sm",children:"No images available"})}):e("div",{className:"relative flex h-full items-center justify-center px-4 py-3",children:[s&&t("div",{className:"absolute right-3 top-3 z-10 rounded-full bg-green-600 px-3 py-1 text-xs font-bold text-white shadow",children:"Đã ghi âm"}),t("div",{className:"flex items-end gap-6",children:a.map(e=>{const a=e.id===i;return t("div",{className:"flex flex-col items-center",children:t("div",{className:n("overflow-hidden",a?"rounded-[10px] border-4 border-white shadow-lg":"rounded-[10px] border-4 border-white shadow-sm"),style:{width:a?"300px":"220px",height:a?"300px":"220px"},children:t(r,{src:e.url,alt:`Image ${e.label}`,className:"h-full w-full object-cover pointer-events-none",draggable:!1})})},e.id)})})]})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpeakingQuestionListRenderer
|
|
3
|
+
*
|
|
4
|
+
* Renderer for LISTEN_AND_SPEAK_QUESTION_LIST (Movers/Flyers Part 4).
|
|
5
|
+
*
|
|
6
|
+
* The group payload holds a list of question texts. Each individual question has an
|
|
7
|
+
* `activeQuestionId` pointing to the question that is currently being asked.
|
|
8
|
+
* The matching item is highlighted. The student listens and records their answer.
|
|
9
|
+
*/
|
|
10
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
11
|
+
interface SpeakingQuestionListRendererProps {
|
|
12
|
+
part: QuestionPart;
|
|
13
|
+
answers: Record<string, unknown>;
|
|
14
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
15
|
+
isReviewMode?: boolean;
|
|
16
|
+
onExit: () => void;
|
|
17
|
+
onPartComplete?: () => void;
|
|
18
|
+
onPartBack?: () => void;
|
|
19
|
+
speakingTheme?: string;
|
|
20
|
+
teacherVideoUrl?: string;
|
|
21
|
+
onTeacherIntroPlayed?: () => void;
|
|
22
|
+
skipTeacherVideo?: boolean;
|
|
23
|
+
waitForTeacherVideo?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function SpeakingQuestionListRenderer(props: SpeakingQuestionListRendererProps): import("react").JSX.Element;
|
|
26
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{useSpeakingRendererSetup as n}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as i}from"../../../../../../components/themes";import a from"../SpeakingReadDisplayedContent.module.css";function s({total:n,currentIndex:i,answeredIds:s,questionIds:r}){return e("div",{className:a.taskIndicator,style:{color:"#1e3a8a",textShadow:"none",fontWeight:800},children:[e("span",{children:["Question ",i+1,"/ ",n]}),t("div",{className:"flex gap-2",children:Array.from({length:n},(e,n)=>{const o=n===i,l=s.has(r[n]);return t("div",{className:`${a.taskDot} ${o?a.taskDotActive:""} ${l&&!o?a.taskDotCompleted:""}`,style:o||l?void 0:{background:"#bfdbfe"}},n)})})]})}export function SpeakingQuestionListRenderer(e){const a=n({...e,maxDurationMs:15e3}),o=i(e.speakingTheme),l=a.currentQuestion?.content,d=a.currentQuestion?.questionGroup?.payload;if(!a.currentQuestion||!l)return null;const c=l.audioUrl??"",u=d?.content??d?.questions??[],h=l.activeQuestionId,x=new Set(e.part.questions.map(e=>e.id).filter(t=>!!e.answers[t])),m=e.part.questions.map(e=>e.id),p=t(s,{total:e.part.questions.length,currentIndex:a.nav.currentIndex,answeredIds:x,questionIds:m});return t(o,{onExit:e.onExit,partName:e.part.name||`Part ${e.part.partNo}`,instruction:e.part.instructions||"ANSWER THE QUESTIONS.",audioUrl:c,questionNumber:a.nav.questionNumberInPart,onPrev:a.handlePrev,onNext:a.handleNext,showPrev:a.nav.showPrev,showNext:a.nav.showNext,canGoPrev:a.nav.canGoPrev,canGoNext:a.nav.canGoNext,onAudioPlayingChange:a.onAudioPlayingChange,recorder:e.isReviewMode?null:a.recorder,showExistingBadge:a.showExistingBadge,headerRight:p,teacherVideoUrl:e.teacherVideoUrl,onTeacherVideoEnded:e.onTeacherIntroPlayed,skipTeacherVideo:e.skipTeacherVideo,waitForTeacherVideo:e.waitForTeacherVideo,contentClassName:"!p-0 !gap-0",children:t("div",{className:"w-full h-full flex items-center justify-center px-8 pb-[80px] pt-[70px]",children:t(r,{questionList:u,activeQuestionId:h,showExistingBadge:a.showExistingBadge})})})}function r({questionList:n,activeQuestionId:i,showExistingBadge:a}){return 0===n.length?t("div",{className:"flex h-40 items-center justify-center text-stone-400",children:t("span",{className:"text-sm",children:"No questions available"})}):e("div",{className:"relative w-full max-w-[820px] flex flex-col gap-3",children:[a&&e("div",{className:"absolute right-0 -top-8 z-10 rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),n.map((e,n)=>{const a=e.id===i;return t(o,{index:n,text:e.text,isActive:a},e.id)})]})}function o({index:n,text:i,isActive:a}){return e("div",{className:`\n flex items-center gap-4 w-full px-6 py-4 rounded-2xl\n border-2 transition-all duration-300 select-none\n ${a?"bg-gradient-to-r from-[#3B82F6] to-[#1D4ED8] border-[#60A5FA] shadow-lg shadow-blue-200/60":"bg-white border-[#BFDBFE] shadow-sm"}\n `,children:[t("div",{className:`\n flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center\n text-base font-bold transition-colors duration-300\n ${a?"bg-white/20 text-white":"bg-blue-100 text-blue-600"}\n `,children:n+1}),t("span",{className:`\n text-xl font-semibold leading-snug tracking-wide transition-colors duration-300\n ${a?"text-white":"text-slate-500"}\n `,children:i}),a&&t("div",{className:"ml-auto flex-shrink-0",children:e("span",{className:"flex h-3 w-3",children:[t("span",{className:"animate-ping absolute inline-flex h-3 w-3 rounded-full bg-white opacity-75"}),t("span",{className:"relative inline-flex rounded-full h-3 w-3 bg-white"})]})})]})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpeakingQuestionRenderer — Factory component for speaking question types.
|
|
3
|
+
*
|
|
4
|
+
* Reads `part.questionType` and dispatches to the appropriate speaking renderer.
|
|
5
|
+
* This is the single entry point from QuestionRenderer.tsx for all LISTEN_AND_SPEAK_* types.
|
|
6
|
+
*
|
|
7
|
+
* `onExit` is optional: when omitted the renderers default to router.back().
|
|
8
|
+
* During Phase 4 integration, page.tsx should pass its handleClose as onExit.
|
|
9
|
+
*/
|
|
10
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
11
|
+
interface SpeakingQuestionRendererProps {
|
|
12
|
+
part: QuestionPart;
|
|
13
|
+
answers: Record<string, unknown>;
|
|
14
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
15
|
+
isReviewMode?: boolean;
|
|
16
|
+
/** Override the exit action. Defaults to router.back(). */
|
|
17
|
+
onExit?: () => void;
|
|
18
|
+
/** Called when navigating past the last question of this part. */
|
|
19
|
+
onPartComplete?: () => void;
|
|
20
|
+
/** Called when navigating back from the first question of this part. */
|
|
21
|
+
onPartBack?: () => void;
|
|
22
|
+
speakingTheme?: string;
|
|
23
|
+
initialQuestionIndex?: number;
|
|
24
|
+
/** Skip teacher intro video (already played for this part). */
|
|
25
|
+
skipTeacherVideo?: boolean;
|
|
26
|
+
/** Callback when teacher intro video finishes playing. */
|
|
27
|
+
onTeacherIntroPlayed?: () => void;
|
|
28
|
+
/** Index of the question to render within the part (for Cambridge YLE 1-by-1 navigation) */
|
|
29
|
+
currentQuestionIndex?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function SpeakingQuestionRenderer({ part, answers, onAnswerChange, isReviewMode, onExit, onPartComplete, onPartBack, speakingTheme, initialQuestionIndex, skipTeacherVideo, onTeacherIntroPlayed, currentQuestionIndex, }: SpeakingQuestionRendererProps): import("react").JSX.Element;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{getTeacherVideoUrl as n,shouldWaitForTeacherVideo as t}from"../SpeakingVideoIntro";import{SpeakingCompareImagesRenderer as o}from"./SpeakingCompareImagesRenderer";import{SpeakingStoryImagesRenderer as i}from"./SpeakingStoryImagesRenderer";import{SpeakingOddOneOutRenderer as a}from"./SpeakingOddOneOutRenderer";import{SpeakingQuestionListRenderer as s}from"./SpeakingQuestionListRenderer";import{SpeakingChooseObjectsRenderer as p}from"./SpeakingChooseObjectsRenderer";import{SpeakingChooseAnswerGroupRenderer as S}from"./SpeakingChooseAnswerGroupRenderer";import{SpeakingDragObjectsRenderer as _}from"./SpeakingDragObjectsRenderer";import{SpeakingListenAndSpeakAnswerRenderer as E}from"./SpeakingListenAndSpeakAnswerRenderer";import{SpeakingDescribeImageRenderer as m}from"./SpeakingDescribeImageRenderer";import{SpeakingInfoExchangeRenderer as d}from"./SpeakingInfoExchangeRenderer";import{SpeakingReadDisplayedContentRenderer as N}from"./SpeakingReadDisplayedContentRenderer";import{SpeakingSpontaneousQARenderer as c}from"./SpeakingSpontaneousQARenderer";import{SpeakingConversationRenderer as A}from"./SpeakingConversationRenderer";import{SpeakingGNInterviewRenderer as u}from"./SpeakingGNInterviewRenderer";import{SpeakingCueCardRenderer as I}from"./SpeakingCueCardRenderer";export function SpeakingQuestionRenderer({part:T,answers:R,onAnswerChange:g,isReviewMode:O=!1,onExit:k,onPartComplete:f,onPartBack:C,speakingTheme:P,initialQuestionIndex:D,skipTeacherVideo:h,onTeacherIntroPlayed:l,currentQuestionIndex:w}){const x=k??(()=>{"undefined"!=typeof window&&window.history.back()}),G=!h&&t(T.questionType),L={part:T,answers:R,onAnswerChange:g,isReviewMode:O,onExit:x,onPartComplete:f,onPartBack:C,speakingTheme:P,initialQuestionIndex:D,teacherVideoUrl:n(T.questionType),waitForTeacherVideo:G,onTeacherIntroPlayed:l,skipTeacherVideo:!!h,currentQuestionIndex:w};switch(T.questionType){case"LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE":return e(p,{...L});case"LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP":return e(S,{...L});case"LISTEN_AND_DRAG_OBJECTS_INTO_SCENE":return e(_,{...L});case"LISTEN_AND_SPEAK_ANSWER":return e(E,{...L});case"SPEAKING_DESCRIBE_IMAGE":return e(m,{...L});case"LISTEN_AND_SPEAK_COMPARE_IMAGES":return e(o,{...L});case"LISTEN_AND_SPEAK_WITH_STORY_IMAGES":return e(i,{...L});case"LISTEN_AND_SPEAK_IMAGE_GROUP":case"LISTEN_AND_SPEAK_ODD_ONE_OUT":return e(a,{...L});case"LISTEN_AND_SPEAK_QUESTION_LIST":return e(s,{...L});case"LISTEN_AND_SPEAK_INFO_EXCHANGE":return e(d,{...L});case"READ_DISPLAYED_CONTENT":return e(N,{...L});case"SPONTANEOUS_QA":return e(c,{...L});case"SPEAKING_CONVERSATION":return e(A,{...L});case"GN_SPEAKING_INTERVIEW":return e(u,{...L});case"SPEAKING_CUE_CARD":return e(I,{...L});default:return e("div",{className:"flex h-full items-center justify-center p-8",children:r("p",{className:"text-center text-sm text-red-500",children:["Unsupported speaking question type: ",T.questionType]})})}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpeakingReadDisplayedContentRenderer
|
|
3
|
+
*
|
|
4
|
+
* Renderer for READ_DISPLAYED_CONTENT (English Primary & Junior Speaking Test — Part 1).
|
|
5
|
+
*
|
|
6
|
+
* Data shape:
|
|
7
|
+
* - content.referenceText: the passage the student must read aloud
|
|
8
|
+
* - content.modelAudioUrl: optional model pronunciation audio
|
|
9
|
+
* - content.difficultyTag: optional tag (BASIC_PHONICS | DAILY_ROUTINE | DESCRIPTIVE_EMOTIONAL)
|
|
10
|
+
*
|
|
11
|
+
* Interaction:
|
|
12
|
+
* - Student sees the reference text prominently displayed
|
|
13
|
+
* - Optionally listens to model pronunciation
|
|
14
|
+
* - Records themselves reading the passage aloud
|
|
15
|
+
* - Answer = S3 audio key from recorder
|
|
16
|
+
* - Backend AI compares recorded audio to referenceText using Phonetic Distance
|
|
17
|
+
*/
|
|
18
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
19
|
+
interface SpeakingReadDisplayedContentRendererProps {
|
|
20
|
+
part: QuestionPart;
|
|
21
|
+
answers: Record<string, unknown>;
|
|
22
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
23
|
+
isReviewMode?: boolean;
|
|
24
|
+
onExit: () => void;
|
|
25
|
+
onPartComplete?: () => void;
|
|
26
|
+
onPartBack?: () => void;
|
|
27
|
+
speakingTheme?: string;
|
|
28
|
+
teacherVideoUrl?: string;
|
|
29
|
+
waitForTeacherVideo?: boolean;
|
|
30
|
+
onTeacherIntroPlayed?: () => void;
|
|
31
|
+
skipTeacherVideo?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function SpeakingReadDisplayedContentRenderer(props: SpeakingReadDisplayedContentRendererProps): import("react").JSX.Element;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{useSpeakingRendererSetup as i}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as n}from"../../../../../../components/themes";import a from"../SpeakingReadDisplayedContent.module.css";import{SpeakingAudioPlayer as r}from"../../../../../../components/shared/audio/SpeakingAudioPlayer";function s(e){switch(e){case"BASIC_PHONICS":return a.difficultyBasicPhonics;case"DAILY_ROUTINE":return a.difficultyDailyRoutine;case"DESCRIPTIVE_EMOTIONAL":return a.difficultyDescriptiveEmotional;default:return""}}function l(e){switch(e){case"BASIC_PHONICS":return"📖 Basic Phonics";case"DAILY_ROUTINE":return"🕐 Daily Routine & Time";case"DESCRIPTIVE_EMOTIONAL":return"🌄 Descriptive & Emotional";default:return""}}function o({total:i,currentIndex:n,answeredIds:r,questionIds:s,isSpaceship:l}){return e("div",{className:a.taskIndicator,style:l?{color:"#78350f",textShadow:"none",fontWeight:800}:void 0,children:[e("span",{children:["Question ",n+1,"/ ",i]}),t("div",{className:"flex gap-2",children:Array.from({length:i},(e,i)=>{const l=i===n,o=r.has(s[i]);return t("div",{className:`${a.taskDot} ${l?a.taskDotActive:""} ${o&&!l?a.taskDotCompleted:""}`},i)})})]})}export function SpeakingReadDisplayedContentRenderer(d){const c=i({...d,maxDurationMs:3e4}),u=n(d.speakingTheme),h="SPACESHIP"===d.speakingTheme||"SUMMER_SKY"===d.speakingTheme,m=c.currentQuestion?.content;if(!c.currentQuestion||!m)return null;const p=m.referenceText??"",f=m.modelAudioUrl??"",x=m.difficultyTag,g=new Set(d.part.questions.map(e=>e.id).filter(e=>!!d.answers[e])),w=d.part.questions.map(e=>e.id),N=t(o,{total:d.part.questions.length,currentIndex:c.nav.currentIndex,answeredIds:g,questionIds:w,isSpaceship:h}),v=f?e("div",{className:a.modelAudioSection,children:[t("span",{className:a.modelAudioLabel,children:"🔊 Model pronunciation:"}),t(r,{audioUrl:f})]}):null;return t(u,{onExit:d.onExit,partName:d.part.name||`Part ${d.part.partNo}`,instruction:d.part.instructions||"READ THE PASSAGE ALOUD",audioUrl:h&&f||"",questionNumber:c.nav.questionNumberInPart,onPrev:c.handlePrev,onNext:c.handleNext,showPrev:c.nav.showPrev,showNext:c.nav.showNext,canGoPrev:c.nav.canGoPrev,canGoNext:c.nav.canGoNext,onAudioPlayingChange:c.onAudioPlayingChange,recorder:d.isReviewMode?null:c.recorder,showExistingBadge:c.showExistingBadge,headerRight:N,audioBarRight:h?null:v,teacherVideoUrl:d.teacherVideoUrl,waitForTeacherVideo:d.waitForTeacherVideo,onTeacherVideoEnded:d.onTeacherIntroPlayed,skipTeacherVideo:d.skipTeacherVideo,children:h?t("div",{className:"w-full h-full flex flex-col items-center relative",children:t("div",{className:"w-full flex-1",style:{minHeight:0},children:t("div",{className:"mx-auto",style:{width:"900px",maxWidth:"95%",height:"calc(100% - 100px)"},children:t("div",{className:"w-full h-full flex flex-col items-center justify-center",style:{paddingTop:"10px"},children:e("div",{className:"relative flex flex-col justify-center items-center",style:{width:"100%",paddingLeft:"60px",paddingRight:"60px",paddingTop:"20px",paddingBottom:"20px"},children:[c.showExistingBadge&&e("div",{className:"absolute right-12 top-12 z-10 rounded-full bg-green-500 px-4 py-1.5 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),t("div",{className:"max-w-full",children:t("div",{className:"text-center max-w-full",style:{fontSize:"40px",fontFamily:'"SVN-Volte Rounded", sans-serif',fontWeight:600,lineHeight:"52px",letterSpacing:"-0.01em",color:"#2C38B6"},children:t("p",{className:"m-0 break-words whitespace-pre-wrap",children:`"${p}"`})})})]})})})})}):e("div",{className:"flex flex-col items-center justify-center h-full w-full gap-4 px-4",children:[x&&t("div",{className:`${a.difficultyBadge} ${s(x)}`,children:l(x)}),e("div",{className:a.referenceTextCard,children:[c.showExistingBadge&&e("div",{className:"absolute right-4 top-4 z-10 rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),t("p",{className:a.referenceText,children:`"${p}"`})]})]})})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingSpontaneousQARendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
waitForTeacherVideo?: boolean;
|
|
13
|
+
onTeacherIntroPlayed?: () => void;
|
|
14
|
+
skipTeacherVideo?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function SpeakingSpontaneousQARenderer(props: SpeakingSpontaneousQARendererProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsxs as e,jsx as t}from"react/jsx-runtime";import{useState as r,useRef as n,useEffect as i,useCallback as a}from"react";import{usePresignedFileUrl as o}from"../../../../../../shared/lib/hooks";import{useSpeakingRendererSetup as s}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as d}from"../../../../../../components/themes";import l from"../SpeakingReadDisplayedContent.module.css";import c from"../../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner";import u from"next/image";import h from"../../../../../../components/themes/summer-sky/assets/images/mic_ver2_icon.png";import m from"../../../../../../components/themes/summer-sky/assets/images/stop_audio_ver2_icon.png";import p from"../../../../../../components/themes/summer-sky/assets/images/audio_ver2_icon.png";import g from"../../../../../../components/themes/summer-sky/assets/images/pause_audio_ver2_icon.png";import f from"../../../../../../components/themes/summer-sky/assets/images/lock_mic_ver2_icon.png";function x({total:r,currentIndex:n,answeredIds:i,questionIds:a}){return e("div",{className:l.taskIndicator,style:{color:"#78350f",textShadow:"none",fontWeight:800},children:[e("span",{children:["Question ",n+1,"/ ",r]}),t("div",{className:"flex gap-2",children:Array.from({length:r},(e,r)=>{const o=r===n,s=i.has(a[r]);return t("div",{className:`${l.taskDot} ${o?l.taskDotActive:""} ${s&&!o?l.taskDotCompleted:""}`},r)})})]})}function v({audioUrl:o,autoPlay:s,onEnded:d,disabled:l}){const c=n(null),[u,h]=r(!1),[m,p]=r(0),[g,f]=r(0);i(()=>{const e=c.current;if(!e)return;const t=()=>p(e.currentTime),r=()=>f(e.duration),n=()=>{h(!1),d?.()},i=()=>h(!0),a=()=>h(!1);return e.addEventListener("timeupdate",t),e.addEventListener("loadedmetadata",r),e.addEventListener("ended",n),e.addEventListener("play",i),e.addEventListener("pause",a),()=>{e.removeEventListener("timeupdate",t),e.removeEventListener("loadedmetadata",r),e.removeEventListener("ended",n),e.removeEventListener("play",i),e.removeEventListener("pause",a)}},[o,d]),i(()=>{const e=c.current;e&&(e.pause(),e.currentTime=0,h(!1),p(0),f(0))},[o]),i(()=>{const e=c.current;if(!e||!s)return;let t=!1;const r=()=>{t||e.play().catch(()=>{})};return e.readyState>=3?r():e.addEventListener("canplay",r,{once:!0}),()=>{t=!0,e.removeEventListener("canplay",r)}},[o,s]);const x=a(()=>{const e=c.current;e&&(u?e.pause():((e.ended||e.currentTime>=e.duration)&&(e.currentTime=0,p(0)),e.play().catch(console.error)))},[u]),v=a(e=>{const t=c.current;if(!t)return;const r=parseFloat(e.target.value);t.currentTime=r,p(r)},[]),N=e=>`${Math.floor(e/60)}:${Math.floor(e%60).toString().padStart(2,"0")}`,w=g>0?m/g*100:0;return e("div",{className:"w-full flex items-center gap-4 rounded-full px-3 py-3",style:{maxWidth:"650px",background:"linear-gradient(135deg, #B4F0FF 0%, #7EEAFF 50%, #5FE0F5 100%)",border:"5px solid white",boxShadow:"0 4px 15px rgba(0,0,0,0.08)",borderRadius:"20px"},children:[t("audio",{ref:c,src:o,preload:"metadata"}),t("button",{onClick:l?void 0:x,disabled:l,className:"flex-shrink-0 flex items-center justify-center rounded-xl transition-all "+(l?"opacity-50 cursor-not-allowed grayscale":"hover:scale-105 active:scale-95"),style:{width:"40px",height:"40px",background:u?"linear-gradient(135deg, #FF5B7F, #FF3D6E)":"linear-gradient(135deg, #1565C0, #1E88E5)",boxShadow:u?"0 3px 8px rgba(255,61,110,0.4)":"0 3px 8px rgba(21,101,192,0.4)"},children:u?e("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"white",children:[t("rect",{x:"5",y:"3",width:"5",height:"18",rx:"2"}),t("rect",{x:"14",y:"3",width:"5",height:"18",rx:"2"})]}):t("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"white",children:t("path",{d:"M8 5v14l11-7z"})})}),e("span",{className:"flex-shrink-0 font-bold tabular-nums",style:{color:"#1A3D6D",fontSize:"16px",fontFamily:'"Quicksand", sans-serif',minWidth:"85px"},children:[N(m)," / ",N(g)]}),e("div",{className:"flex-1 relative h-2 rounded-full overflow-hidden",style:{background:"rgba(255,255,255,0.6)"},children:[t("div",{className:"absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${w}%`,background:"linear-gradient(90deg, #1565C0, #42A5F5)"}}),t("input",{type:"range",min:0,max:g||0,step:.1,value:m,onChange:l?void 0:v,disabled:l,className:"absolute inset-0 w-full h-full opacity-0 "+(l?"cursor-not-allowed":"cursor-pointer")}),t("div",{className:"absolute top-1/2 -translate-y-1/2 w-4 h-4 rounded-full border-2 border-white shadow-md pointer-events-none",style:{left:`calc(${w}% - 8px)`,background:"#1565C0"}})]})]})}function N({total:r,currentIndex:n,answeredIds:i,questionIds:a}){return e("div",{className:"flex items-center gap-2 font-bold text-[#78350f] font-['Quicksand'] z-10",children:[e("span",{className:"text-sm",children:["Question ",n+1,"/ ",r]}),t("div",{className:"flex gap-1.5",children:Array.from({length:r},(e,r)=>{const o=r===n,s=i.has(a[r]);return t("div",{className:"w-2.5 h-2.5 rounded-full "+(o?"bg-[#FF9900] scale-125 shadow-sm":s?"bg-[#16CA3B]":"bg-white/60")},r)})})]})}export function SpeakingSpontaneousQARenderer(l){const w=s({...l,maxDurationMs:6e4}),y=d(l.speakingTheme),b=w.currentQuestion?.content,{previewUrl:E}=o(b?.audioUrl??""),k=0===w.nav.currentIndex,I=w.isQuestionLocked,L=a(()=>I?"ALLOW_RECORDING":k&&!l.skipTeacherVideo&&l.waitForTeacherVideo?"WAITING_TEACHER":"DELAYING",[k,l.skipTeacherVideo,l.waitForTeacherVideo,I]),[A,T]=r(L),F=I||"ALLOW_RECORDING"===A,S=n(null),[C,_]=r(!1),[,P]=r(0),[,R]=r(0);i(()=>{const e=S.current;if(!e)return;const t=()=>{_(!0),w.onAudioPlayingChange?.(!0)},r=()=>{_(!1),w.onAudioPlayingChange?.(!1)},n=()=>{_(!1),P(0),w.onAudioPlayingChange?.(!1)},i=()=>{P(e.currentTime),e.duration&&isFinite(e.duration)&&R(e.duration)},a=()=>{e.duration&&isFinite(e.duration)&&R(e.duration)};return e.addEventListener("play",t),e.addEventListener("pause",r),e.addEventListener("ended",n),e.addEventListener("timeupdate",i),e.addEventListener("loadedmetadata",a),e.addEventListener("durationchange",a),()=>{e.removeEventListener("play",t),e.removeEventListener("pause",r),e.removeEventListener("ended",n),e.removeEventListener("timeupdate",i),e.removeEventListener("loadedmetadata",a),e.removeEventListener("durationchange",a),w.onAudioPlayingChange?.(!1)}},[w.recorder?.previewUrl]),i(()=>{T(L())},[L,w.nav.currentIndex]),i(()=>{if("DELAYING"===A&&!I){const e=setTimeout(()=>{T("PLAYING_QUESTION")},3e3);return()=>clearTimeout(e)}},[A,I]);const G=a(()=>{T("DELAYING"),l.onTeacherIntroPlayed?.()},[l.onTeacherIntroPlayed]),D=a(()=>{"PLAYING_QUESTION"!==A||I||T("ALLOW_RECORDING")},[A,I]);if(!w.currentQuestion||!b)return null;const O=new Set(l.part.questions.map(e=>e.id).filter(e=>!!l.answers[e])),W=l.part.questions.map(e=>e.id);if(l.speakingTheme&&"CAMBRIDGE_YLE"!==l.speakingTheme){const r=t(x,{total:l.part.questions.length,currentIndex:w.nav.currentIndex,answeredIds:O,questionIds:W});return t(y,{onExit:l.onExit,partName:l.part.name||`Part ${l.part.partNo}`,instruction:l.part.instructions||"LISTEN AND ANSWER",audioUrl:b.audioUrl??"",questionNumber:w.nav.questionNumberInPart,onPrev:w.handlePrev,onNext:w.handleNext,showPrev:w.nav.showPrev,showNext:w.nav.showNext,canGoPrev:w.nav.canGoPrev,canGoNext:w.nav.canGoNext,onAudioPlayingChange:w.onAudioPlayingChange,recorder:l.isReviewMode?null:w.recorder,showExistingBadge:w.showExistingBadge,headerRight:r,hideAudioBar:!0,teacherVideoUrl:l.teacherVideoUrl,waitForTeacherVideo:!(!k||l.skipTeacherVideo)&&l.waitForTeacherVideo,onTeacherVideoEnded:G,skipTeacherVideo:!k||l.skipTeacherVideo,showTeacherImage:F,isMicLockedOverride:!I&&"ALLOW_RECORDING"!==A,micLockedText:I?void 0:"PLAYING_QUESTION"===A?"Nghe câu hỏi trước khi trả lời":"DELAYING"===A?"Chuẩn bị phát audio câu hỏi...":"Hãy chờ giáo viên hoàn thành hướng dẫn",children:e("div",{className:"flex flex-col items-center justify-center h-full w-full gap-8 px-8 pb-40",children:[w.showExistingBadge&&e("div",{className:"rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),e("h2",{className:"text-center",style:{color:"#2C38B6",fontSize:"30px",fontFamily:'"SVN-Volte Rounded", "Quicksand", sans-serif',fontWeight:600,lineHeight:"45.74px",letterSpacing:"-0.01em",maxWidth:"700px",WebkitTextStroke:"1px #ffffff",paintOrder:"stroke fill"},children:["Nghe câu hỏi, suy nghĩ và trả lời tự do",t("br",{}),"bằng tiếng Anh."]}),E&&t(v,{audioUrl:E,autoPlay:!I&&"PLAYING_QUESTION"===A,onEnded:D,disabled:"WAITING_TEACHER"===A})]})})}const Q=!I&&"ALLOW_RECORDING"!==A;return e("div",{className:"flex flex-col w-full h-full relative",style:{background:"linear-gradient(180deg, #E6F3FF 0%, #FFFFFF 100%)"},children:[e("div",{className:"relative w-full flex justify-between items-start pt-6 px-6 z-20 shrink-0",children:[t("div",{className:"flex-1"}),t("div",{className:"flex-none drop-shadow-md",children:t(c,{partNumber:l.part.partNo,questionCount:l.part.questions.length,partName:l.part.name||`Part ${l.part.partNo}`})}),t("div",{className:"flex-1 flex justify-end mt-2 z-20",children:t(N,{total:l.part.questions.length,currentIndex:w.nav.currentIndex,answeredIds:O,questionIds:W})})]}),e("div",{className:"flex-1 w-full flex flex-col items-center min-h-0 px-4 pb-6 pt-12",children:[e("div",{className:"flex-1 w-full flex flex-col justify-center items-center gap-12 min-h-0 relative mb-6",children:[w.showExistingBadge&&e("div",{className:"absolute right-4 top-0 z-10 rounded-full bg-green-500 px-4 py-1.5 flex items-center gap-2 text-sm font-bold text-white shadow-lg border-2 border-white transform -translate-y-1/2",children:[t("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),e("h2",{className:"text-center z-10",style:{color:"#2C38B6",fontSize:"30px",fontFamily:'"SVN-Volte Rounded", "Quicksand", sans-serif',fontWeight:600,lineHeight:"45.74px",letterSpacing:"-0.01em",maxWidth:"700px",WebkitTextStroke:"1px #ffffff",paintOrder:"stroke fill"},children:["Nghe câu hỏi, suy nghĩ và trả lời tự do",t("br",{}),"bằng tiếng Anh."]}),E&&t(v,{audioUrl:E,autoPlay:!I&&"PLAYING_QUESTION"===A,onEnded:D,disabled:"WAITING_TEACHER"===A})]}),w.recorder&&!l.isReviewMode&&e("div",{className:"shrink-0 flex flex-col items-center z-50 gap-3",children:[t("div",{className:"bg-white rounded-[100px] shadow-[0_4px_10px_rgba(0,0,0,0.1)] whitespace-nowrap flex items-center justify-center",style:{padding:"10px 30px",color:Q?"#888":"recording"===w.recorder.status?"#FF4D4D":["recorded","uploading","uploaded"].includes(w.recorder.status)?"#16CA3B":"#343C55",fontSize:"18px",fontFamily:'"Quicksand", sans-serif',fontWeight:700,lineHeight:"100%",letterSpacing:"-0.01em"},children:Q?I?"Ấn biểu tượng Micro để trả lời":"PLAYING_QUESTION"===A?"Nghe câu hỏi trước khi trả lời":"DELAYING"===A?"Chuẩn bị phát audio câu hỏi...":"Hãy chờ giáo viên hoàn thành hướng dẫn":"recording"===w.recorder.status?"Ấn lần nữa để lưu kết quả":["recorded","uploading","uploaded"].includes(w.recorder.status)?"Câu trả lời đã được ghi nhận !":"Ấn biểu tượng Micro để trả lời"}),t("div",{className:"relative flex flex-col items-center",children:"recording"===w.recorder.status?e("div",{className:"relative flex flex-col justify-center items-center cursor-pointer drop-shadow-[0_0_15px_rgba(255,100,100,0.8)] transition-transform hover:scale-[1.05] active:scale-[0.95]",onClick:w.recorder.stopRecording,children:[t(u,{src:m,alt:"stop audio",className:"animate-pulse",style:{width:"120px",height:"120px"}}),e("span",{className:"absolute text-white text-[16px] font-bold font-['Quicksand']",style:{bottom:"30px"},children:[Math.floor(w.recorder.remainingSeconds/60),":",(w.recorder.remainingSeconds%60).toString().padStart(2,"0")]})]}):t(u,Q?{src:f,alt:"micro locked",className:"opacity-80 cursor-not-allowed",style:{width:"120px",height:"120px"}}:{src:["recorded","uploading","uploaded"].includes(w.recorder.status)?C?g:p:h,alt:"micro",className:"transition-transform cursor-pointer hover:scale-[1.05] active:scale-[0.95]",style:{width:"120px",height:"120px"},onClick:()=>{if(!Q)if("recording"===w.recorder?.status)w.recorder.stopRecording();else if("idle"===w.recorder?.status||"error"===w.recorder?.status)w.recorder.startRecording();else if("uploaded"===w.recorder?.status||"recorded"===w.recorder?.status){const e=S.current;e&&(C?e.pause():e.play().catch(console.error))}}})}),w.recorder.previewUrl&&t("audio",{ref:S,src:w.recorder.previewUrl})]})]})]})}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
+
interface SpeakingStoryImagesRendererProps {
|
|
3
|
+
part: QuestionPart;
|
|
4
|
+
answers: Record<string, unknown>;
|
|
5
|
+
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
onExit: () => void;
|
|
8
|
+
onPartComplete?: () => void;
|
|
9
|
+
onPartBack?: () => void;
|
|
10
|
+
speakingTheme?: string;
|
|
11
|
+
teacherVideoUrl?: string;
|
|
12
|
+
waitForTeacherVideo?: boolean;
|
|
13
|
+
onTeacherIntroPlayed?: () => void;
|
|
14
|
+
skipTeacherVideo?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function SpeakingStoryImagesRenderer(props: SpeakingStoryImagesRendererProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useCallback as a,useEffect as n,useState as o}from"react";import{ResolvedImage as t}from"../../../../../../components/common/ResolvedImage";import{useSpeakingRendererSetup as i}from"../../../hooks/useSpeakingRendererSetup";import{getSpeakingLayout as s}from"../../../../../../components/themes";import l from"../SpeakingStoryImages.module.css";export function SpeakingStoryImagesRenderer(c){const m=i({...c,maxDurationMs:3e4}),h=s(c.speakingTheme),u=m.currentQuestion?.content,[g,p]=o(null),x=a(()=>p(null),[]);if(n(()=>{if(!g)return;const e=e=>{"Escape"===e.key&&x()};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[g,x]),!m.currentQuestion||!u)return null;const N=u.audioUrl??"",v=u.images??[];return e(h,{onExit:c.onExit,partName:c.part.name||`Part ${c.part.partNo}`,instruction:c.part.instructions||"LOOK AT THE PICTURES. LISTEN AND TELL THE STORY.",audioUrl:N,questionNumber:m.nav.questionNumberInPart,onPrev:m.handlePrev,onNext:m.handleNext,showPrev:m.nav.showPrev,showNext:m.nav.showNext,canGoPrev:m.nav.canGoPrev,canGoNext:m.nav.canGoNext,onAudioPlayingChange:m.onAudioPlayingChange,recorder:c.isReviewMode?null:m.recorder,showExistingBadge:m.showExistingBadge,teacherVideoUrl:c.teacherVideoUrl,waitForTeacherVideo:c.waitForTeacherVideo,onTeacherVideoEnded:c.onTeacherIntroPlayed,skipTeacherVideo:c.skipTeacherVideo,children:r("div",{className:l.part3Container,children:[m.showExistingBadge&&r("div",{className:"absolute right-4 top-4 z-10 rounded-full bg-green-500 px-4 py-1 flex items-center gap-2 text-sm font-bold text-white shadow-md border-2 border-white",children:[e("span",{className:"h-2.5 w-2.5 rounded-full bg-white animate-pulse"}),"Đã ghi âm"]}),0===v.length?e("div",{className:l.emptyState,children:e("span",{children:"No images available"})}):e("div",{className:l.storyGrid,"data-count":v.length,children:v.map((r,a)=>e(d,{src:r,index:a,onEnlarge:()=>p(r)},`${r}-${a}`))}),g&&r("div",{className:l.lightboxBackdrop,onClick:x,role:"dialog","aria-modal":"true",children:[e("button",{type:"button",className:l.lightboxClose,onClick:x,"aria-label":"Đóng",children:"✕"}),e(t,{src:g,alt:"Story image enlarged",className:l.lightboxImage,draggable:!1})]})]})})}function d({src:a,index:n,onEnlarge:o}){return r("div",{className:l.storyFrame,children:[e("span",{className:l.storyNumber,"aria-hidden":"true",children:n+1}),e("button",{type:"button",className:l.imageBorder,onClick:o,"aria-label":`Phóng to ảnh số ${n+1}`,children:e(t,{src:a,alt:`Story image ${n+1}`,draggable:!1})})]})}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SpeakingCompareImagesRenderer } from './SpeakingCompareImagesRenderer';
|
|
2
|
+
export { SpeakingStoryImagesRenderer } from './SpeakingStoryImagesRenderer';
|
|
3
|
+
export { SpeakingOddOneOutRenderer } from './SpeakingOddOneOutRenderer';
|
|
4
|
+
export { SpeakingQuestionListRenderer } from './SpeakingQuestionListRenderer';
|
|
5
|
+
export { SpeakingChooseObjectsRenderer } from './SpeakingChooseObjectsRenderer';
|
|
6
|
+
export { SpeakingChooseAnswerGroupRenderer } from './SpeakingChooseAnswerGroupRenderer';
|
|
7
|
+
export { SpeakingDragObjectsRenderer } from './SpeakingDragObjectsRenderer';
|
|
8
|
+
export { SpeakingListenAndSpeakAnswerRenderer } from './SpeakingListenAndSpeakAnswerRenderer';
|
|
9
|
+
export { SpeakingDescribeImageRenderer } from './SpeakingDescribeImageRenderer';
|
|
10
|
+
export { SpeakingInfoExchangeRenderer } from './SpeakingInfoExchangeRenderer';
|
|
11
|
+
export { SpeakingReadDisplayedContentRenderer } from './SpeakingReadDisplayedContentRenderer';
|
|
12
|
+
export { SpeakingSpontaneousQARenderer } from './SpeakingSpontaneousQARenderer';
|
|
13
|
+
export { SpeakingQuestionRenderer } from './SpeakingQuestionRenderer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{SpeakingCompareImagesRenderer}from"./SpeakingCompareImagesRenderer";export{SpeakingStoryImagesRenderer}from"./SpeakingStoryImagesRenderer";export{SpeakingOddOneOutRenderer}from"./SpeakingOddOneOutRenderer";export{SpeakingQuestionListRenderer}from"./SpeakingQuestionListRenderer";export{SpeakingChooseObjectsRenderer}from"./SpeakingChooseObjectsRenderer";export{SpeakingChooseAnswerGroupRenderer}from"./SpeakingChooseAnswerGroupRenderer";export{SpeakingDragObjectsRenderer}from"./SpeakingDragObjectsRenderer";export{SpeakingListenAndSpeakAnswerRenderer}from"./SpeakingListenAndSpeakAnswerRenderer";export{SpeakingDescribeImageRenderer}from"./SpeakingDescribeImageRenderer";export{SpeakingInfoExchangeRenderer}from"./SpeakingInfoExchangeRenderer";export{SpeakingReadDisplayedContentRenderer}from"./SpeakingReadDisplayedContentRenderer";export{SpeakingSpontaneousQARenderer}from"./SpeakingSpontaneousQARenderer";export{SpeakingQuestionRenderer}from"./SpeakingQuestionRenderer";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useExamTimer}from"./useExamTimer";export{useAutoSave}from"./useAutoSave";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ExamTakingAnswerValue, ExamTakingSdkApi } from '../../../../api/exam-taking';
|
|
2
|
+
export interface UseAnswerAutosaveOptions {
|
|
3
|
+
/** API adapter; autosave is skipped when `saveAnswers` is undefined. */
|
|
4
|
+
api: ExamTakingSdkApi;
|
|
5
|
+
/** Active attempt id, or null while loading. */
|
|
6
|
+
attemptId: string | null;
|
|
7
|
+
/** Whether autosave is active (attempt loaded & not submitting). */
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
/** Debounce window before a pending change is flushed to the server. */
|
|
10
|
+
debounceMs?: number;
|
|
11
|
+
/** Notified when a save fails so the host can surface a toast. */
|
|
12
|
+
onSaveError?: (error: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface UseAnswerAutosaveResult {
|
|
15
|
+
/** Queue a single answer change; debounced + flushed on tab hide/unload. */
|
|
16
|
+
queueAnswer: (questionId: string, answer: ExamTakingAnswerValue) => void;
|
|
17
|
+
/** Force an immediate flush of all pending answers (e.g. before submit). */
|
|
18
|
+
flushNow: () => Promise<void>;
|
|
19
|
+
/** `true` when there are unsaved answers buffered locally. */
|
|
20
|
+
hasPendingRef: React.MutableRefObject<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Buffers answer changes and persists them in debounced batches. Ports the host
|
|
24
|
+
* app's resilience: a `keepalive` flush on tab hide / page unload and a retry on
|
|
25
|
+
* the `online` event so answers survive refreshes and flaky networks.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useAnswerAutosave({ api, attemptId, enabled, debounceMs, onSaveError, }: UseAnswerAutosaveOptions): UseAnswerAutosaveResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{useCallback as e,useEffect as r,useRef as n}from"react";export function useAnswerAutosave({api:t,attemptId:i,enabled:s,debounceMs:u=800,onSaveError:c}){const a=n(new Map),o=n(!1),d=n(null),w=n(c);w.current=c;const v=e(async e=>{if(!i||!t.saveAnswers||0===a.current.size)return;const r=Array.from(a.current.entries()).map(([e,r])=>({questionId:e,answer:r}));a.current.clear(),o.current=!1;try{await t.saveAnswers({attemptId:i,answers:r,keepalive:e})}catch(e){r.forEach(({questionId:e,answer:r})=>a.current.set(e,r)),o.current=!0,w.current?.(e)}},[t,i]),m=e(()=>v(!1),[v]),l=e((e,r)=>{s&&(a.current.set(e,r),o.current=!0,d.current&&clearTimeout(d.current),d.current=setTimeout(()=>{v(!1)},u))},[u,v,s]);return r(()=>{if(!s)return;const e=()=>{v(!0)},r=()=>{"hidden"===document.visibilityState&&e()},n=()=>{o.current&&v(!1)};return document.addEventListener("visibilitychange",r),window.addEventListener("pagehide",e),window.addEventListener("online",n),()=>{document.removeEventListener("visibilitychange",r),window.removeEventListener("pagehide",e),window.removeEventListener("online",n),d.current&&clearTimeout(d.current)}},[v,s]),{queueAnswer:l,flushNow:m,hasPendingRef:o}}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ExamTakingSdkApi } from '../../../../api/exam-taking';
|
|
2
|
+
export interface UseAttemptSubmitOptions {
|
|
3
|
+
/** API adapter. */
|
|
4
|
+
api: ExamTakingSdkApi;
|
|
5
|
+
/** Flush pending answers before submitting (e.g. from `useAnswerAutosave`). */
|
|
6
|
+
flushAnswers?: () => Promise<void>;
|
|
7
|
+
/** Notified on success with the submit result + pending grading answer ids. */
|
|
8
|
+
onSubmitted?: (result: SubmittedSummary) => void;
|
|
9
|
+
/** Notified when submission fails. */
|
|
10
|
+
onError?: (error: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface SubmittedSummary {
|
|
13
|
+
attemptId: string;
|
|
14
|
+
pendingAnswerIds: string[];
|
|
15
|
+
manualReviewAnswerIds: string[];
|
|
16
|
+
autoGradedCount: number;
|
|
17
|
+
}
|
|
18
|
+
export interface UseAttemptSubmitResult {
|
|
19
|
+
/** Submit the attempt (flushes answers first); guards against double submit. */
|
|
20
|
+
submit: (attemptId: string) => Promise<void>;
|
|
21
|
+
/** `true` while a submit request is in flight. */
|
|
22
|
+
isSubmitting: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Submits an attempt with the host app's guarantees: flush any buffered answers
|
|
26
|
+
* first, prevent concurrent submits, and surface the pending/auto-graded answer
|
|
27
|
+
* ids so the caller can route to the review screen with the right context.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useAttemptSubmit({ api, flushAnswers, onSubmitted, onError, }: UseAttemptSubmitOptions): UseAttemptSubmitResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{useCallback as t,useState as e}from"react";export function useAttemptSubmit({api:n,flushAnswers:a,onSubmitted:i,onError:s}){const[r,u]=e(!1);return{submit:t(async t=>{if(!r){u(!0);try{a&&await a();const s="object"==typeof(e=await n.submitAttempt(t))&&null!==e&&"data"in e?e.data:e??{};i?.({attemptId:t,pendingAnswerIds:s.pendingAnswerIds??[],manualReviewAnswerIds:s.manualReviewAnswerIds??[],autoGradedCount:s.autoGradedCount??0})}catch(t){s?.(t),u(!1)}var e}},[n,a,r,s,i]),isSubmitting:r}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ExamTakingSdkApi } from '../../../../api/exam-taking';
|
|
2
|
+
import type { QuestionPart } from '../utils/question-transformers';
|
|
3
|
+
interface UseAutoSaveOptions {
|
|
4
|
+
/** Injected API adapter (replaces the host app's `attemptsApi`). */
|
|
5
|
+
api: ExamTakingSdkApi;
|
|
6
|
+
attemptId: string;
|
|
7
|
+
parts: QuestionPart[];
|
|
8
|
+
intervalMs?: number;
|
|
9
|
+
onSaveStart?: () => void;
|
|
10
|
+
onSaveSuccess?: () => void;
|
|
11
|
+
onSaveError?: (error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
interface UseAutoSaveReturn {
|
|
14
|
+
isSaving: boolean;
|
|
15
|
+
lastSavedAt: Date | null;
|
|
16
|
+
lastError: Error | null;
|
|
17
|
+
saveNow: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function useAutoSave(options: UseAutoSaveOptions): UseAutoSaveReturn;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState as t,useEffect as r,useRef as e,useCallback as a}from"react";import{useExamTakingStore as n}from"../../../../shared/lib/stores/examTakingStore";import{transformAnswersForSubmission as s}from"../utils/answer-transformers";export function useAutoSave(i){const o=n(),[l,u]=t(!1),[c,S]=t(null),v=e(null),m=i.intervalMs??3e4,p=a(async()=>{if(l||o.isSubmitting||!o.isDirty)return;if(0!==Object.keys(o.answers).length){u(!0),i.onSaveStart?.();try{const t=s(o.answers,i.parts);if(0===t.length)return void o.markSaved();await(i.api.saveAnswers?.({attemptId:i.attemptId,answers:t})),o.markSaved(),S(null),i.onSaveSuccess?.()}catch(t){const r=t;S(r),i.onSaveError?.(r)}finally{u(!1)}}},[i.api,i.attemptId,i.onSaveStart,i.onSaveSuccess,i.onSaveError,l,o.isSubmitting,o.isDirty,o.answers]);return r(()=>(v.current&&clearInterval(v.current),v.current=setInterval(async()=>{!o.isDirty||o.isSubmitting||l||await p()},m),()=>{v.current&&(clearInterval(v.current),v.current=null)}),[m,o.isDirty,o.isSubmitting,l,p]),{isSaving:l,lastSavedAt:o.lastSavedAt,lastError:c,saveNow:p}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UseExamCountdownOptions {
|
|
2
|
+
/** Total duration of the attempt in minutes. */
|
|
3
|
+
durationMinutes: number;
|
|
4
|
+
/** ISO timestamp marking when the attempt started. */
|
|
5
|
+
startedAt: string | null | undefined;
|
|
6
|
+
/** Whether the countdown is active (e.g. attempt loaded & not submitted). */
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
/** Fired exactly once when remaining time reaches zero. */
|
|
9
|
+
onExpire: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseExamCountdownResult {
|
|
12
|
+
/** Whole seconds remaining (clamped at 0). */
|
|
13
|
+
remainingSeconds: number;
|
|
14
|
+
/** `true` once the countdown has reached zero. */
|
|
15
|
+
isExpired: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Drives the exam timer. Computes remaining seconds from `startedAt + duration`
|
|
19
|
+
* so the value stays correct across tab refreshes, and invokes `onExpire` once
|
|
20
|
+
* when time runs out (mirrors the host app's auto-submit trigger).
|
|
21
|
+
*/
|
|
22
|
+
export declare function useExamCountdown({ durationMinutes, startedAt, enabled, onExpire, }: UseExamCountdownOptions): UseExamCountdownResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{useEffect as e,useRef as t,useState as r}from"react";export function useExamCountdown({durationMinutes:n,startedAt:o,enabled:c,onExpire:u}){const[a,i]=r(0),s=t(!1),d=t(u);return d.current=u,e(()=>{if(!c||!o)return;const e=new Date(o).getTime()+6e4*n;s.current=!1;const t=()=>{const t=Math.max(0,Math.floor((e-Date.now())/1e3));i(t),t<=0&&!s.current&&(s.current=!0,d.current())};t();const r=setInterval(t,1e3);return()=>clearInterval(r)},[n,c,o]),{remainingSeconds:a,isExpired:s.current}}
|