@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

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 (514) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  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/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,448 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { ImagePreview } from '../../../../components/ui/image-preview';
4
- import { useState, useRef, useEffect, useMemo } from 'react';
5
- import { Button } from '../../../../components/ui/button';
6
- import { Label } from '../../../../components/ui/label';
7
- import { Input } from '../../../../components/ui/input';
8
- import { Textarea } from '../../../../components/ui/textarea';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
11
- import { Switch } from '../../../../components/ui/switch';
12
- import { Pencil, Eye, Lightbulb, Type, MessageSquare, ToggleLeft, Plus, Trash2, ListChecks, ImageIcon, BookOpen } from 'lucide-react';
13
- import { FillInBlankClient } from './FillInBlankClient';
14
- import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
15
- import { FileUpload } from '../../../../components/ui/file-upload';
16
- import { BasicQuestionGroupCard } from '../../groups/BasicQuestionGroupCard';
17
- import { useBasicQuestionGroup } from '../../_shared/hooks/useBasicQuestionGroup';
18
- // Helper: Extract placeholder indices from question text
19
- // e.g., "He {0} 10 years old and she {1} 12." -> [0, 1]
20
- function extractPlaceholderIndices(question) {
21
- const regex = /\{(\d+)\}/g;
22
- const indices = [];
23
- let match;
24
- while ((match = regex.exec(question)) !== null) {
25
- const index = parseInt(match[1], 10);
26
- if (!indices.includes(index)) {
27
- indices.push(index);
28
- }
29
- }
30
- return indices.sort((a, b) => a - b);
31
- }
32
- // Helper: Get the maximum placeholder index
33
- function getMaxPlaceholderIndex(question) {
34
- const indices = extractPlaceholderIndices(question);
35
- return indices.length > 0 ? Math.max(...indices) : -1;
36
- }
37
- // Helper: Determine which group a question belongs to based on its index in the part
38
- function getGroupNumberForIndex(questionIndexInPart, groups) {
39
- if (!groups || groups.length === 0 || questionIndexInPart === undefined)
40
- return 1;
41
- let cumulative = 0;
42
- for (const group of groups) {
43
- cumulative += group.questionCount;
44
- if (questionIndexInPart < cumulative) {
45
- return group.number;
46
- }
47
- }
48
- return groups[groups.length - 1]?.number ?? 1;
49
- }
50
- function FillInBlankCreatorContent({ initialData, onSave, onCancel, onChange, externalErrors, onUnsavedChangesChange, validationRef, groups, questionIndexInPart, partId, headerExtra, }) {
51
- // ============ GROUP (shared title / content / image) ============
52
- const { isGrouped, currentGroupNumber, groupTitle, groupSubtitle, groupContent, imageUrl, setGroupTitle, setGroupSubtitle, setGroupContent, setImageUrl, } = useBasicQuestionGroup({
53
- partId,
54
- groups,
55
- questionIndexInPart,
56
- initialData: {
57
- title: (() => {
58
- if (initialData?.content?.passageTitle)
59
- return initialData.content.passageTitle;
60
- const qgPayload = initialData?.questionGroup?.payload;
61
- if (qgPayload?.title)
62
- return qgPayload.title;
63
- if (initialData?.title)
64
- return initialData.title;
65
- return '';
66
- })(),
67
- subtitle: (() => {
68
- if (initialData?.content?.passageSubtitle)
69
- return initialData.content.passageSubtitle;
70
- const qgPayload = initialData?.questionGroup?.payload;
71
- if (qgPayload?.subtitle)
72
- return qgPayload.subtitle;
73
- return '';
74
- })(),
75
- content: (() => {
76
- if (initialData?.content?.passage)
77
- return initialData.content.passage;
78
- const qgPayload = initialData?.questionGroup?.payload;
79
- if (qgPayload?.content)
80
- return qgPayload.content;
81
- if (initialData?.groupContent)
82
- return initialData.groupContent;
83
- return '';
84
- })(),
85
- imageUrl: (() => {
86
- if (initialData?.content?.imageUrl)
87
- return initialData.content.imageUrl;
88
- const qgPayload = initialData?.questionGroup?.payload;
89
- if (qgPayload?.imageUrl)
90
- return qgPayload.imageUrl;
91
- if (initialData?.imageUrl)
92
- return initialData.imageUrl;
93
- return '';
94
- })(),
95
- },
96
- });
97
- // ============ STATE ============
98
- // Support NEW API format (content.question + correctAnswer.answers) and legacy formats
99
- const getInitialQuestion = () => {
100
- // NEW API format: content.question
101
- if (initialData?.content?.question)
102
- return initialData.content.question;
103
- // Legacy format: question at root level
104
- if (initialData?.question)
105
- return initialData.question;
106
- // Very old format: blanks[].text
107
- if (initialData?.blanks?.[0]?.text)
108
- return initialData.blanks[0].text;
109
- return '';
110
- };
111
- const getInitialAnswers = () => {
112
- // NEW API format: correctAnswer.answers
113
- if (initialData?.correctAnswer?.answers && Array.isArray(initialData.correctAnswer.answers)) {
114
- return initialData.correctAnswer.answers;
115
- }
116
- // Legacy format: answers at root level
117
- if (initialData?.answers && initialData.answers.length > 0) {
118
- return initialData.answers;
119
- }
120
- // Very old format: blanks[].correctAnswer
121
- if (initialData?.blanks?.[0]?.correctAnswer) {
122
- return [initialData.blanks[0].correctAnswer];
123
- }
124
- return [];
125
- };
126
- const getInitialCaseSensitive = () => {
127
- // NEW API format: correctAnswer.caseSensitive
128
- if (initialData?.correctAnswer?.caseSensitive !== undefined) {
129
- return initialData.correctAnswer.caseSensitive;
130
- }
131
- // Legacy format: caseSensitive at root level
132
- return initialData?.caseSensitive || false;
133
- };
134
- const getInitialShowHints = () => {
135
- // NEW API format: content.showHints
136
- if (initialData?.content?.showHints !== undefined) {
137
- return Boolean(initialData.content.showHints);
138
- }
139
- // Flat format from answer or root level
140
- if (initialData?.showHints !== undefined) {
141
- return Boolean(initialData.showHints);
142
- }
143
- // Default: false
144
- return false;
145
- };
146
- const getInitialHints = () => {
147
- // NEW API format: content.hints
148
- if (Array.isArray(initialData?.content?.hints)) {
149
- return initialData.content.hints;
150
- }
151
- // Flat format from answer or root level
152
- if (Array.isArray(initialData?.hints)) {
153
- return initialData.hints;
154
- }
155
- // Default: empty array
156
- return [];
157
- };
158
- const [question, setQuestion] = useState(getInitialQuestion());
159
- const [answers, setAnswers] = useState(getInitialAnswers());
160
- const [caseSensitive, setCaseSensitive] = useState(getInitialCaseSensitive());
161
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
162
- const [points, setPoints] = useState(initialData?.points || 1);
163
- const [showHints, setShowHints] = useState(getInitialShowHints());
164
- const [hints, setHints] = useState(getInitialHints());
165
- const [newHint, setNewHint] = useState('');
166
- const [isExample, setIsExample] = useState(initialData?.isExample || false);
167
- // UI State
168
- const [isClientMode, setIsClientMode] = useState(false);
169
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
170
- // Presigned URL for per-question image preview (non-grouped only)
171
- const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(imageUrl);
172
- const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
173
- // Inline validation errors
174
- const [validationErrors, setValidationErrors] = useState({});
175
- // Refs for initialization tracking
176
- const previousInitialDataRef = useRef(initialData);
177
- const isInitializingRef = useRef(false);
178
- const onChangeRef = useRef(onChange);
179
- const previousPayloadRef = useRef('');
180
- // NOTE: Status tracking is now handled entirely by ExamCreator via localStorage (exam_question_status_{id})
181
- // The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
182
- // Update refs
183
- useEffect(() => {
184
- onChangeRef.current = onChange;
185
- }, [onChange]);
186
- // Debounced onChange callback
187
- const debouncedOnChange = useDebouncedCallback((data) => {
188
- onChangeRef.current?.(data);
189
- }, 300);
190
- // Extract placeholder indices from question
191
- const placeholderIndices = useMemo(() => extractPlaceholderIndices(question), [question]);
192
- const maxIndex = useMemo(() => getMaxPlaceholderIndex(question), [question]);
193
- // Ensure answers array has correct length when placeholders change
194
- useEffect(() => {
195
- if (maxIndex >= 0) {
196
- const requiredLength = maxIndex + 1;
197
- if (answers.length < requiredLength) {
198
- // Extend array with empty strings
199
- const newAnswers = [...answers];
200
- while (newAnswers.length < requiredLength) {
201
- newAnswers.push('');
202
- }
203
- setAnswers(newAnswers);
204
- }
205
- }
206
- }, [maxIndex, answers.length]);
207
- // Expose validation function via ref
208
- useEffect(() => {
209
- if (validationRef) {
210
- validationRef.current = {
211
- triggerValidation: async () => {
212
- return validateForm();
213
- },
214
- // BUGFIX: Expose getFormData to get fresh form data at save time
215
- // This bypasses the debounced onChange race condition
216
- getFormData: () => ({
217
- question,
218
- answers,
219
- caseSensitive,
220
- explanation,
221
- points,
222
- imageUrl,
223
- showHints,
224
- hints: showHints ? hints : [],
225
- isExample,
226
- title: groupTitle,
227
- subtitle: groupSubtitle,
228
- groupContent,
229
- groupNumber: currentGroupNumber,
230
- groups,
231
- }),
232
- };
233
- }
234
- }, [validationRef, question, answers, caseSensitive, explanation, points, imageUrl, showHints, hints, groupTitle, groupSubtitle, groupContent, currentGroupNumber, groups]);
235
- // ============ SYNC INITIAL DATA ============
236
- useEffect(() => {
237
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
238
- if (initialDataChanged) {
239
- isInitializingRef.current = true;
240
- previousInitialDataRef.current = initialData;
241
- if (initialData) {
242
- setQuestion(getInitialQuestion());
243
- setAnswers(getInitialAnswers());
244
- setCaseSensitive(getInitialCaseSensitive());
245
- // Explanation can be at root level or in initialData
246
- setExplanation(initialData.explanation || '');
247
- // Points can be at root level
248
- setPoints(initialData.points || 1);
249
- setShowHints(getInitialShowHints());
250
- setHints(getInitialHints());
251
- setIsExample(initialData.isExample || false);
252
- }
253
- else {
254
- setQuestion('');
255
- setAnswers([]);
256
- setCaseSensitive(false);
257
- setExplanation('');
258
- setPoints(1);
259
- setShowHints(false);
260
- setHints([]);
261
- setIsExample(false);
262
- }
263
- setTimeout(() => {
264
- isInitializingRef.current = false;
265
- }, 150);
266
- }
267
- }, [initialData]);
268
- // ============ ON CHANGE CALLBACK ============
269
- useEffect(() => {
270
- if (isInitializingRef.current) {
271
- const currentPayload = JSON.stringify({
272
- question,
273
- answers,
274
- caseSensitive,
275
- explanation,
276
- points,
277
- imageUrl,
278
- showHints,
279
- hints,
280
- isExample,
281
- title: groupTitle,
282
- subtitle: groupSubtitle,
283
- groupContent,
284
- groupNumber: currentGroupNumber,
285
- groups,
286
- });
287
- previousPayloadRef.current = currentPayload;
288
- return;
289
- }
290
- const payload = {
291
- question,
292
- answers,
293
- caseSensitive,
294
- explanation,
295
- points,
296
- imageUrl,
297
- showHints,
298
- hints: showHints ? hints : [],
299
- isExample,
300
- title: groupTitle,
301
- subtitle: groupSubtitle,
302
- groupContent,
303
- groupNumber: currentGroupNumber,
304
- groups,
305
- };
306
- const payloadString = JSON.stringify(payload);
307
- if (previousPayloadRef.current !== payloadString) {
308
- previousPayloadRef.current = payloadString;
309
- debouncedOnChange(payload);
310
- }
311
- }, [question, answers, caseSensitive, explanation, points, imageUrl, showHints, hints, isExample, groupTitle, groupSubtitle, groupContent, currentGroupNumber, groups]);
312
- // ============ VALIDATION ============
313
- const validateForm = () => {
314
- const errors = {};
315
- if (!question.trim()) {
316
- errors.question = 'Vui lòng nhập câu hỏi!';
317
- }
318
- else if (placeholderIndices.length === 0) {
319
- errors.question = 'Câu hỏi cần có ít nhất một chỗ trống {0}!';
320
- }
321
- // Check if placeholder indices are sequential starting from 0
322
- const expectedIndices = Array.from({ length: placeholderIndices.length }, (_, i) => i);
323
- const hasGaps = !placeholderIndices.every((idx, i) => idx === expectedIndices[i]);
324
- if (hasGaps && placeholderIndices.length > 0) {
325
- errors.question = 'Các chỗ trống phải đánh số liên tục từ {0}!';
326
- }
327
- // Validate each answer - use placeholderIndices or default [0]
328
- errors.answers = {};
329
- const indicesToValidate = placeholderIndices.length > 0 ? placeholderIndices : [0];
330
- indicesToValidate.forEach((idx) => {
331
- if (!answers[idx] || !answers[idx].trim()) {
332
- errors.answers[idx] = 'Vui lòng nhập đáp án!';
333
- }
334
- });
335
- // Clean up empty answers object
336
- if (Object.keys(errors.answers).length === 0) {
337
- delete errors.answers;
338
- }
339
- setValidationErrors(errors);
340
- return !errors.question && !errors.answers;
341
- };
342
- // Handle save
343
- const handleSave = () => {
344
- if (!validateForm()) {
345
- return;
346
- }
347
- // NOTE: Save status is now managed by ExamCreator via localStorage
348
- const saveData = {
349
- question,
350
- answers,
351
- caseSensitive,
352
- explanation,
353
- points,
354
- imageUrl,
355
- showHints,
356
- hints: showHints ? hints : [],
357
- isExample,
358
- title: groupTitle,
359
- subtitle: groupSubtitle,
360
- groupContent,
361
- groupNumber: currentGroupNumber,
362
- groups,
363
- };
364
- onSave?.(saveData);
365
- };
366
- // Update answer at specific index
367
- const updateAnswer = (index, value) => {
368
- const newAnswers = [...answers];
369
- // Ensure array is long enough
370
- while (newAnswers.length <= index) {
371
- newAnswers.push('');
372
- }
373
- newAnswers[index] = value;
374
- setAnswers(newAnswers);
375
- // Clear validation error for this answer
376
- if (validationErrors.answers?.[index]) {
377
- setValidationErrors(prev => {
378
- const newAnswerErrors = { ...prev.answers };
379
- delete newAnswerErrors[index];
380
- return {
381
- ...prev,
382
- answers: Object.keys(newAnswerErrors).length > 0 ? newAnswerErrors : undefined,
383
- };
384
- });
385
- }
386
- };
387
- // Generate preview text with answers filled in
388
- const getPreviewText = () => {
389
- let preview = question;
390
- placeholderIndices.forEach((idx) => {
391
- const answer = answers[idx] || '_____';
392
- preview = preview.replace(`{${idx}}`, `[${answer}]`);
393
- });
394
- return preview;
395
- };
396
- // ============ CLIENT MODE PREVIEW ============
397
- if (isClientMode) {
398
- const questionData = {
399
- question,
400
- answers,
401
- caseSensitive,
402
- explanation,
403
- imageUrl: isGrouped ? '' : (displayPreviewUrl || imageUrl),
404
- showHints,
405
- hints: showHints ? hints : [],
406
- isExample,
407
- // Group fields for Client preview
408
- isGroup: isGrouped,
409
- groupTitle: isGrouped ? groupTitle : '',
410
- groupSubtitle: isGrouped ? groupSubtitle : '',
411
- groupContent: isGrouped ? groupContent : '',
412
- groupImageUrl: isGrouped ? (displayPreviewUrl || imageUrl) : '',
413
- groupNumber: isGrouped ? currentGroupNumber : undefined,
414
- };
415
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { children: "Fill In Blank (Preview)" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(FillInBlankClient, { questionData: questionData, autoFillCorrectAnswers: true, isReviewMode: false, explanation: explanation })] }));
416
- }
417
- // ============ CREATOR UI ============
418
- return (_jsxs("div", { className: "space-y-4", children: [isGrouped && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: imageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setImageUrl, uploadIdPrefix: `fill-in-blank-${partId ?? 'group'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm", children: _jsx(Type, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng" }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u1ED7 tr\u1EE3 nhi\u1EC1u ch\u1ED7 tr\u1ED1ng trong m\u1ED9t c\u00E2u h\u1ECFi" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !question?.trim() || placeholderIndices.length === 0, className: "gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-4 px-4 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx(PointsInput, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "show-suggested-words", checked: showHints, onCheckedChange: setShowHints }), _jsx(Label, { htmlFor: "show-suggested-words", className: "cursor-pointer text-sm font-medium text-gray-700", children: "Hi\u1EC3n th\u1ECB t\u1EEB g\u1EE3i \u00FD" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "isExample", checked: isExample, onCheckedChange: setIsExample }), _jsx(Label, { htmlFor: "isExample", className: "cursor-pointer text-sm font-medium text-gray-700", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 c\u00E2u h\u1ECFi Example" })] })] }), _jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-blue-600" }) }), _jsxs("div", { className: "text-sm text-blue-900", children: [_jsx("p", { className: "font-semibold text-blue-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "S\u1EED d\u1EE5ng ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{0}' }), ", ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{1}' }), ", ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{2}' }), "... \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u c\u00E1c ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "H\u1EC7 th\u1ED1ng s\u1EBD t\u1EF1 \u0111\u1ED9ng t\u1EA1o \u00F4 nh\u1EADp \u0111\u00E1p \u00E1n cho m\u1ED7i ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "V\u00ED d\u1EE5: \u201CHe ", '{0}', " 10 years old and she ", '{1}', " 12.\u201D"] })] })] })] }) }), !isGrouped && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(T\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "fill-in-blank-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => {
419
- setImageUrl(url);
420
- }, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", value: question, onChange: (e) => {
421
- setQuestion(e.target.value);
422
- if (validationErrors.question) {
423
- setValidationErrors(prev => ({ ...prev, question: undefined }));
424
- }
425
- }, placeholder: "V\u00ED d\u1EE5: He {0} 10 years old and she {1} 12.", rows: 3, className: `min-h-[100px] border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${validationErrors.question ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question })), placeholderIndices.length > 0 && (_jsx("div", { className: "flex items-center gap-2 rounded-lg bg-green-50 px-3 py-2", children: _jsxs("span", { className: "text-sm text-green-700", children: ["\u2713 \u0110\u00E3 ph\u00E1t hi\u1EC7n ", _jsx("strong", { children: placeholderIndices.length }), " ch\u1ED7 tr\u1ED1ng: ", placeholderIndices.map(i => (_jsx("code", { className: "mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-mono text-green-800", children: `{${i}}` }, i)))] }) }))] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Type, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n cho c\u00E1c ch\u1ED7 tr\u1ED1ng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-2", children: [_jsx("div", { className: "space-y-3", children: (placeholderIndices.length > 0 ? placeholderIndices : [0]).map((idx) => (_jsxs("div", { className: `rounded-xl border-2 p-4 transition-all ${answers[idx]
426
- ? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
427
- : 'border-gray-200 bg-white'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl font-mono font-bold transition-all ${answers[idx]
428
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
429
- : 'bg-indigo-100 text-indigo-700'}`, children: `{${idx}}` }), _jsx(Input, { value: answers[idx] || '', onChange: (e) => updateAnswer(idx, e.target.value), placeholder: `Đáp án cho {${idx}}`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${validationErrors.answers?.[idx] ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` })] }), validationErrors.answers?.[idx] && (_jsx("p", { className: "mt-2 ml-13 text-sm text-red-600", children: validationErrors.answers[idx] }))] }, idx))) }), showHints && (_jsxs("div", { className: "space-y-4 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["Danh s\u00E1ch t\u1EEB g\u1EE3i \u00FD ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", hints.length, " t\u1EEB)"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: newHint, onChange: (e) => setNewHint(e.target.value), placeholder: "Nh\u1EADp t\u1EEB g\u1EE3i \u00FD m\u1EDBi...", className: "flex-1 border-gray-200 bg-white", onKeyDown: (e) => {
430
- if (e.key === 'Enter' && newHint.trim()) {
431
- e.preventDefault();
432
- if (!hints.includes(newHint.trim())) {
433
- setHints([...hints, newHint.trim()]);
434
- }
435
- setNewHint('');
436
- }
437
- } }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
438
- if (newHint.trim() && !hints.includes(newHint.trim())) {
439
- setHints([...hints, newHint.trim()]);
440
- setNewHint('');
441
- }
442
- }, disabled: !newHint.trim(), className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm"] })] }), hints.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: hints.map((word, index) => (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-purple-200 bg-white px-3 py-2 shadow-sm transition-all hover:border-purple-300 hover:shadow", children: [_jsx("span", { className: "text-sm font-medium text-gray-700", children: word }), _jsx("button", { type: "button", onClick: () => {
443
- setHints(hints.filter((_, i) => i !== index));
444
- }, className: "rounded-full p-0.5 text-gray-400 transition-colors hover:bg-red-100 hover:text-red-500", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, index))) })) : (_jsx("p", { className: "text-center text-sm text-gray-500 italic", children: "Ch\u01B0a c\u00F3 t\u1EEB g\u1EE3i \u00FD n\u00E0o. Th\u00EAm t\u1EEB \u0111\u1EC3 hi\u1EC3n th\u1ECB cho h\u1ECDc sinh." }))] }))] })] }), question && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 text-xs font-semibold text-gray-600", children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc c\u00E2u ho\u00E0n ch\u1EC9nh"] }), _jsx("p", { className: "text-sm text-gray-700", children: getPreviewText() })] })), _jsxs("div", { className: "flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(ToggleLeft, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { htmlFor: "case-sensitive", className: "font-semibold text-gray-800", children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" }), _jsx("p", { className: "text-sm text-gray-500", children: "B\u1EADt n\u1EBFu \u0111\u00E1p \u00E1n c\u1EA7n ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }), _jsx(Switch, { id: "case-sensitive", checked: caseSensitive, onCheckedChange: setCaseSensitive })] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { className: "px-4 pb-4", children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (e) => setExplanation(e.target.value), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
445
- }
446
- export function FillInBlankCreator(props) {
447
- return _jsx(FillInBlankCreatorContent, { ...props });
448
- }
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{ImagePreview as n}from"../../../../components/ui/image-preview";import{useState as r,useRef as s,useEffect as i,useMemo as a}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as c}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{PointsInput as m}from"../../../../components/ui/points-input";import{Card as h,CardContent as g,CardHeader as u,CardTitle as p}from"../../../../components/ui/card";import{Switch as x}from"../../../../components/ui/switch";import{Pencil as b,Eye as f,Lightbulb as N,Type as w,MessageSquare as v,ToggleLeft as y,Plus as C,Trash2 as k,ListChecks as q,ImageIcon as U,BookOpen as S}from"lucide-react";import{FillInBlankClient as j}from"./FillInBlankClient";import{useDebouncedCallback as A,usePresignedFileUrl as E}from"../../../../shared/lib/hooks";import{FileUpload as H}from"../../../../components/ui/file-upload";import{BasicQuestionGroupCard as G}from"../../groups/BasicQuestionGroupCard";import{useBasicQuestionGroup as I}from"../../_shared/hooks/useBasicQuestionGroup";function T(e){const t=/\{(\d+)\}/g,n=[];let r;for(;null!==(r=t.exec(e));){const e=parseInt(r[1],10);n.includes(e)||n.push(e)}return n.sort((e,t)=>e-t)}function F({initialData:F,onSave:B,onCancel:D,onChange:P,externalErrors:V,onUnsavedChangesChange:O,validationRef:$,groups:_,questionIndexInPart:z,partId:J,headerExtra:M}){const{isGrouped:Q,currentGroupNumber:R,groupTitle:K,groupSubtitle:L,groupContent:X,imageUrl:W,setGroupTitle:Y,setGroupSubtitle:Z,setGroupContent:ee,setImageUrl:te}=I({partId:J,groups:_,questionIndexInPart:z,initialData:{title:(()=>{if(F?.content?.passageTitle)return F.content.passageTitle;const e=F?.questionGroup?.payload;return e?.title?e.title:F?.title?F.title:""})(),subtitle:(()=>{if(F?.content?.passageSubtitle)return F.content.passageSubtitle;const e=F?.questionGroup?.payload;return e?.subtitle?e.subtitle:""})(),content:(()=>{if(F?.content?.passage)return F.content.passage;const e=F?.questionGroup?.payload;return e?.content?e.content:F?.groupContent?F.groupContent:""})(),imageUrl:(()=>{if(F?.content?.imageUrl)return F.content.imageUrl;const e=F?.questionGroup?.payload;return e?.imageUrl?e.imageUrl:F?.imageUrl?F.imageUrl:""})()}}),ne=()=>F?.content?.question?F.content.question:F?.question?F.question:F?.blanks?.[0]?.text?F.blanks[0].text:"",re=()=>F?.correctAnswer?.answers&&Array.isArray(F.correctAnswer.answers)?F.correctAnswer.answers:F?.answers&&F.answers.length>0?F.answers:F?.blanks?.[0]?.correctAnswer?[F.blanks[0].correctAnswer]:[],se=()=>void 0!==F?.correctAnswer?.caseSensitive?F.correctAnswer.caseSensitive:F?.caseSensitive||!1,ie=()=>void 0!==F?.content?.showHints?Boolean(F.content.showHints):void 0!==F?.showHints&&Boolean(F.showHints),ae=()=>Array.isArray(F?.content?.hints)?F.content.hints:Array.isArray(F?.hints)?F.hints:[],[le,oe]=r(ne()),[ce,de]=r(re()),[me,he]=r(se()),[ge,ue]=r(F?.explanation||""),[pe,xe]=r(F?.points||1),[be,fe]=r(ie()),[Ne,we]=r(ae()),[ve,ye]=r(""),[Ce,ke]=r(F?.isExample||!1),[qe,Ue]=r(!1),[Se,je]=r(""),{previewUrl:Ae}=E(W),Ee=Se||Ae||"",[He,Ge]=r({}),Ie=s(F),Te=s(!1),Fe=s(P),Be=s("");i(()=>{Fe.current=P},[P]);const De=A(e=>{Fe.current?.(e)},300),Pe=a(()=>T(le),[le]),Ve=a(()=>function(e){const t=T(e);return t.length>0?Math.max(...t):-1}(le),[le]);i(()=>{if(Ve>=0){const e=Ve+1;if(ce.length<e){const t=[...ce];for(;t.length<e;)t.push("");de(t)}}},[Ve,ce.length]),i(()=>{$&&($.current={triggerValidation:async()=>Oe(),getFormData:()=>({question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:be?Ne:[],isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_})})},[$,le,ce,me,ge,pe,W,be,Ne,K,L,X,R,_]),i(()=>{JSON.stringify(Ie.current)!==JSON.stringify(F)&&(Te.current=!0,Ie.current=F,F?(oe(ne()),de(re()),he(se()),ue(F.explanation||""),xe(F.points||1),fe(ie()),we(ae()),ke(F.isExample||!1)):(oe(""),de([]),he(!1),ue(""),xe(1),fe(!1),we([]),ke(!1)),setTimeout(()=>{Te.current=!1},150))},[F]),i(()=>{if(Te.current){const e=JSON.stringify({question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:Ne,isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_});return void(Be.current=e)}const e={question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:be?Ne:[],isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_},t=JSON.stringify(e);Be.current!==t&&(Be.current=t,De(e))},[le,ce,me,ge,pe,W,be,Ne,Ce,K,L,X,R,_]);const Oe=()=>{const e={};le.trim()?0===Pe.length&&(e.question="Câu hỏi cần có ít nhất một chỗ trống {0}!"):e.question="Vui lòng nhập câu hỏi!";const t=Array.from({length:Pe.length},(e,t)=>t);!Pe.every((e,n)=>e===t[n])&&Pe.length>0&&(e.question="Các chỗ trống phải đánh số liên tục từ {0}!"),e.answers={};return(Pe.length>0?Pe:[0]).forEach(t=>{ce[t]&&ce[t].trim()||(e.answers[t]="Vui lòng nhập đáp án!")}),0===Object.keys(e.answers).length&&delete e.answers,Ge(e),!e.question&&!e.answers};if(qe){const n={question:le,answers:ce,caseSensitive:me,explanation:ge,imageUrl:Q?"":Ee||W,showHints:be,hints:be?Ne:[],isExample:Ce,isGroup:Q,groupTitle:Q?K:"",groupSubtitle:Q?L:"",groupContent:Q?X:"",groupImageUrl:Q?Ee||W:"",groupNumber:Q?R:void 0};return t("div",{className:"space-y-4",children:[e(h,{children:e(u,{children:t("div",{className:"flex items-center justify-between",children:[e(p,{children:"Fill In Blank (Preview)"}),t(l,{variant:"outline",size:"sm",onClick:()=>Ue(!1),children:[e(b,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(j,{questionData:n,autoFillCorrectAnswers:!0,isReviewMode:!1,explanation:ge})]})}return t("div",{className:"space-y-4",children:[Q&&e(G,{groupNumber:R,title:K,subtitle:L,content:X,imageUrl:W,onTitleChange:Y,onSubtitleChange:Z,onContentChange:ee,onImageChange:te,uploadIdPrefix:`fill-in-blank-${J??"group"}`}),t(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e(u,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm",children:e(w,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(p,{className:"text-base font-bold text-gray-900",children:"Điền vào chỗ trống"}),e("p",{className:"text-xs text-gray-500",children:"Hỗ trợ nhiều chỗ trống trong một câu hỏi"})]})]}),t("div",{className:"flex items-center gap-2",children:[M,t(l,{variant:"outline",size:"sm",onClick:()=>Ue(!0),disabled:!le?.trim()||0===Pe.length,className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(f,{className:"h-4 w-4"}),"Xem trước"]})]})]})}),t(g,{className:"space-y-4 px-4 pb-4",children:[t("div",{className:"flex flex-wrap items-center gap-6",children:[e(m,{defaultValue:pe,onChange:e=>xe(Number(e.target.value)||1)}),t("div",{className:"flex items-center gap-2",children:[e(x,{id:"show-suggested-words",checked:be,onCheckedChange:fe}),e(o,{htmlFor:"show-suggested-words",className:"cursor-pointer text-sm font-medium text-gray-700",children:"Hiển thị từ gợi ý"})]}),t("div",{className:"flex items-center gap-2",children:[e(x,{id:"isExample",checked:Ce,onCheckedChange:ke}),e(o,{htmlFor:"isExample",className:"cursor-pointer text-sm font-medium text-gray-700",children:"Đánh dấu là câu hỏi Example"})]})]}),e("div",{className:"rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm",children:t("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100",children:e(N,{className:"h-4 w-4 text-blue-600"})}),t("div",{className:"text-sm text-blue-900",children:[e("p",{className:"font-semibold text-blue-800",children:"Hướng dẫn tạo câu hỏi"}),t("ul",{className:"mt-2 space-y-1.5 text-blue-700",children:[t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Sử dụng ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{0}"}),", ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{1}"}),", ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{2}"}),"... để đánh dấu các chỗ trống"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Hệ thống sẽ tự động tạo ô nhập đáp án cho mỗi chỗ trống"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Ví dụ: “He ","{0}"," 10 years old and she ","{1}"," 12.”"]})]})]})]})}),!Q&&t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(U,{className:"h-4 w-4 text-indigo-500"}),"Hình ảnh ",e("span",{className:"text-sm font-normal text-gray-500",children:"(Tùy chọn)"})]}),e(H,{id:"fill-in-blank-image",label:"",accept:"image/*",value:W||"",onChange:e=>{te(e)},onPresignedUrlChange:je,maxSize:5,placeholder:"Upload ảnh hoặc paste URL",autoUpload:!0,prefix:"questions"}),Ee&&e(n,{src:Ee,alt:"Preview",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]}),t("div",{className:"space-y-2",children:[t(o,{htmlFor:"question",className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(v,{className:"h-4 w-4 text-indigo-500"}),"Câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(d,{id:"question",value:le,onChange:e=>{oe(e.target.value),He.question&&Ge(e=>({...e,question:void 0}))},placeholder:"Ví dụ: He {0} 10 years old and she {1} 12.",rows:3,className:"min-h-[100px] border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(He.question?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),He.question&&e("p",{className:"text-sm text-red-600",children:He.question}),Pe.length>0&&e("div",{className:"flex items-center gap-2 rounded-lg bg-green-50 px-3 py-2",children:t("span",{className:"text-sm text-green-700",children:["✓ Đã phát hiện ",e("strong",{children:Pe.length})," chỗ trống: ",Pe.map(t=>e("code",{className:"mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-mono text-green-800",children:`{${t}}`},t))]})})]}),t("div",{className:"space-y-4",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(w,{className:"h-3.5 w-3.5 text-indigo-600"})}),t(o,{className:"text-base font-semibold text-gray-800",children:["Đáp án cho các chỗ trống ",e("span",{className:"text-red-500",children:"*"})]})]}),t("div",{className:"grid gap-6 lg:grid-cols-2",children:[e("div",{className:"space-y-3",children:(Pe.length>0?Pe:[0]).map(n=>t("div",{className:"rounded-xl border-2 p-4 transition-all "+(ce[n]?"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50":"border-gray-200 bg-white"),children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl font-mono font-bold transition-all "+(ce[n]?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-indigo-100 text-indigo-700"),children:`{${n}}`}),e(c,{value:ce[n]||"",onChange:e=>((e,t)=>{const n=[...ce];for(;n.length<=e;)n.push("");n[e]=t,de(n),He.answers?.[e]&&Ge(t=>{const n={...t.answers};return delete n[e],{...t,answers:Object.keys(n).length>0?n:void 0}})})(n,e.target.value),placeholder:`Đáp án cho {${n}}`,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(He.answers?.[n]?"border-red-400 focus:border-red-500 focus:ring-red-200":"")})]}),He.answers?.[n]&&e("p",{className:"mt-2 ml-13 text-sm text-red-600",children:He.answers[n]})]},n))}),be&&t("div",{className:"space-y-4 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-purple-100",children:e(q,{className:"h-3.5 w-3.5 text-purple-600"})}),t(o,{className:"text-base font-semibold text-gray-800",children:["Danh sách từ gợi ý ",t("span",{className:"text-sm font-normal text-gray-500",children:["(",Ne.length," từ)"]})]})]}),t("div",{className:"flex gap-2",children:[e(c,{value:ve,onChange:e=>ye(e.target.value),placeholder:"Nhập từ gợi ý mới...",className:"flex-1 border-gray-200 bg-white",onKeyDown:e=>{"Enter"===e.key&&ve.trim()&&(e.preventDefault(),Ne.includes(ve.trim())||we([...Ne,ve.trim()]),ye(""))}}),t(l,{type:"button",variant:"outline",size:"sm",onClick:()=>{ve.trim()&&!Ne.includes(ve.trim())&&(we([...Ne,ve.trim()]),ye(""))},disabled:!ve.trim(),className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(C,{className:"h-4 w-4"}),"Thêm"]})]}),Ne.length>0?e("div",{className:"flex flex-wrap gap-2",children:Ne.map((n,r)=>t("div",{className:"flex items-center gap-2 rounded-lg border border-purple-200 bg-white px-3 py-2 shadow-sm transition-all hover:border-purple-300 hover:shadow",children:[e("span",{className:"text-sm font-medium text-gray-700",children:n}),e("button",{type:"button",onClick:()=>{we(Ne.filter((e,t)=>t!==r))},className:"rounded-full p-0.5 text-gray-400 transition-colors hover:bg-red-100 hover:text-red-500",children:e(k,{className:"h-3.5 w-3.5"})})]},r))}):e("p",{className:"text-center text-sm text-gray-500 italic",children:"Chưa có từ gợi ý nào. Thêm từ để hiển thị cho học sinh."})]})]})]}),le&&t("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:[t("div",{className:"mb-2 flex items-center gap-2 text-xs font-semibold text-gray-600",children:[e(f,{className:"h-3.5 w-3.5"}),"Xem trước câu hoàn chỉnh"]}),e("p",{className:"text-sm text-gray-700",children:(()=>{let e=le;return Pe.forEach(t=>{const n=ce[t]||"_____";e=e.replace(`{${t}}`,`[${n}]`)}),e})()})]}),t("div",{className:"flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100",children:e(y,{className:"h-4 w-4 text-gray-600"})}),t("div",{className:"space-y-0.5",children:[e(o,{htmlFor:"case-sensitive",className:"font-semibold text-gray-800",children:"Phân biệt hoa/thường"}),e("p",{className:"text-sm text-gray-500",children:"Bật nếu đáp án cần phân biệt chữ hoa/thường"})]})]}),e(x,{id:"case-sensitive",checked:me,onCheckedChange:he})]}),V&&V.length>0&&t("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100",children:e("span",{className:"text-lg",children:"❌"})}),t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Lỗi:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:V.map((t,n)=>e("li",{children:t},n))})]})]})]})]}),t(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e(u,{className:"px-4 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(S,{className:"h-3.5 w-3.5 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(p,{className:"text-base font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-xs text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(g,{className:"px-4 pb-4",children:e(d,{id:"explanation",value:ge,onChange:e=>ue(e.target.value),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function FillInBlankCreator(t){return e(F,{...t})}
@@ -1,2 +1 @@
1
- export * from './FillInBlankCreator';
2
- export * from './FillInBlankClient';
1
+ export*from"./FillInBlankCreator";export*from"./FillInBlankClient";