@tinyweb_dev/oe-exam-sdk 0.1.2 → 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.
Files changed (328) hide show
  1. package/README.md +58 -4
  2. package/dist/api/contest-rooms.d.ts +13 -0
  3. package/dist/api/contest-rooms.js +1 -0
  4. package/dist/api/contest-rooms.types.d.ts +47 -0
  5. package/dist/api/contest-rooms.types.js +1 -0
  6. package/dist/api/exam-entry.d.ts +14 -0
  7. package/dist/api/exam-entry.js +1 -0
  8. package/dist/api/exam-entry.types.d.ts +93 -0
  9. package/dist/api/exam-entry.types.js +1 -0
  10. package/dist/api/exam-questions.d.ts +1 -1
  11. package/dist/api/exam-questions.js +1 -1
  12. package/dist/api/exam-questions.types.d.ts +45 -1
  13. package/dist/api/index.d.ts +2 -0
  14. package/dist/api/index.js +1 -1
  15. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  16. package/dist/components/exams/CompletionDialog.js +1 -0
  17. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  18. package/dist/components/exams/CreateExamInfoForm.js +1 -1
  19. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  20. package/dist/components/exams/CreateExamPageContainer.js +1 -1
  21. package/dist/components/exams/ExamCreator.d.ts +9 -3
  22. package/dist/components/exams/ExamCreator.js +1 -1
  23. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  24. package/dist/components/exams/ExamPartTabs.js +1 -1
  25. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  26. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  27. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  28. package/dist/components/exams/ExamQuestionGrid.js +1 -1
  29. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  30. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -1
  31. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  32. package/dist/components/exams/ExamViewDialog.js +1 -0
  33. package/dist/components/exams/TemplateSelector.js +1 -1
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  38. package/dist/components/exams/create-exam.texts.js +1 -0
  39. package/dist/components/exams/create-exam.validation.js +1 -1
  40. package/dist/components/exams/entry/EntryRestartDialog.d.ts +12 -0
  41. package/dist/components/exams/entry/EntryRestartDialog.js +1 -0
  42. package/dist/components/exams/entry/StudentExamEntryPageContainer.d.ts +31 -0
  43. package/dist/components/exams/entry/StudentExamEntryPageContainer.js +1 -0
  44. package/dist/components/exams/entry/exam-entry.states.d.ts +22 -0
  45. package/dist/components/exams/entry/exam-entry.states.js +1 -0
  46. package/dist/components/exams/entry/exam-entry.utils.d.ts +16 -0
  47. package/dist/components/exams/entry/exam-entry.utils.js +1 -0
  48. package/dist/components/exams/entry/fullscreen-loading.d.ts +5 -0
  49. package/dist/components/exams/entry/fullscreen-loading.js +1 -0
  50. package/dist/components/exams/entry/index.d.ts +7 -0
  51. package/dist/components/exams/entry/index.js +1 -0
  52. package/dist/components/exams/exam-question-status.d.ts +9 -0
  53. package/dist/components/exams/exam-question-status.js +1 -0
  54. package/dist/components/exams/index.d.ts +19 -1
  55. package/dist/components/exams/index.js +1 -1
  56. package/dist/components/exams/rooms/StudentContestRoomsPageContainer.d.ts +18 -0
  57. package/dist/components/exams/rooms/StudentContestRoomsPageContainer.js +1 -0
  58. package/dist/components/exams/rooms/contest-rooms.utils.d.ts +32 -0
  59. package/dist/components/exams/rooms/contest-rooms.utils.js +1 -0
  60. package/dist/components/exams/rooms/index.d.ts +3 -0
  61. package/dist/components/exams/rooms/index.js +1 -0
  62. package/dist/components/exams/take/ExamTakingApiContext.d.ts +10 -0
  63. package/dist/components/exams/take/ExamTakingApiContext.js +1 -0
  64. package/dist/components/exams/take/ExamTakingPageContainer.d.ts +28 -4
  65. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -1
  66. package/dist/components/exams/take/components/QuestionRenderer.d.ts +25 -0
  67. package/dist/components/exams/take/components/QuestionRenderer.js +1 -0
  68. package/dist/components/exams/take/components/index.d.ts +10 -0
  69. package/dist/components/exams/take/components/index.js +1 -0
  70. package/dist/components/exams/take/components/question-renderers/ArticlesViewer.d.ts +7 -0
  71. package/dist/components/exams/take/components/question-renderers/ArticlesViewer.js +1 -0
  72. package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.d.ts +22 -0
  73. package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +1 -0
  74. package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.d.ts +14 -0
  75. package/dist/components/exams/take/components/question-renderers/MoversChooseAdjectiveRenderer.js +1 -0
  76. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.d.ts +2 -0
  77. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +1 -0
  78. package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.d.ts +3 -0
  79. package/dist/components/exams/take/components/question-renderers/MoversColoringRenderer.js +1 -0
  80. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +2 -0
  81. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +1 -0
  82. package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.d.ts +2 -0
  83. package/dist/components/exams/take/components/question-renderers/MoversGridFillRenderer.js +1 -0
  84. package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.d.ts +2 -0
  85. package/dist/components/exams/take/components/question-renderers/MoversLabelThePictureRenderer.js +1 -0
  86. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.d.ts +21 -0
  87. package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +1 -0
  88. package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.d.ts +19 -0
  89. package/dist/components/exams/take/components/question-renderers/MoversMatchByWritingAnswerRenderer.js +1 -0
  90. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +2 -0
  91. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +1 -0
  92. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +2 -0
  93. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +1 -0
  94. package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.d.ts +2 -0
  95. package/dist/components/exams/take/components/question-renderers/MoversReadingPassageRenderer.js +1 -0
  96. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.d.ts +20 -0
  97. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.js +1 -0
  98. package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.d.ts +32 -0
  99. package/dist/components/exams/take/components/question-renderers/MoversWordFillParagraphRenderer.js +1 -0
  100. package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.d.ts +18 -0
  101. package/dist/components/exams/take/components/question-renderers/MoversWordFillStructuredFormRenderer.js +1 -0
  102. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.d.ts +19 -0
  103. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingRenderer.js +1 -0
  104. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.d.ts +22 -0
  105. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.js +1 -0
  106. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +18 -0
  107. package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +1 -0
  108. package/dist/components/exams/take/components/question-renderers/index.d.ts +19 -0
  109. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  110. package/dist/components/exams/take/components/speaking/FindObjectInPicture.module.css +133 -0
  111. package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.d.ts +9 -0
  112. package/dist/components/exams/take/components/speaking/SpeakingAudioPlayer.js +1 -0
  113. package/dist/components/exams/take/components/speaking/SpeakingCompareImages.module.css +153 -0
  114. package/dist/components/exams/take/components/speaking/SpeakingContentFrame.d.ts +7 -0
  115. package/dist/components/exams/take/components/speaking/SpeakingContentFrame.js +1 -0
  116. package/dist/components/exams/take/components/speaking/SpeakingDragObjects.module.css +238 -0
  117. package/dist/components/exams/take/components/speaking/SpeakingExamLayout.d.ts +27 -0
  118. package/dist/components/exams/take/components/speaking/SpeakingExamLayout.js +1 -0
  119. package/dist/components/exams/take/components/speaking/SpeakingHeader.d.ts +6 -0
  120. package/dist/components/exams/take/components/speaking/SpeakingHeader.js +1 -0
  121. package/dist/components/exams/take/components/speaking/SpeakingInfoExchange.module.css +193 -0
  122. package/dist/components/exams/take/components/speaking/SpeakingInfoExchangeSummerSky.module.css +238 -0
  123. package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.d.ts +5 -0
  124. package/dist/components/exams/take/components/speaking/SpeakingInstructionBar.js +1 -0
  125. package/dist/components/exams/take/components/speaking/SpeakingMicButton.d.ts +14 -0
  126. package/dist/components/exams/take/components/speaking/SpeakingMicButton.js +1 -0
  127. package/dist/components/exams/take/components/speaking/SpeakingNavButton.d.ts +7 -0
  128. package/dist/components/exams/take/components/speaking/SpeakingNavButton.js +1 -0
  129. package/dist/components/exams/take/components/speaking/SpeakingPartBadge.d.ts +6 -0
  130. package/dist/components/exams/take/components/speaking/SpeakingPartBadge.js +1 -0
  131. package/dist/components/exams/take/components/speaking/SpeakingReadDisplayedContent.module.css +115 -0
  132. package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.d.ts +11 -0
  133. package/dist/components/exams/take/components/speaking/SpeakingRecordingPreview.js +1 -0
  134. package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.d.ts +1 -0
  135. package/dist/components/exams/take/components/speaking/SpeakingReportErrorButton.js +1 -0
  136. package/dist/components/exams/take/components/speaking/SpeakingStoryImages.module.css +231 -0
  137. package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.d.ts +13 -0
  138. package/dist/components/exams/take/components/speaking/SpeakingVideoIntro.js +1 -0
  139. package/dist/components/exams/take/components/speaking/index.d.ts +1 -0
  140. package/dist/components/exams/take/components/speaking/index.js +1 -0
  141. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.d.ts +16 -0
  142. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseAnswerGroupRenderer.js +1 -0
  143. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.d.ts +16 -0
  144. package/dist/components/exams/take/components/speaking/renderers/SpeakingChooseObjectsRenderer.js +1 -0
  145. package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.d.ts +17 -0
  146. package/dist/components/exams/take/components/speaking/renderers/SpeakingCompareImagesRenderer.js +1 -0
  147. package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.d.ts +16 -0
  148. package/dist/components/exams/take/components/speaking/renderers/SpeakingConversationRenderer.js +1 -0
  149. package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.d.ts +17 -0
  150. package/dist/components/exams/take/components/speaking/renderers/SpeakingCueCardRenderer.js +1 -0
  151. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.d.ts +18 -0
  152. package/dist/components/exams/take/components/speaking/renderers/SpeakingDescribeImageRenderer.js +1 -0
  153. package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.d.ts +16 -0
  154. package/dist/components/exams/take/components/speaking/renderers/SpeakingDragObjectsRenderer.js +1 -0
  155. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +16 -0
  156. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +1 -0
  157. package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.d.ts +16 -0
  158. package/dist/components/exams/take/components/speaking/renderers/SpeakingInfoExchangeRenderer.js +1 -0
  159. package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.d.ts +16 -0
  160. package/dist/components/exams/take/components/speaking/renderers/SpeakingListenAndSpeakAnswerRenderer.js +1 -0
  161. package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.d.ts +16 -0
  162. package/dist/components/exams/take/components/speaking/renderers/SpeakingOddOneOutRenderer.js +1 -0
  163. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.d.ts +26 -0
  164. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionListRenderer.js +1 -0
  165. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.d.ts +32 -0
  166. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +1 -0
  167. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.d.ts +34 -0
  168. package/dist/components/exams/take/components/speaking/renderers/SpeakingReadDisplayedContentRenderer.js +1 -0
  169. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.d.ts +17 -0
  170. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQARenderer.js +1 -0
  171. package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.d.ts +17 -0
  172. package/dist/components/exams/take/components/speaking/renderers/SpeakingStoryImagesRenderer.js +1 -0
  173. package/dist/components/exams/take/components/speaking/renderers/index.d.ts +13 -0
  174. package/dist/components/exams/take/components/speaking/renderers/index.js +1 -0
  175. package/dist/components/exams/take/hooks/index.d.ts +2 -0
  176. package/dist/components/exams/take/hooks/index.js +1 -0
  177. package/dist/components/exams/take/hooks/useAnswerAutosave.d.ts +27 -0
  178. package/dist/components/exams/take/hooks/useAnswerAutosave.js +1 -0
  179. package/dist/components/exams/take/hooks/useAttemptSubmit.d.ts +29 -0
  180. package/dist/components/exams/take/hooks/useAttemptSubmit.js +1 -0
  181. package/dist/components/exams/take/hooks/useAutoSave.d.ts +20 -0
  182. package/dist/components/exams/take/hooks/useAutoSave.js +1 -0
  183. package/dist/components/exams/take/hooks/useExamCountdown.d.ts +22 -0
  184. package/dist/components/exams/take/hooks/useExamCountdown.js +1 -0
  185. package/dist/components/exams/take/hooks/useExamTimer.d.ts +21 -0
  186. package/dist/components/exams/take/hooks/useExamTimer.js +1 -0
  187. package/dist/components/exams/take/hooks/useProctoringCamera.d.ts +19 -0
  188. package/dist/components/exams/take/hooks/useProctoringCamera.js +1 -0
  189. package/dist/components/exams/take/hooks/useProctoringSnapshot.d.ts +25 -0
  190. package/dist/components/exams/take/hooks/useProctoringSnapshot.js +1 -0
  191. package/dist/components/exams/take/hooks/useSpeakingConversationAudio.d.ts +19 -0
  192. package/dist/components/exams/take/hooks/useSpeakingConversationAudio.js +1 -0
  193. package/dist/components/exams/take/hooks/useSpeakingNavigation.d.ts +37 -0
  194. package/dist/components/exams/take/hooks/useSpeakingNavigation.js +1 -0
  195. package/dist/components/exams/take/hooks/useSpeakingRecorder.d.ts +45 -5
  196. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -1
  197. package/dist/components/exams/take/hooks/useSpeakingRendererSetup.d.ts +39 -0
  198. package/dist/components/exams/take/hooks/useSpeakingRendererSetup.js +1 -0
  199. package/dist/components/exams/take/hooks/useToeicAudio.d.ts +17 -0
  200. package/dist/components/exams/take/hooks/useToeicAudio.js +1 -0
  201. package/dist/components/exams/take/index.d.ts +9 -1
  202. package/dist/components/exams/take/index.js +1 -1
  203. package/dist/components/exams/take/types/speaking-cue-card.types.d.ts +10 -0
  204. package/dist/components/exams/take/types/speaking-cue-card.types.js +1 -0
  205. package/dist/components/exams/take/types/speaking.types.d.ts +153 -0
  206. package/dist/components/exams/take/types/speaking.types.js +1 -0
  207. package/dist/components/exams/take/types.d.ts +286 -1
  208. package/dist/components/exams/take/utils/answer-transformers.d.ts +26 -0
  209. package/dist/components/exams/take/utils/answer-transformers.js +1 -0
  210. package/dist/components/exams/take/utils/coloring-assignments.d.ts +5 -0
  211. package/dist/components/exams/take/utils/coloring-assignments.js +1 -0
  212. package/dist/components/exams/take/utils/question-transformers.d.ts +362 -0
  213. package/dist/components/exams/take/utils/question-transformers.js +1 -0
  214. package/dist/components/features/exam-entry/entry-state-cards.d.ts +19 -0
  215. package/dist/components/features/exam-entry/entry-state-cards.js +1 -0
  216. package/dist/components/features/exam-entry/index.d.ts +2 -0
  217. package/dist/components/features/exam-entry/index.js +1 -0
  218. package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.d.ts +12 -0
  219. package/dist/components/features/exam-entry/themed/CambridgeYleWaitingRoom.js +1 -0
  220. package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.d.ts +14 -0
  221. package/dist/components/features/exam-entry/themed/ThemedWaitingRoom.js +1 -0
  222. package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.d.ts +16 -0
  223. package/dist/components/features/exam-entry/themed/ToeicWaitingRoom.js +1 -0
  224. package/dist/components/features/exam-entry/themed/components/CloseButton.d.ts +9 -0
  225. package/dist/components/features/exam-entry/themed/components/CloseButton.js +1 -0
  226. package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.d.ts +13 -0
  227. package/dist/components/features/exam-entry/themed/components/ExamInfoPanel.js +1 -0
  228. package/dist/components/features/exam-entry/themed/components/ModeSelector.d.ts +7 -0
  229. package/dist/components/features/exam-entry/themed/components/ModeSelector.js +1 -0
  230. package/dist/components/features/exam-entry/themed/components/StartButton.d.ts +11 -0
  231. package/dist/components/features/exam-entry/themed/components/StartButton.js +1 -0
  232. package/dist/components/features/exam-entry/themed/components/index.d.ts +4 -0
  233. package/dist/components/features/exam-entry/themed/components/index.js +1 -0
  234. package/dist/components/features/exam-entry/themed/index.d.ts +1 -0
  235. package/dist/components/features/exam-entry/themed/index.js +1 -0
  236. package/dist/components/results/ResultReviewPageContainer.d.ts +3 -1
  237. package/dist/components/results/ResultReviewPageContainer.js +1 -1
  238. package/dist/components/results/hooks/useResultReviewPolling.d.ts +27 -0
  239. package/dist/components/results/hooks/useResultReviewPolling.js +1 -0
  240. package/dist/components/results/index.d.ts +3 -0
  241. package/dist/components/results/index.js +1 -1
  242. package/dist/components/results/review-status.utils.d.ts +12 -0
  243. package/dist/components/results/review-status.utils.js +1 -0
  244. package/dist/components/shared/audio/SpeakingAudioPlayer.d.ts +12 -0
  245. package/dist/components/shared/audio/SpeakingAudioPlayer.js +1 -0
  246. package/dist/components/shared/audio/SpeakingAudioPlayer.module.css +115 -0
  247. package/dist/components/shared/audio/assets/fr-image-part1-border.png +0 -0
  248. package/dist/components/shared/audio/assets/ic-speaker.webp +0 -0
  249. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -1
  250. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.d.ts +11 -3
  251. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -1
  252. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.d.ts +5 -1
  253. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -1
  254. package/dist/components/themes/index.d.ts +11 -0
  255. package/dist/components/themes/index.js +1 -1
  256. package/dist/components/themes/spaceship/SpaceshipAudioPlayer.d.ts +8 -0
  257. package/dist/components/themes/spaceship/SpaceshipAudioPlayer.js +1 -0
  258. package/dist/components/themes/spaceship/SpaceshipLayout.d.ts +2 -0
  259. package/dist/components/themes/spaceship/SpaceshipLayout.js +1 -0
  260. package/dist/components/themes/spaceship/assets/images/X-sunset.png +0 -0
  261. package/dist/components/themes/spaceship/assets/images/X.webp +0 -0
  262. package/dist/components/themes/spaceship/assets/images/audio-bar.webp +0 -0
  263. package/dist/components/themes/spaceship/assets/images/background.webp +0 -0
  264. package/dist/components/themes/spaceship/assets/images/bg_sunset.png +0 -0
  265. package/dist/components/themes/spaceship/assets/images/big-blue-badge-sunset.png +0 -0
  266. package/dist/components/themes/spaceship/assets/images/big-blue-badge.png +0 -0
  267. package/dist/components/themes/spaceship/assets/images/big-blue-badge.webp +0 -0
  268. package/dist/components/themes/spaceship/assets/images/bingo.webp +0 -0
  269. package/dist/components/themes/spaceship/assets/images/blue-badge-sunset.png +0 -0
  270. package/dist/components/themes/spaceship/assets/images/blue-badge.png +0 -0
  271. package/dist/components/themes/spaceship/assets/images/blue-wrapper-sunset.png +0 -0
  272. package/dist/components/themes/spaceship/assets/images/blue-wrapper.webp +0 -0
  273. package/dist/components/themes/spaceship/assets/images/board_red.png +0 -0
  274. package/dist/components/themes/spaceship/assets/images/button_feedback_vi.png +0 -0
  275. package/dist/components/themes/spaceship/assets/images/circle-btn-right-sunset.png +0 -0
  276. package/dist/components/themes/spaceship/assets/images/circle-btn-right.png +0 -0
  277. package/dist/components/themes/spaceship/assets/images/circle-btn-sunset.png +0 -0
  278. package/dist/components/themes/spaceship/assets/images/circle-btn.webp +0 -0
  279. package/dist/components/themes/spaceship/assets/images/focus-vector.webp +0 -0
  280. package/dist/components/themes/spaceship/assets/images/head-phone_red.png +0 -0
  281. package/dist/components/themes/spaceship/assets/images/ic-mute-sunset.png +0 -0
  282. package/dist/components/themes/spaceship/assets/images/ic-mute.png +0 -0
  283. package/dist/components/themes/spaceship/assets/images/ic-play-sunset.png +0 -0
  284. package/dist/components/themes/spaceship/assets/images/ic-play-violet.webp +0 -0
  285. package/dist/components/themes/spaceship/assets/images/ic-play.webp +0 -0
  286. package/dist/components/themes/spaceship/assets/images/micro.png +0 -0
  287. package/dist/components/themes/spaceship/assets/images/next-small.webp +0 -0
  288. package/dist/components/themes/spaceship/assets/images/next-sunset.png +0 -0
  289. package/dist/components/themes/spaceship/assets/images/next.webp +0 -0
  290. package/dist/components/themes/spaceship/assets/images/purple-circle.webp +0 -0
  291. package/dist/components/themes/spaceship/assets/images/question-badge-sunset.png +0 -0
  292. package/dist/components/themes/spaceship/assets/images/question-badge.webp +0 -0
  293. package/dist/components/themes/spaceship/assets/images/red-badge-sunset.png +0 -0
  294. package/dist/components/themes/spaceship/assets/images/red-badge.webp +0 -0
  295. package/dist/components/themes/spaceship/assets/images/red-wrapper-sunset.png +0 -0
  296. package/dist/components/themes/spaceship/assets/images/red-wrapper.webp +0 -0
  297. package/dist/components/themes/spaceship/assets/images/robot-avatar.png +0 -0
  298. package/dist/components/themes/spaceship/assets/images/sound_red.png +0 -0
  299. package/dist/components/themes/spaceship/assets/images/student-avatar.png +0 -0
  300. package/dist/components/themes/spaceship/assets/images/teacher-play-sunset.png +0 -0
  301. package/dist/components/themes/spaceship/assets/images/teacher-play.webp +0 -0
  302. package/dist/components/themes/spaceship/assets/images/timer-badge.png +0 -0
  303. package/dist/components/themes/spaceship/assets/styles/SpaceshipLayout.module.css +347 -0
  304. package/dist/components/themes/spaceship/assets/styles/SpaceshipWaitingRoom.module.css +395 -0
  305. package/dist/components/ui/badge.d.ts +1 -1
  306. package/dist/index.d.ts +4 -4
  307. package/dist/index.js +1 -1
  308. package/dist/shared/index.d.ts +3 -0
  309. package/dist/shared/index.js +1 -1
  310. package/dist/shared/lib/stores/examTakingStore.d.ts +54 -0
  311. package/dist/shared/lib/stores/examTakingStore.js +1 -0
  312. package/dist/shared/lib/stores/gradingTransientStore.d.ts +23 -0
  313. package/dist/shared/lib/stores/gradingTransientStore.js +1 -0
  314. package/dist/shared/lib/themes/config/starters.config.d.ts +3 -0
  315. package/dist/shared/lib/themes/config/starters.config.js +1 -0
  316. package/dist/shared/lib/themes/index.d.ts +18 -0
  317. package/dist/shared/lib/themes/index.js +1 -0
  318. package/dist/shared/lib/themes/types.d.ts +39 -0
  319. package/dist/shared/lib/themes/types.js +1 -0
  320. package/dist/shared/lib/toast.d.ts +19 -0
  321. package/dist/shared/lib/toast.js +1 -0
  322. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  323. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  324. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  325. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  326. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  327. package/dist/shared/lib/utils/question-transform.js +1 -1
  328. package/package.json +19 -4
@@ -4,10 +4,12 @@ export interface ResultReviewPageContainerProps {
4
4
  attemptId: string;
5
5
  token?: string | null;
6
6
  api: ResultReviewSdkApi;
7
+ /** When true, polling is disabled and PDF readiness is signalled (headless render). */
8
+ isPrintMode?: boolean;
7
9
  onBack?: () => void;
8
10
  onNavigate?: (href: string) => void;
9
11
  topActionsSlot?: ReactNode;
10
12
  beforeReportSlot?: ReactNode;
11
13
  afterReportSlot?: ReactNode;
12
14
  }
13
- export declare function ResultReviewPageContainer({ attemptId, token, api, onBack, onNavigate, topActionsSlot, beforeReportSlot, afterReportSlot }: ResultReviewPageContainerProps): import("react").JSX.Element;
15
+ export declare function ResultReviewPageContainer({ attemptId, token, api, isPrintMode, onBack, onNavigate, topActionsSlot, beforeReportSlot, afterReportSlot }: ResultReviewPageContainerProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as a,useMemo as s,useState as l}from"react";import{ChevronLeft as n,Download as r,Loader2 as i,Sparkles as c,XCircle as o}from"lucide-react";import{Button as m}from"../../components/ui/button";import{Card as d,CardContent as h,CardHeader as p,CardTitle as u}from"../../components/ui/card";import{useToast as x}from"../../shared/lib/hooks/useToast";import{transformResultAnswersForReview as f}from"./review-data";import{ReviewQuestionRenderer as b}from"./ReviewQuestionRenderer";import{unwrap as g}from"./result-review.utils";export function ResultReviewPageContainer({attemptId:c,token:b,api:y,onBack:A,onNavigate:S,topActionsSlot:R,beforeReportSlot:E,afterReportSlot:P}){const{error:j,success:$}=x(),[D,F]=l(null),[C,O]=l(null),[q,U]=l(!0),[K,L]=l(!1);a(()=>{let e=!1;return U(!0),Promise.all([y.getDetailedResult(c,b),y.getAttempt?.(c,b)]).then(([t,a])=>{e||(F(g(t)),O(a?g(a):null))}).catch(e=>j({message:"Không thể tải kết quả",description:e instanceof Error?e.message:void 0})).finally(()=>{e||U(!1)}),()=>{e=!0}},[y,c,b,j]);const M=s(()=>f(D?.answers??[],C?.exam?.template?.parts),[D?.answers,C?.exam?.template?.parts]),Q=C?.studentName??"Học sinh",T=C?.studentUsername,X=C?.branchName??"Chưa tham gia chi nhánh",H=C?.exam?.template?.title??D?.examName??"Kết quả bài thi",I=()=>{A?A():S?S("/student/results"):"undefined"!=typeof window&&window.history.back()},B=async()=>{if(D&&y.exportPdf){L(!0);try{const{blob:e,filename:t}=await y.exportPdf(c,C?.exam?.template?.type);!function(e,t){const a=URL.createObjectURL(e),s=document.createElement("a");s.href=a,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(a)}(e,t),$({message:"Xuất PDF thành công"})}catch(e){j({message:"Không xuất được PDF",description:e instanceof Error?e.message:void 0})}finally{L(!1)}}};return q?e(k,{icon:e(i,{className:"h-8 w-8 animate-spin text-[#2874A6]"}),title:"Đang tải kết quả bài thi..."}):D?e("div",{className:"min-h-screen bg-[#e0e0e0] py-4 md:py-6",children:t("div",{className:"mx-auto flex w-full max-w-[1160px] flex-col gap-4 px-3 sm:px-4",children:[t("div",{className:"flex items-center justify-between gap-3 print:hidden",children:[t(m,{variant:"outline",onClick:I,children:[e(n,{className:"mr-2 h-4 w-4"}),"Quay lại"]}),t("div",{className:"flex items-center gap-2",children:[y.exportPdf&&t(m,{onClick:()=>{B()},disabled:K,className:"bg-[#00AEEF] hover:bg-[#0099D6]",children:[K?e(i,{className:"mr-2 h-4 w-4 animate-spin"}):e(r,{className:"mr-2 h-4 w-4"}),"Xuất PDF"]}),R]})]}),E,t(d,{className:"overflow-hidden rounded-[28px] border-0 shadow-xl",children:[t(p,{className:"bg-gradient-to-r from-[#0054A6] to-[#00AEEF] text-white",children:[e(u,{className:"text-2xl",children:H}),t("p",{className:"text-sm text-white/80",children:[Q,T?` • ${T}`:""," • ",X]})]}),t(h,{className:"space-y-6 p-5 md:p-8",children:[e(N,{detail:D}),e(w,{detail:D}),!1!==D.showDetailedResults&&e(v,{reviewData:M})]})]}),P]})}):e(k,{icon:e(o,{className:"h-8 w-8 text-red-400"}),title:"Không thể tải kết quả",action:e(m,{onClick:I,children:"Quay lại"})})}function N({detail:a}){return t("div",{className:"grid gap-3 md:grid-cols-4",children:[e(y,{label:"Tổng điểm",value:`${A(a.totalScore)} / ${A(a.maxScore)}`}),e(y,{label:"Câu đúng",value:`${a.correctAnswers} / ${a.totalQuestions}`}),e(y,{label:"Xếp hạng",value:a.rank?`${a.rank}/${a.totalStudents??"-"}`:"-"}),e(y,{label:"Trạng thái",value:a.isPendingGrading?"Đang chấm":"Hoàn tất"})]})}function w({detail:a}){return a.llmOverallAssessment||a.llmSkillAssessment?t("section",{className:"rounded-2xl border border-[#00AEEF]/30 bg-[#f0f8ff] p-4",children:[t("div",{className:"mb-2 flex items-center gap-2 font-bold text-[#0054A6]",children:[e(c,{className:"h-5 w-5"}),"Phân tích tổng quan"]}),a.llmOverallAssessment&&e("p",{className:"whitespace-pre-wrap text-sm text-slate-700",children:a.llmOverallAssessment}),a.llmSkillAssessment&&e("div",{className:"mt-3 grid gap-2 md:grid-cols-2",children:Object.entries(a.llmSkillAssessment).map(([a,s])=>s?t("div",{className:"rounded-xl bg-white p-3 text-sm",children:[e("strong",{children:s.label}),e("p",{className:"mt-1 text-slate-600",children:s.feedback})]},a):null)})]}):null}function v({reviewData:a}){return e("div",{className:"space-y-6",children:a.parts.map((s,l)=>t("section",{className:"rounded-2xl border border-[#00AEEF] bg-white p-5",children:[t("div",{className:"mb-4 flex items-center justify-between",children:[e("h3",{className:"text-lg font-bold text-[#0054A6]",children:s.name||`Phần ${s.partNo}`}),a.partScoresMap[l]&&t("span",{className:"rounded-full bg-[#0054A6] px-3 py-1 text-sm font-bold text-white",children:[A(a.partScoresMap[l].score)," / ",A(a.partScoresMap[l].maxScore)]})]}),s.instructions&&e("p",{className:"mb-4 font-semibold text-slate-700",children:s.instructions}),e(b,{part:s,reviewData:a})]},`${s.partId}-${l}`))})}function y({label:a,value:s}){return t("div",{className:"rounded-2xl bg-slate-50 p-4",children:[e("p",{className:"text-xs font-semibold uppercase text-slate-500",children:a}),e("p",{className:"mt-1 text-xl font-black text-[#0054A6]",children:s})]})}function k({icon:a,title:s,action:l}){return e("div",{className:"flex min-h-screen items-center justify-center bg-[#e0e0e0]",children:t("div",{className:"rounded-[28px] bg-white p-8 text-center shadow-xl",children:[e("div",{className:"mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-50",children:a}),e("p",{className:"font-semibold text-slate-700",children:s}),l&&e("div",{className:"mt-5",children:l})]})})}function A(e){return null!=e&&Number.isFinite(e)?e<10?e.toFixed(1):String(Math.round(e)):"0"}
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useCallback as a,useEffect as n,useMemo as l,useState as s}from"react";import{ChevronLeft as i,Download as r,Loader2 as c,Lock as o,Sparkles as m,XCircle as d}from"lucide-react";import{Button as u}from"../../components/ui/button";import{Card as h,CardContent as p,CardHeader as f,CardTitle as x}from"../../components/ui/card";import{useToast as b}from"../../shared/lib/hooks/useToast";import{transformResultAnswersForReview as g}from"./review-data";import{ReviewQuestionRenderer as N}from"./ReviewQuestionRenderer";import{unwrap as w}from"./result-review.utils";import{useResultReviewPolling as v}from"./hooks/useResultReviewPolling";import{isResultNotPublic as y,setPdfReady as k}from"./review-status.utils";export function ResultReviewPageContainer({attemptId:m,token:N,api:S,isPrintMode:F=!1,onBack:j,onNavigate:$,topActionsSlot:C,beforeReportSlot:D,afterReportSlot:q}){const{error:M,success:O}=b(),[K,L]=s(null),[Q,U]=s(null),[B,T]=s(!0),[X,H]=s(null),[I,G]=s(!1),z=a(async e=>{e&&T(!0);try{const[e,t]=await Promise.all([S.getDetailedResult(m,N),S.getAttempt?.(m,N)]);L(w(e)),U(t?w(t):null),H(null)}catch(e){H(e),y(e,Boolean(N))||M({message:"Không thể tải kết quả",description:e instanceof Error?e.message:void 0})}finally{e&&T(!1)}},[S,m,N,M]);n(()=>{let e=!1;return z(!0).then(()=>{}),()=>{e=!0}},[z]);const J=a(()=>{z(!1)},[z]);v({detail:K,isPrintMode:F,isLoading:B,onRefetch:J}),n(()=>{if(!F||B)return;const e=requestAnimationFrame(()=>k(!0));return()=>{cancelAnimationFrame(e),k(!1)}},[F,B]);const V=l(()=>g(K?.answers??[],Q?.exam?.template?.parts),[K?.answers,Q?.exam?.template?.parts]),W=Q?.studentName??"Học sinh",Y=Q?.studentUsername,Z=Q?.branchName??"Chưa tham gia chi nhánh",_=Q?.exam?.template?.title??K?.examName??"Kết quả bài thi",ee=()=>{j?j():$?$("/student/results"):"undefined"!=typeof window&&window.history.back()},te=async()=>{if(K&&S.exportPdf){G(!0);try{const{blob:e,filename:t}=await S.exportPdf(m,Q?.exam?.template?.type);!function(e,t){const a=URL.createObjectURL(e),n=document.createElement("a");n.href=a,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(a)}(e,t),O({message:"Xuất PDF thành công"})}catch(e){M({message:"Không xuất được PDF",description:e instanceof Error?e.message:void 0})}finally{G(!1)}}};return B?e(E,{icon:e(c,{className:"h-8 w-8 animate-spin text-[#2874A6]"}),title:"Đang tải kết quả bài thi..."}):y(X,Boolean(N))?e(E,{icon:e(o,{className:"h-8 w-8 text-amber-500"}),title:"Kết quả này chưa được công khai",action:e(u,{onClick:ee,children:"Quay lại"})}):K?e("div",{className:"min-h-screen bg-[#e0e0e0] py-4 md:py-6",children:t("div",{className:"mx-auto flex w-full max-w-[1160px] flex-col gap-4 px-3 sm:px-4",children:[t("div",{className:"flex items-center justify-between gap-3 print:hidden",children:[t(u,{variant:"outline",onClick:ee,children:[e(i,{className:"mr-2 h-4 w-4"}),"Quay lại"]}),t("div",{className:"flex items-center gap-2",children:[S.exportPdf&&t(u,{onClick:()=>{te()},disabled:I,className:"bg-[#00AEEF] hover:bg-[#0099D6]",children:[I?e(c,{className:"mr-2 h-4 w-4 animate-spin"}):e(r,{className:"mr-2 h-4 w-4"}),"Xuất PDF"]}),C]})]}),D,t(h,{className:"overflow-hidden rounded-[28px] border-0 shadow-xl",children:[t(f,{className:"bg-gradient-to-r from-[#0054A6] to-[#00AEEF] text-white",children:[e(x,{className:"text-2xl",children:_}),t("p",{className:"text-sm text-white/80",children:[W,Y?` • ${Y}`:""," • ",Z]})]}),t(p,{className:"space-y-6 p-5 md:p-8",children:[e(A,{detail:K}),e(R,{detail:K}),!1!==K.showDetailedResults&&e(P,{reviewData:V})]})]}),q]})}):e(E,{icon:e(d,{className:"h-8 w-8 text-red-400"}),title:"Không thể tải kết quả",action:e(u,{onClick:ee,children:"Quay lại"})})}function A({detail:a}){return t("div",{className:"grid gap-3 md:grid-cols-4",children:[e(S,{label:"Tổng điểm",value:`${F(a.totalScore)} / ${F(a.maxScore)}`}),e(S,{label:"Câu đúng",value:`${a.correctAnswers} / ${a.totalQuestions}`}),e(S,{label:"Xếp hạng",value:a.rank?`${a.rank}/${a.totalStudents??"-"}`:"-"}),e(S,{label:"Trạng thái",value:a.isPendingGrading?"Đang chấm":"Hoàn tất"})]})}function R({detail:a}){if(!a.llmOverallAssessment&&!a.llmSkillAssessment)return null;const n=Object.entries(a.llmSkillAssessment??{}).filter(e=>null!=e[1]);return t("section",{className:"rounded-2xl border border-[#00AEEF]/30 bg-[#f0f8ff] p-4",children:[t("div",{className:"mb-2 flex items-center gap-2 font-bold text-[#0054A6]",children:[e(m,{className:"h-5 w-5"}),"Phân tích tổng quan"]}),a.llmOverallAssessment&&e("p",{className:"whitespace-pre-wrap text-sm text-slate-700",children:a.llmOverallAssessment}),n.length>0&&e("div",{className:"mt-3 grid gap-2 md:grid-cols-2",children:n.map(([a,n])=>t("div",{className:"rounded-xl bg-white p-3 text-sm",children:[e("strong",{children:n.label}),e("p",{className:"mt-1 text-slate-600",children:n.feedback})]},a))})]})}function P({reviewData:a}){return e("div",{className:"space-y-6",children:a.parts.map((n,l)=>t("section",{className:"rounded-2xl border border-[#00AEEF] bg-white p-5",children:[t("div",{className:"mb-4 flex items-center justify-between",children:[e("h3",{className:"text-lg font-bold text-[#0054A6]",children:n.name||`Phần ${n.partNo}`}),a.partScoresMap[l]&&t("span",{className:"rounded-full bg-[#0054A6] px-3 py-1 text-sm font-bold text-white",children:[F(a.partScoresMap[l].score)," / ",F(a.partScoresMap[l].maxScore)]})]}),n.instructions&&e("p",{className:"mb-4 font-semibold text-slate-700",children:n.instructions}),e(N,{part:n,reviewData:a})]},`${n.partId}-${l}`))})}function S({label:a,value:n}){return t("div",{className:"rounded-2xl bg-slate-50 p-4",children:[e("p",{className:"text-xs font-semibold uppercase text-slate-500",children:a}),e("p",{className:"mt-1 text-xl font-black text-[#0054A6]",children:n})]})}function E({icon:a,title:n,action:l}){return e("div",{className:"flex min-h-screen items-center justify-center bg-[#e0e0e0]",children:t("div",{className:"rounded-[28px] bg-white p-8 text-center shadow-xl",children:[e("div",{className:"mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-50",children:a}),e("p",{className:"font-semibold text-slate-700",children:n}),l&&e("div",{className:"mt-5",children:l})]})})}function F(e){return null!=e&&Number.isFinite(e)?e<10?e.toFixed(1):String(Math.round(e)):"0"}
@@ -0,0 +1,27 @@
1
+ import type { ResultReviewDetail } from '../../../api/result-review';
2
+ export interface UseResultReviewPollingOptions {
3
+ /** Latest loaded result detail (null while first load is in flight). */
4
+ detail: ResultReviewDetail | null;
5
+ /** Disable polling entirely (e.g. headless PDF print mode). */
6
+ isPrintMode: boolean;
7
+ /** Whether the initial load has finished. */
8
+ isLoading: boolean;
9
+ /** Poll interval in milliseconds. */
10
+ intervalMs?: number;
11
+ /** Called on each tick to refetch the result; should be stable. */
12
+ onRefetch: () => void;
13
+ }
14
+ export interface UseResultReviewPollingResult {
15
+ /** `true` while some answers are still being auto/LLM graded. */
16
+ hasPendingQuestions: boolean;
17
+ /** `true` while the LLM overall assessment hasn't arrived yet. */
18
+ hasPendingLlm: boolean;
19
+ /** `true` while polling is active. */
20
+ isPolling: boolean;
21
+ }
22
+ /**
23
+ * Polls the result endpoint while any answer is still being graded or the LLM
24
+ * assessment hasn't been generated yet. Ports the host app's behaviour: polling
25
+ * is suppressed in print mode so the headless PDF renderer reaches an idle state.
26
+ */
27
+ export declare function useResultReviewPolling({ detail, isPrintMode, isLoading, intervalMs, onRefetch, }: UseResultReviewPollingOptions): UseResultReviewPollingResult;
@@ -0,0 +1 @@
1
+ "use client";import{useEffect as e,useRef as n}from"react";const s=new Set(["PENDING","PROCESSING","QUEUED","IN_PROGRESS"]);export function useResultReviewPolling({detail:t,isPrintMode:l,isLoading:r,intervalMs:i=5e3,onRefetch:a}){const o=(u=t?.answers,!!u&&u.some(e=>null!=e.gradingStatus&&s.has(e.gradingStatus)));var u;const c=Boolean(t?.llmOverallAssessment||t?.llmSkillAssessment),g=Boolean(t)&&!c,m=!l&&!r&&(o||g),P=n(a);return P.current=a,e(()=>{if(!m)return;const e=setInterval(()=>P.current(),i);return()=>clearInterval(e)},[i,m]),{hasPendingQuestions:o,hasPendingLlm:g,isPolling:m}}
@@ -2,3 +2,6 @@ export { ResultReviewPageContainer } from './ResultReviewPageContainer';
2
2
  export type { ResultReviewPageContainerProps } from './ResultReviewPageContainer';
3
3
  export { ReviewQuestionRenderer } from './ReviewQuestionRenderer';
4
4
  export type { ReviewQuestionRendererProps } from './ReviewQuestionRenderer';
5
+ export { useResultReviewPolling } from './hooks/useResultReviewPolling';
6
+ export type { UseResultReviewPollingOptions, UseResultReviewPollingResult } from './hooks/useResultReviewPolling';
7
+ export { isResultNotPublic, setPdfReady } from './review-status.utils';
@@ -1 +1 @@
1
- export{ResultReviewPageContainer}from"./ResultReviewPageContainer";export{ReviewQuestionRenderer}from"./ReviewQuestionRenderer";
1
+ export{ResultReviewPageContainer}from"./ResultReviewPageContainer";export{ReviewQuestionRenderer}from"./ReviewQuestionRenderer";export{useResultReviewPolling}from"./hooks/useResultReviewPolling";export{isResultNotPublic,setPdfReady}from"./review-status.utils";
@@ -0,0 +1,12 @@
1
+ /** Status & access helpers for the result review screen. */
2
+ /**
3
+ * Determine whether a load error means "this result is not public". Ports the
4
+ * host app's logic: a 403 with an explicit `RESULT_NOT_PUBLIC`/`FORBIDDEN` code,
5
+ * or any 403 reached via a token link (the only anonymous-access path).
6
+ */
7
+ export declare function isResultNotPublic(error: unknown, hasToken: boolean): boolean;
8
+ /**
9
+ * Toggle the `data-pdf-ready` body attribute the headless PDF renderer waits on.
10
+ * Call with `true` once the result has finished loading in print mode.
11
+ */
12
+ export declare function setPdfReady(ready: boolean): void;
@@ -0,0 +1 @@
1
+ const t=new Set(["RESULT_NOT_PUBLIC","FORBIDDEN"]);export function isResultNotPublic(e,n){const o=function(t){return"object"!=typeof t||null===t?null:t}(e);if(403!==o?.response?.status)return!1;const r=o.response.data?.errorCode;return r?t.has(r):n}export function setPdfReady(t){"undefined"!=typeof document&&(t?document.body.setAttribute("data-pdf-ready","true"):document.body.removeAttribute("data-pdf-ready"))}
@@ -0,0 +1,12 @@
1
+ interface SpeakingAudioPlayerProps {
2
+ audioUrl: string;
3
+ onPlayingChange?: (isPlaying: boolean) => void;
4
+ /**
5
+ * Auto-play the audio a few seconds (3s) after it loads or changes — e.g.
6
+ * when navigating to the next question. Defaults to false so other usages
7
+ * (such as model-pronunciation playback) are not auto-triggered.
8
+ */
9
+ autoPlay?: boolean;
10
+ }
11
+ export declare function SpeakingAudioPlayer({ audioUrl, onPlayingChange, autoPlay }: SpeakingAudioPlayerProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as a}from"react/jsx-runtime";import{useRef as t,useState as n,useEffect as r}from"react";import{Pause as i,Play as o}from"lucide-react";import{usePresignedFileUrl as d}from"../../../shared/lib/hooks/usePresignedFileUrl";import{useDelayedAutoPlay as l}from"../../../shared/lib/hooks/useDelayedAutoPlay";import s from"./SpeakingAudioPlayer.module.css";const u=[.4,.6,.85,.55,.75,1,.65,.45,.7,.9,.6,.5,.8,.65,.95,.7,.5,.6,.8,.45];export function SpeakingAudioPlayer({audioUrl:c,onPlayingChange:m,autoPlay:p=!1}){const v=t(null),[h,y]=n(!1),[g,f]=n(0),[L,E]=n(0),{previewUrl:N}=d(c||void 0);r(()=>{const e=v.current;if(!e)return;const a=()=>{e.duration&&!isNaN(e.duration)&&isFinite(e.duration)&&E(e.duration)},t=()=>{f(e.currentTime),a()},n=()=>{y(!0),m?.(!0)},r=()=>{y(!1),m?.(!1)},i=()=>{y(!1),f(0),m?.(!1)};return a(),e.addEventListener("loadedmetadata",a),e.addEventListener("durationchange",a),e.addEventListener("canplay",a),e.addEventListener("timeupdate",t),e.addEventListener("play",n),e.addEventListener("pause",r),e.addEventListener("ended",i),()=>{e.removeEventListener("loadedmetadata",a),e.removeEventListener("durationchange",a),e.removeEventListener("canplay",a),e.removeEventListener("timeupdate",t),e.removeEventListener("play",n),e.removeEventListener("pause",r),e.removeEventListener("ended",i)}},[N]),r(()=>{y(!1),f(0),E(0)},[N]),l({audioRef:v,audioUrl:N,enabled:p});const b=L>0?g/L:0,P=!N;return a("div",{className:s.audioPill,children:[e("audio",{ref:v,...N?{src:N}:{},preload:"metadata"}),e("button",{type:"button",className:`${s.playButton} ${h?s.playButtonPlaying:""} ${P?s.playButtonDisabled:""}`,onClick:P?void 0:()=>{const e=v.current;e&&(h?e.pause():e.play().catch(e=>{console.warn("Audio play request was interrupted:",e)}))},disabled:P,"aria-label":P?"Không có âm thanh đính kèm":h?"Tạm dừng":"Nghe câu hỏi",title:P?"Không có âm thanh đính kèm":h?"Tạm dừng":"Nghe câu hỏi",children:h?e(i,{className:s.playIcon,fill:"currentColor"}):e(o,{className:s.playIcon,fill:"currentColor",style:{marginLeft:"2px"}})}),e("div",{className:s.waveform,"aria-hidden":"true",children:u.map((a,t)=>{const n=(t+.5)/u.length<=b;return e("span",{className:`${s.waveBar} ${h&&n?s.waveBarPlaying:""}`,style:{height:100*a+"%",background:n?"linear-gradient(180deg, #ffc93c, #ff9900)":"#d8e0ea",animationDelay:h&&n?t%6*.1+"s":void 0}},t)})})]})}
@@ -0,0 +1,115 @@
1
+ /* ===== Speaking Audio Player — "Listen to the question" ===== */
2
+ /* Small, simple, kid-friendly player for light backgrounds (Summer Sky theme):
3
+ a sky-blue play/replay button + a sunny-amber waveform progress indicator.
4
+ No time digits, no seek — compact and tap-friendly. */
5
+
6
+ .audioPill {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: 10px;
10
+ width: 100%;
11
+ padding: 5px 14px 5px 5px;
12
+ background: #ffffff;
13
+ border: 2px solid #dcebff;
14
+ border-radius: 999px;
15
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ /* ===== Play / Pause button ===== */
20
+ .playButton {
21
+ flex-shrink: 0;
22
+ height: 42px;
23
+ width: 42px;
24
+ min-width: 42px;
25
+ border: none;
26
+ border-radius: 50%;
27
+ background: linear-gradient(135deg, #5ba8e8, #357abd);
28
+ color: #ffffff;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ cursor: pointer;
33
+ padding: 0;
34
+ box-shadow: 0 3px 8px rgba(53, 122, 189, 0.35);
35
+ transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease;
36
+ }
37
+
38
+ .playButton:hover {
39
+ transform: scale(1.08);
40
+ }
41
+
42
+ .playButton:active {
43
+ transform: scale(0.95);
44
+ }
45
+
46
+ .playIcon {
47
+ width: 20px;
48
+ height: 20px;
49
+ }
50
+
51
+ /* Playing — coral with a gentle pulsing ring */
52
+ .playButtonPlaying {
53
+ background: linear-gradient(135deg, #ff8a8a, #ff6b6b);
54
+ box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.5);
55
+ animation: playPulse 1.5s infinite;
56
+ }
57
+
58
+ @keyframes playPulse {
59
+ 0% {
60
+ box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.5);
61
+ }
62
+
63
+ 70% {
64
+ box-shadow: 0 0 0 8px rgba(255, 107, 107, 0);
65
+ }
66
+
67
+ 100% {
68
+ box-shadow: 0 0 0 0 rgba(255, 107, 107, 0);
69
+ }
70
+ }
71
+
72
+ /* Disabled — no audio attached */
73
+ .playButtonDisabled {
74
+ background: #cbd5e1;
75
+ box-shadow: none;
76
+ cursor: not-allowed;
77
+ opacity: 0.7;
78
+ animation: none;
79
+ }
80
+
81
+ .playButtonDisabled:hover {
82
+ transform: none;
83
+ }
84
+
85
+ /* ===== Waveform progress indicator (display-only, fills L→R with progress) ===== */
86
+ .waveform {
87
+ flex: 1;
88
+ min-width: 0;
89
+ height: 20px;
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 2px;
93
+ }
94
+
95
+ .waveBar {
96
+ flex: 1;
97
+ min-width: 0;
98
+ border-radius: 999px;
99
+ transform-origin: center;
100
+ }
101
+
102
+ /* Subtle liveliness on the already-played region while playing */
103
+ .waveBarPlaying {
104
+ animation: waveBounce 0.9s ease-in-out infinite alternate;
105
+ }
106
+
107
+ @keyframes waveBounce {
108
+ 0% {
109
+ transform: scaleY(0.7);
110
+ }
111
+
112
+ 100% {
113
+ transform: scaleY(1);
114
+ }
115
+ }
@@ -1 +1 @@
1
- import{jsx as e,jsxs as t}from"react/jsx-runtime";import s from"next/image";import r from"@/components/themes/summer-sky/assets/images/clock.png";export function CambridgeYleTimer({remainingSeconds:a,totalQuestions:n,answeredCount:p}){const o=a%60,x=`${Math.floor(a/60).toString().padStart(2,"0")}:${o.toString().padStart(2,"0")}`;let m="bg-[#001590]",i="shadow-[0_4px_20px_rgba(0,0,0,0.25)]";return a<=120?(m="bg-[rgba(220,38,38,0.85)]",i="shadow-[0_0_24px_rgba(220,38,38,0.5)] animate-pulse"):a<=300&&(m="bg-[rgba(180,120,0,0.85)]"),t("div",{className:`flex items-center w-[130px] sm:w-[170px] h-[40px] sm:h-[50px] rounded-[33.5px] backdrop-blur-[8px] transition-all duration-300 ease-in-out border-none ${m} ${i}`,children:[e(s,{src:r,alt:"clock",width:75,height:75,className:"w-[50px] h-[50px] sm:w-[75px] sm:h-[75px] -ml-[5px] sm:-ml-[10px] -mt-[10px] sm:-mt-[15px] shrink-0 drop-shadow-[0_2px_6px_rgba(0,0,0,0.2)] pointer-events-none z-10"}),e("span",{className:"flex-1 text-center font-bold text-[18px] sm:text-[25px] leading-none tracking-[-0.03em] uppercase text-white pr-[10px] sm:pr-[16px]",style:{fontFamily:"'SVN-Volte Rounded', 'Quicksand', sans-serif",textShadow:"0 2px 4px rgba(0, 0, 0, 0.2)"},children:x})]})}
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import s from"next/image";import r from"../../../components/themes/summer-sky/assets/images/clock.png";export function CambridgeYleTimer({remainingSeconds:a,totalQuestions:n,answeredCount:p}){const o=a%60,x=`${Math.floor(a/60).toString().padStart(2,"0")}:${o.toString().padStart(2,"0")}`;let m="bg-[#001590]",i="shadow-[0_4px_20px_rgba(0,0,0,0.25)]";return a<=120?(m="bg-[rgba(220,38,38,0.85)]",i="shadow-[0_0_24px_rgba(220,38,38,0.5)] animate-pulse"):a<=300&&(m="bg-[rgba(180,120,0,0.85)]"),t("div",{className:`flex items-center w-[130px] sm:w-[170px] h-[40px] sm:h-[50px] rounded-[33.5px] backdrop-blur-[8px] transition-all duration-300 ease-in-out border-none ${m} ${i}`,children:[e(s,{src:r,alt:"clock",width:75,height:75,className:"w-[50px] h-[50px] sm:w-[75px] sm:h-[75px] -ml-[5px] sm:-ml-[10px] -mt-[10px] sm:-mt-[15px] shrink-0 drop-shadow-[0_2px_6px_rgba(0,0,0,0.2)] pointer-events-none z-10"}),e("span",{className:"flex-1 text-center font-bold text-[18px] sm:text-[25px] leading-none tracking-[-0.03em] uppercase text-white pr-[10px] sm:pr-[16px]",style:{fontFamily:"'SVN-Volte Rounded', 'Quicksand', sans-serif",textShadow:"0 2px 4px rgba(0, 0, 0, 0.2)"},children:x})]})}
@@ -2,12 +2,20 @@ interface EnglishCertificationAudioPlayerProps {
2
2
  audioUrl: string;
3
3
  partNumber?: number;
4
4
  isLoading?: boolean;
5
+ /**
6
+ * TOEIC play-once rule: once true, playback is permanently disabled for
7
+ * this part (persisted so a page reload can't be used to re-listen).
8
+ */
9
+ isLocked?: boolean;
10
+ /** Fired the first time the student starts playback in this part. */
11
+ onFirstPlay?: () => void;
5
12
  }
6
13
  /**
7
14
  * Branded Ocean Edu audio bar for English Certification Listening sections.
8
15
  *
9
- * Handles play/pause/reset, progress and presigned-URL resolution while
10
- * adopting the compact Ocean Edu audio-bar look.
16
+ * Enforces the TOEIC "play once" rule: no seeking/scrubbing and no replay
17
+ * once playback starts, `onFirstPlay` locks the part permanently (survives
18
+ * reload via the caller's persisted state).
11
19
  */
12
- export declare function EnglishCertificationAudioPlayer({ audioUrl, partNumber, isLoading, }: EnglishCertificationAudioPlayerProps): import("react").JSX.Element;
20
+ export declare function EnglishCertificationAudioPlayer({ audioUrl, partNumber, isLoading, isLocked, onFirstPlay, }: EnglishCertificationAudioPlayerProps): import("react").JSX.Element;
13
21
  export {};
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useRef as t,useState as a,useEffect as n}from"react";import{Play as i,Pause as d,RotateCcw as l,RefreshCw as s}from"lucide-react";import{usePresignedFileUrl as o}from"../../../shared/lib/hooks/usePresignedFileUrl";import{cn as c}from"../../../shared/lib/utils";export function EnglishCertificationAudioPlayer({audioUrl:u,partNumber:p=0,isLoading:m=!1}){const b=t(null),{previewUrl:f}=o(u||void 0),[g,x]=a(!1),[h,v]=a(0),[y,N]=a(0),[w,L]=a(null),E=e=>{if(!e||isNaN(e))return"00:00";const r=Math.floor(e/60),t=Math.floor(e%60);return`${r.toString().padStart(2,"0")}:${t.toString().padStart(2,"0")}`};n(()=>{const e=b.current;if(!e)return;const r=()=>v(e.currentTime),t=()=>{N(e.duration),L(null)},a=()=>x(!1),n=()=>{L("Failed to load audio file"),x(!1)};return e.addEventListener("timeupdate",r),e.addEventListener("loadedmetadata",t),e.addEventListener("ended",a),e.addEventListener("error",n),()=>{e.removeEventListener("timeupdate",r),e.removeEventListener("loadedmetadata",t),e.removeEventListener("ended",a),e.removeEventListener("error",n)}},[]),n(()=>{b.current&&f&&(b.current.src=f,v(0),x(!1),L(null))},[f]);const _=y>0?h/y*100:0,k=w?"Audio unavailable":m?"Loading...":g?"Playing...":h>0?"Paused":"Ready to play";return r("div",{className:"grid w-full grid-cols-[64px_1fr] items-center gap-4 rounded-[10px] border border-[#d9e4f2] bg-white/90 p-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] sm:gap-[18px] sm:px-5",children:[e("audio",{ref:b}),e("button",{type:"button",onClick:()=>{b.current&&(g?(b.current.pause(),x(!1)):(b.current.play().catch(e=>{L(`Failed to play audio: ${e.message}`)}),x(!0)))},disabled:m||!u||!!w,className:"grid h-14 w-14 place-items-center rounded-full text-2xl text-white shadow-[0_12px_22px_rgba(11,87,208,0.22)] transition-transform hover:scale-[1.03] disabled:cursor-not-allowed disabled:opacity-50",style:{background:"linear-gradient(180deg, #1f7cff, #0b57d0)"},"aria-label":g?"Pause audio":"Play audio",children:g?e(d,{size:24}):e(i,{size:24,className:"ml-0.5"})}),r("div",{className:"min-w-0",children:[r("div",{className:"flex items-center justify-between gap-3",children:[r("span",{className:"text-[18px] font-black text-[#0f1730]",children:["Listening Audio",p>0&&` · Part ${p}`]}),e("button",{type:"button",onClick:()=>{b.current&&(b.current.currentTime=0,b.current.pause(),x(!1),v(0))},disabled:m||!u||!!w,className:"flex h-8 w-8 items-center justify-center rounded-full border border-[#d9e4f2] text-[#0b57d0] transition-colors hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-50","aria-label":"Replay from start",children:e(l,{size:16})})]}),r("div",{className:"mt-1 flex items-center gap-2.5",children:[e("span",{className:c("text-sm font-bold",w?"text-red-500":"text-[#64748b]"),children:w??k}),w&&r("button",{type:"button",onClick:()=>{const e=b.current;e&&f&&(L(null),v(0),x(!1),e.src=f,e.load())},disabled:!u,className:"inline-flex items-center gap-1.5 rounded-full border border-red-200 bg-red-50 px-3 py-1 text-xs font-bold text-red-600 transition-colors hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-50","aria-label":"Retry loading audio",children:[e(s,{size:13}),"Retry"]})]}),r("div",{className:"mt-2 grid grid-cols-[46px_1fr_46px] items-center gap-2.5 text-sm font-bold tabular-nums text-[#0f1730]",children:[e("span",{children:E(h)}),r("div",{className:"relative",children:[e("div",{className:"pointer-events-none absolute inset-y-0 left-0 rounded-full",style:{width:`${_}%`,background:"linear-gradient(90deg, #1f7cff, #0b57d0)"}}),e("input",{type:"range",min:"0",max:y||0,step:"0.1",value:h,onChange:e=>{const r=parseFloat(e.target.value);b.current&&(b.current.currentTime=r,v(r))},disabled:m||!u||!!w,className:"relative z-10 h-2 w-full cursor-pointer appearance-none rounded-full bg-[#dfe9f6] accent-[#0b57d0] disabled:cursor-not-allowed disabled:opacity-50","aria-label":"Audio progress"})]}),e("span",{children:y>0?E(y):"00:00"})]})]})]})}
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useRef as t,useState as a,useEffect as i}from"react";import{Play as d,Pause as l,RefreshCw as n,Lock as o}from"lucide-react";import{usePresignedFileUrl as s}from"../../../shared/lib/hooks/usePresignedFileUrl";import{cn as c}from"../../../shared/lib/utils";export function EnglishCertificationAudioPlayer({audioUrl:u,partNumber:m=0,isLoading:p=!1,isLocked:f=!1,onFirstPlay:b}){const g=t(null),{previewUrl:x}=s(u||void 0),[h,v]=a(!1),[y,w]=a(0),[N,L]=a(0),[E,_]=a(null),k=t(!1),P=p||!u||!!E||f,A=e=>{if(!e||isNaN(e))return"00:00";const r=Math.floor(e/60),t=Math.floor(e%60);return`${r.toString().padStart(2,"0")}:${t.toString().padStart(2,"0")}`};i(()=>{const e=g.current;if(!e)return;const r=()=>w(e.currentTime),t=()=>{L(e.duration),_(null)},a=()=>v(!1),i=()=>{_("Failed to load audio file"),v(!1)};return e.addEventListener("timeupdate",r),e.addEventListener("loadedmetadata",t),e.addEventListener("ended",a),e.addEventListener("error",i),()=>{e.removeEventListener("timeupdate",r),e.removeEventListener("loadedmetadata",t),e.removeEventListener("ended",a),e.removeEventListener("error",i)}},[]),i(()=>{g.current&&x&&(g.current.src=x,w(0),v(!1),_(null))},[x]);const $=N>0?y/N*100:0,j=f?"Already played":E?"Audio unavailable":p?"Loading...":h?"Playing...":y>0?"Paused":"Ready to play";return r("div",{className:"grid w-full grid-cols-[64px_1fr] items-center gap-4 rounded-[10px] border border-[#d9e4f2] bg-white/90 p-4 shadow-[0_5px_16px_rgba(13,71,161,0.08)] sm:gap-[18px] sm:px-5",children:[e("audio",{ref:g}),e("button",{type:"button",onClick:()=>{g.current&&!f&&(h?(g.current.pause(),v(!1)):(k.current||(k.current=!0,b?.()),g.current.play().catch(e=>{_(`Failed to play audio: ${e.message}`)}),v(!0)))},disabled:P,className:"grid h-14 w-14 place-items-center rounded-full text-2xl text-white shadow-[0_12px_22px_rgba(11,87,208,0.22)] transition-transform hover:scale-[1.03] disabled:cursor-not-allowed disabled:opacity-50",style:{background:"linear-gradient(180deg, #1f7cff, #0b57d0)"},"aria-label":f?"Audio locked, already played":h?"Pause audio":"Play audio",children:f?e(o,{size:22}):h?e(l,{size:24}):e(d,{size:24,className:"ml-0.5"})}),r("div",{className:"min-w-0",children:[e("div",{className:"flex items-center justify-between gap-3",children:r("span",{className:"text-[18px] font-black text-[#0f1730]",children:["Listening Audio",m>0&&` · Part ${m}`]})}),r("div",{className:"mt-1 flex items-center gap-2.5",children:[e("span",{className:c("text-sm font-bold",E?"text-red-500":"text-[#64748b]"),children:E??j}),E&&r("button",{type:"button",onClick:()=>{const e=g.current;e&&x&&(_(null),w(0),v(!1),e.src=x,e.load())},disabled:!u,className:"inline-flex items-center gap-1.5 rounded-full border border-red-200 bg-red-50 px-3 py-1 text-xs font-bold text-red-600 transition-colors hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-50","aria-label":"Retry loading audio",children:[e(n,{size:13}),"Retry"]})]}),r("div",{className:"mt-2 grid grid-cols-[46px_1fr_46px] items-center gap-2.5 text-sm font-bold tabular-nums text-[#0f1730]",children:[e("span",{children:A(y)}),e("div",{className:"relative h-2 w-full overflow-hidden rounded-full bg-[#dfe9f6]",role:"progressbar","aria-label":"Audio progress","aria-valuemin":0,"aria-valuemax":N||0,"aria-valuenow":y,children:e("div",{className:"absolute inset-y-0 left-0 rounded-full",style:{width:`${$}%`,background:"linear-gradient(90deg, #1f7cff, #0b57d0)"}})}),e("span",{children:N>0?A(N):"00:00"})]})]})]})}
@@ -10,6 +10,10 @@ interface EnglishCertificationQuestionRendererProps {
10
10
  onAnswerChange: (questionId: string, answer: any) => void;
11
11
  onToggleFlag: (questionId: string) => void;
12
12
  isReviewMode?: boolean;
13
+ /** TOEIC play-once: true once this part's listening audio has been played. */
14
+ isAudioPlayed?: boolean;
15
+ /** Fired the first time the student plays this part's listening audio. */
16
+ onAudioFirstPlay?: () => void;
13
17
  }
14
18
  /**
15
19
  * Dispatcher for English Certification question rendering based on part config.
@@ -20,5 +24,5 @@ interface EnglishCertificationQuestionRendererProps {
20
24
  * (full-width, structured to match TOEIC) — never delegated to the shared
21
25
  * Movers renderer, which squeezed IELTS tables into narrow columns.
22
26
  */
23
- export declare function EnglishCertificationQuestionRenderer({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode, }: EnglishCertificationQuestionRendererProps): import("react").JSX.Element;
27
+ export declare function EnglishCertificationQuestionRenderer({ part, partConfig, answers, flaggedQuestions, onAnswerChange, onToggleFlag, isReviewMode, isAudioPlayed, onAudioFirstPlay, }: EnglishCertificationQuestionRendererProps): import("react").JSX.Element;
24
28
  export {};
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{SkillEnum as i}from"../../../shared/types/common.types";import{EnglishCertificationSingleQuestionList as n}from"./EnglishCertificationSingleQuestionList";import{EnglishCertificationGroupedQuestion as o}from"./EnglishCertificationGroupedQuestion";import{EnglishCertificationAudioPlayer as s}from"./EnglishCertificationAudioPlayer";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as t}from"./english-certification-theme";import{EcWordFillStructuredForm as a}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as l}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as d}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as c}from"./EcWordFillParagraph";import{EcWritingTask as g}from"./EcWritingTask";const u=new Set(["CHOOSE_THE_CORRECT_ANSWER"]);export function EnglishCertificationQuestionRenderer({part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E=!1}){if(!p.questions.length)return e("div",{className:"flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600",children:"No questions found for this part"});const A=m.category===i.LISTENING,C=A?p.audioUrl??m.audioUrl??"":void 0,T=p.questionType,N=Boolean(p.isGroup)&&"DOCUMENT"===p.group?.type,_=m.isGroup&&u.has(T),v=Boolean(p.sections?.length);if(N||_||v)return e("div",{className:"h-full",children:e(o,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E,isListeningPart:A,partAudioUrl:C})});if(u.has(T))return e(n,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:E,isListeningPart:A,partAudioUrl:C});return r("div",{className:"flex flex-col items-center gap-5 pb-4",children:[A&&e("div",{className:t,children:e(s,{audioUrl:C??"",partNumber:p.partNo})}),e("div",{className:`${t} w-full`,children:(()=>{switch(T){case"WORD_FILL_STRUCTURED_FORM":return e(a,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"MATCH_BY_WRITING_ANSWER":return e(l,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"ANSWER_THE_QUESTION":return e(d,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"WORD_FILL_PARAGRAPH":return e(c,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});case"WRITE_A_SHORT_LETTER":return e(g,{part:p,answers:f,onAnswerChange:w,isReviewMode:E});default:return r("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700",children:['Loại câu hỏi "',T,'" chưa được hỗ trợ trong giao diện này.']})}})()})]})}
1
+ "use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{SkillEnum as i}from"../../../shared/types/common.types";import{EnglishCertificationSingleQuestionList as n}from"./EnglishCertificationSingleQuestionList";import{EnglishCertificationGroupedQuestion as o}from"./EnglishCertificationGroupedQuestion";import{EnglishCertificationAudioPlayer as s}from"./EnglishCertificationAudioPlayer";import{ENGLISH_CERT_CONTENT_WIDTH_CLASS as t}from"./english-certification-theme";import{EcWordFillStructuredForm as a}from"./EcWordFillStructuredForm";import{EcMatchByWritingAnswer as l}from"./EcMatchByWritingAnswer";import{EcAnswerTheQuestion as d}from"./EcAnswerTheQuestion";import{EcWordFillParagraph as c}from"./EcWordFillParagraph";import{EcWritingTask as u}from"./EcWritingTask";const g=new Set(["CHOOSE_THE_CORRECT_ANSWER"]);export function EnglishCertificationQuestionRenderer({part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:A=!1,isAudioPlayed:E=!1,onAudioFirstPlay:C}){if(!p.questions.length)return e("div",{className:"flex h-full items-center justify-center rounded-xl border border-red-200 bg-red-50 p-8 text-red-600",children:"No questions found for this part"});const T=m.category===i.LISTENING,N=T?p.audioUrl??m.audioUrl??"":void 0,_=p.questionType,v=Boolean(p.isGroup)&&"DOCUMENT"===p.group?.type,x=m.isGroup&&g.has(_),y=Boolean(p.sections?.length);if(v||x||y)return e("div",{className:"h-full",children:e(o,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:A,isListeningPart:T,partAudioUrl:N})});if(g.has(_))return e(n,{part:p,partConfig:m,answers:f,flaggedQuestions:h,onAnswerChange:w,onToggleFlag:R,isReviewMode:A,isListeningPart:T,partAudioUrl:N});return r("div",{className:"flex flex-col items-center gap-5 pb-4",children:[T&&e("div",{className:t,children:e(s,{audioUrl:N??"",partNumber:p.partNo,isLocked:!A&&E,onFirstPlay:C})}),e("div",{className:`${t} w-full`,children:(()=>{switch(_){case"WORD_FILL_STRUCTURED_FORM":return e(a,{part:p,answers:f,onAnswerChange:w,isReviewMode:A});case"MATCH_BY_WRITING_ANSWER":return e(l,{part:p,answers:f,onAnswerChange:w,isReviewMode:A});case"ANSWER_THE_QUESTION":return e(d,{part:p,answers:f,onAnswerChange:w,isReviewMode:A});case"WORD_FILL_PARAGRAPH":return e(c,{part:p,answers:f,onAnswerChange:w,isReviewMode:A});case"WRITE_A_SHORT_LETTER":return e(u,{part:p,answers:f,onAnswerChange:w,isReviewMode:A});default:return r("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-700",children:['Loại câu hỏi "',_,'" chưa được hỗ trợ trong giao diện này.']})}})()})]})}
@@ -1,7 +1,18 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { SpeakingLayoutProps, StandardExamLayoutProps, StandardQuestionCardProps } from './types';
1
3
  export * from './cambridge-yle';
2
4
  export * from './english-certification';
5
+ export * from './spaceship/SpaceshipLayout';
6
+ export * from './spaceship/SpaceshipAudioPlayer';
3
7
  export * from './summer-sky/SummerSkyAudioPlayer';
4
8
  export * from './summer-sky/SummerSkyLayout';
5
9
  export * from './summer-sky/SummerSkySubmitModal';
6
10
  export * from './summer-sky/SummerSkyWaitingRoom';
7
11
  export type * from './types';
12
+ export type SpeakingThemeId = 'SPACESHIP' | 'SUMMER_SKY' | string;
13
+ export declare function getSpeakingLayout(themeId?: SpeakingThemeId): ComponentType<SpeakingLayoutProps>;
14
+ export interface StandardThemeEntry {
15
+ Layout: ComponentType<StandardExamLayoutProps>;
16
+ QuestionCard: ComponentType<StandardQuestionCardProps>;
17
+ }
18
+ export declare function getStandardExamTheme(themeId?: string): StandardThemeEntry;
@@ -1 +1 @@
1
- export*from"./cambridge-yle";export*from"./english-certification";export*from"./summer-sky/SummerSkyAudioPlayer";export*from"./summer-sky/SummerSkyLayout";export*from"./summer-sky/SummerSkySubmitModal";export*from"./summer-sky/SummerSkyWaitingRoom";
1
+ import{SpaceshipLayout as r}from"./spaceship/SpaceshipLayout";import{SummerSkyLayout as e}from"./summer-sky/SummerSkyLayout";import{CambridgeYleExamLayout as m}from"./cambridge-yle/CambridgeYleExamLayout";import o from"./cambridge-yle/CambridgeYleQuestionCard";export*from"./cambridge-yle";export*from"./english-certification";export*from"./spaceship/SpaceshipLayout";export*from"./spaceship/SpaceshipAudioPlayer";export*from"./summer-sky/SummerSkyAudioPlayer";export*from"./summer-sky/SummerSkyLayout";export*from"./summer-sky/SummerSkySubmitModal";export*from"./summer-sky/SummerSkyWaitingRoom";const t={SPACESHIP:r,SUMMER_SKY:e},a="SUMMER_SKY";export function getSpeakingLayout(r){return t[r||a]||t[a]}const i={CAMBRIDGE_YLE:{Layout:m,QuestionCard:o}},p="CAMBRIDGE_YLE";export function getStandardExamTheme(r){return i[r||p]||i[p]}
@@ -0,0 +1,8 @@
1
+ interface SpaceshipAudioPlayerProps {
2
+ audioUrl: string;
3
+ partName?: string;
4
+ onEnded?: () => void;
5
+ onPlayingChange?: (isPlaying: boolean) => void;
6
+ }
7
+ export declare function SpaceshipAudioPlayer({ audioUrl, partName, onEnded, onPlayingChange }: SpaceshipAudioPlayerProps): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as a,useState as r,useEffect as n}from"react";import s from"next/image";import{Pause as i,Play as l,VolumeX as o}from"lucide-react";import{usePresignedFileUrl as d}from"../../../shared/lib/hooks/usePresignedFileUrl";import c from"./assets/styles/SpaceshipLayout.module.css";import u from"./assets/images/big-blue-badge-sunset.png";import m from"./assets/images/teacher-play-sunset.png";import p from"./assets/images/ic-mute-sunset.png";function v(e){if(isNaN(e)||!isFinite(e))return"0:00";const t=Math.floor(e/60),a=Math.floor(e%60);return`${t}:${String(a).padStart(2,"0")}`}export function SpaceshipAudioPlayer({audioUrl:h,partName:f="Part 1 - Phase 2",onEnded:g,onPlayingChange:N}){const w=a(null),[y,b]=r(!1),[L,E]=r(0),[x,k]=r(0),[C,B]=r(100),{previewUrl:P}=d(h||void 0);n(()=>{const e=w.current;if(!e)return;const t=()=>{e.duration&&!isNaN(e.duration)&&isFinite(e.duration)&&k(e.duration)},a=()=>{E(e.currentTime),t()},r=()=>{b(!0),N?.(!0)},n=()=>{b(!1),N?.(!1)},s=()=>{b(!1),E(0),N?.(!1),g?.()};return t(),e.addEventListener("loadedmetadata",t),e.addEventListener("durationchange",t),e.addEventListener("canplay",t),e.addEventListener("timeupdate",a),e.addEventListener("play",r),e.addEventListener("pause",n),e.addEventListener("ended",s),()=>{e.removeEventListener("loadedmetadata",t),e.removeEventListener("durationchange",t),e.removeEventListener("canplay",t),e.removeEventListener("timeupdate",a),e.removeEventListener("play",r),e.removeEventListener("pause",n),e.removeEventListener("ended",s)}},[P,g]),n(()=>{b(!1),E(0),k(0)},[P]),n(()=>{const e=w.current;if(!e||!P)return;let t=!1;const a=()=>{t||e.play().catch(()=>{if(t)return;const a=()=>{t||(e.play().catch(()=>{}),document.removeEventListener("click",a),document.removeEventListener("keydown",a))};document.addEventListener("click",a,{once:!0}),document.addEventListener("keydown",a,{once:!0})})};return e.readyState>=3?a():e.addEventListener("canplay",a,{once:!0}),()=>{t=!0,e.removeEventListener("canplay",a)}},[P]);const F=x>0?L/x*100:0;return t("div",{className:c.examAudioContainer,children:[e("audio",{ref:w,...P?{src:P}:{},preload:"metadata"}),t("div",{className:c.bigBlueBadge,children:[e(s,{src:u,alt:"Audio Badge",className:c.bigBlueBadgeBg,priority:!0}),e("div",{className:c.audioBarContent,children:t("div",{className:c.audioBarInner,children:[e("span",{className:c.partLabel,children:f}),t("div",{className:c.teacherAvatarContainer,onClick:P?()=>{const e=w.current;e&&(e.paused?e.play().catch(()=>{}):e.pause())}:void 0,children:[e("img",{src:m.src,alt:"Play",className:c.teacherPlayIcon}),e("div",{className:"absolute inset-0 flex items-center justify-center",children:y?e(i,{className:"h-5 w-5 text-white drop-shadow-md",fill:"currentColor"}):e(l,{className:"h-5 w-5 text-white drop-shadow-md ml-1",fill:"currentColor"})})]}),t("div",{className:c.audioControlsWrapper,children:[t("span",{className:c.audioTime,children:[v(L)," / ",v(x)]}),t("div",{className:c.sliderTrack,children:[e("div",{className:c.sliderFill,style:{width:`${F}%`}}),e("input",{type:"range",min:"0",max:x||0,step:"0.1",value:L,onChange:e=>{const t=w.current;if(!t)return;const a=parseFloat(e.target.value);t.currentTime=a,E(a)},className:"absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed",disabled:!P})]}),t("div",{className:c.volumeContainer,children:[e("div",{className:c.volumeIcon,onClick:()=>{const e=w.current;e&&(C>0?(e.volume=0,B(0)):(e.volume=1,B(100)))},children:0===C?e(o,{className:"text-white w-full h-full"}):e(s,{src:p,alt:"volume",className:"w-full h-full"})}),e("div",{className:"flex-grow items-center flex",style:{width:"60px"},children:e("input",{type:"range",min:"0",max:"100",value:C,onChange:e=>{const t=w.current;if(!t)return;const a=parseFloat(e.target.value);t.volume=a/100,B(a)},className:"w-full h-[3px] appearance-none bg-white/50 rounded cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white"})})]})]})]})})]})]})}
@@ -0,0 +1,2 @@
1
+ import type { SpeakingLayoutProps } from '../types';
2
+ export declare function SpaceshipLayout({ onExit, partName, headerRight, onPrev, onNext, canGoPrev, canGoNext, audioUrl, onAudioEnded, recorder, children, contentClassName, hideAudioBar, onAudioPlayingChange, }: SpeakingLayoutProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ "use client";import{jsx as e,jsxs as t,Fragment as s}from"react/jsx-runtime";import a from"next/image";import r from"./assets/styles/SpaceshipLayout.module.css";import{SpaceshipAudioPlayer as i}from"./SpaceshipAudioPlayer";import n from"./assets/images/circle-btn-right-sunset.png";import l from"./assets/images/circle-btn-sunset.png";import c from"./assets/images/next-sunset.png";import o from"./assets/images/X-sunset.png";import m from"./assets/images/button_feedback_vi.png";import d from"./assets/images/blue-wrapper-sunset.png";import p from"./assets/images/timer-badge.png";import h from"./assets/images/micro.png";export function SpaceshipLayout({onExit:g,partName:u,headerRight:f,onPrev:x,onNext:v,canGoPrev:N,canGoNext:b,audioUrl:y,onAudioEnded:B,recorder:w,children:C,contentClassName:S="",hideAudioBar:k,onAudioPlayingChange:I}){return t("div",{id:"exam-main-scroll-area",className:r.pageContainer,children:[e("div",{className:r.examContainer,children:t("div",{className:r.examMainBoard,children:[e("div",{className:r.headerTitleWrapper,children:e("div",{className:r.exitButtonWrapper,children:t("div",{className:r.circleBtn,tabIndex:0,onClick:g,children:[e(a,{src:n,alt:"btn",className:r.circleBtnBg}),e("div",{className:r.circleBtnInner,children:e(a,{src:o,alt:"X",className:r.exitIcon})})]})})}),N&&e("div",{className:r.controlLeft,children:t("div",{className:r.circleBtn,onClick:x,children:[e(a,{src:n,alt:"btn",className:r.circleBtnBg}),e("div",{className:r.circleBtnInner,children:e(a,{src:c,alt:"prev",className:`${r.nextPrevIcon} ${r.rotate}`})})]})}),t("div",{className:r.examContentMainContainer,children:[!k&&e(i,{audioUrl:y,partName:u,onEnded:B,onPlayingChange:I}),t("div",{className:r.examContentSpace,children:[f&&e("div",{className:"absolute top-2 right-6 z-50",children:f}),e("div",{className:`w-full h-full flex flex-col items-center justify-center ${S}`,children:C})]}),w&&t(s,{children:[e("p",{className:"absolute left-1/2 -translate-x-1/2 text-center whitespace-nowrap text-black font-bold m-0",style:{bottom:"115px",fontSize:"16px",fontFamily:'"Open Sans", sans-serif'},children:"uploaded"===w.status?"Đã ghi nhận câu trả lời":"Ấn biểu tượng micro để trả lời"}),e("div",{className:`absolute left-1/2 -translate-x-1/2 flex flex-col items-center z-10 transition-transform ${"recording"===w.status?"scale-[1.05]":"hover:scale-[1.02] active:scale-[0.98]"} ${"uploaded"===w.status?"opacity-80 grayscale-[20%] pointer-events-none":""}`,style:{bottom:"-40px",overflow:"visible"},children:t("div",{className:"block relative",style:{overflow:"visible"},children:[e(a,{src:d,alt:"wrapper",className:"z-0 "+("recording"===w.status?"animate-pulse":""),style:{width:"158px",height:"116px"}}),"uploaded"!==w.status&&t("div",{className:"absolute left-1/2 -translate-x-1/2 z-20",style:{top:"-32px"},children:[e(a,{src:p,alt:"timer",style:{width:"104px",height:"45px"}}),e("div",{className:"absolute inset-0 flex items-center justify-center",children:t("p",{className:"text-white text-center text-[20px] font-normal m-0",style:{margin:0},children:[Math.floor(w.remainingSeconds/60),":",(w.remainingSeconds%60).toString().padStart(2,"0")]})})]}),e("div",{className:"absolute inset-0 flex justify-center items-center w-full h-full",children:e(a,{src:h,alt:"micro",className:"cursor-pointer "+("recording"===w.status?"drop-shadow-[0_0_15px_rgba(0,255,255,0.8)]":""),style:{width:"51px",height:"51px"},onClick:"recording"===w.status?w.stopRecording:"idle"===w.status||"error"===w.status?w.startRecording:void 0})})]})})]})]}),b&&e("div",{className:r.controlRight,children:t("div",{className:r.circleBtn,onClick:v,children:[e(a,{src:l,alt:"btn",className:r.circleBtnBg}),e("div",{className:r.circleBtnInner,children:e(a,{src:c,alt:"next",className:r.nextPrevIcon})})]})})]})}),e("div",{className:r.feedbackButton,children:e("a",{target:"_blank",href:"https://forms.gle/DxdZqfgSirYzf1vW8",rel:"noopener noreferrer",children:e(a,{alt:"feedback",src:m})})})]})}