@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,420 +1 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useRef, useCallback, useEffect } from 'react';
4
- import { Button } from '../../../components/ui/button';
5
- import { Label } from '../../../components/ui/label';
6
- import { Card, CardContent, CardHeader, CardTitle } from '../../../components/ui/card';
7
- import { PointsInput } from '../../../components/ui/points-input';
8
- import { X, Pencil, Eye, Plus, Trash2, MessageSquare, Palette, Lightbulb, Image } from 'lucide-react';
9
- import { Input } from '../../../components/ui/input';
10
- import { Textarea } from '../../../components/ui/textarea';
11
- import { FileUpload } from '../../../components/ui/file-upload';
12
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../components/ui/select';
13
- import { ColorRegionNode } from './ColorRegionNode';
14
- import { usePresignedFileUrl } from '../../../shared/lib/hooks';
15
- import { COLOR_PALETTE } from '../_shared/constants/color-palette';
16
- import { useReactHookForm } from '../../../shared/lib/hooks/useReactHookForm';
17
- import { readAndColorObjectsFormSchema, } from '../../../shared/lib/validation/read-and-color-objects.validation';
18
- function ColorRegionCreatorContent({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange, validationRef, config, ClientComponent, }) {
19
- // State
20
- const [backgroundImageUrl, setBackgroundImageUrl] = useState(initialData?.backgroundImageUrl || '');
21
- const [colorNodes, setColorNodes] = useState(initialData?.colorNodes || []);
22
- const [regionNodes, setRegionNodes] = useState(initialData?.regionNodes || []);
23
- // Instructions state
24
- const [instructions, setInstructions] = useState(Array.isArray(initialData?.instructions) ? initialData.instructions : []);
25
- // Add node state - selected color from palette
26
- const [selectedColor, setSelectedColor] = useState('');
27
- const [colorLabelValue, setColorLabelValue] = useState('');
28
- const [showAddColorPanel, setShowAddColorPanel] = useState(false);
29
- // Whether to show label input alongside color picker
30
- const showColorLabel = config.showColorLabel || false;
31
- // Points state
32
- const [points, setPoints] = useState(initialData?.points || 1);
33
- // Explanation state
34
- const [explanation, setExplanation] = useState(initialData?.explanation || '');
35
- // Use react-hook-form with Zod validation
36
- const form = useReactHookForm({
37
- defaultValues: {
38
- backgroundImageUrl: backgroundImageUrl,
39
- colorNodes: colorNodes,
40
- regionNodes: regionNodes,
41
- instructions: instructions,
42
- },
43
- zodSchema: readAndColorObjectsFormSchema,
44
- mode: 'onBlur',
45
- });
46
- // Track previous initialData to detect changes
47
- const previousInitialDataRef = useRef(initialData);
48
- const isInitializingRef = useRef(true); // Start as true to prevent onChange on first render
49
- const hasInitializedRef = useRef(false); // Track if we've initialized from initialData
50
- // NOTE: Status tracking is now handled entirely by ExamCreator via localStorage (exam_question_status_{id})
51
- // The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
52
- // Expose validation function via ref
53
- useEffect(() => {
54
- if (validationRef) {
55
- validationRef.current = {
56
- triggerValidation: async () => {
57
- return validateForm();
58
- },
59
- // Return current form data immediately (not debounced)
60
- getFormData: () => ({
61
- backgroundImageUrl,
62
- colorNodes,
63
- regionNodes,
64
- instructions,
65
- points,
66
- explanation,
67
- }),
68
- };
69
- }
70
- }, [validationRef, backgroundImageUrl, colorNodes, regionNodes, instructions, points, explanation]);
71
- // Use custom hooks for presigned URLs
72
- const { previewUrl: backgroundImagePreviewUrl } = usePresignedFileUrl(backgroundImageUrl);
73
- const [backgroundPreviewUrl, setBackgroundPreviewUrl] = useState('');
74
- // Sync presigned background URL → backgroundPreviewUrl (matches LACOIS pattern)
75
- useEffect(() => {
76
- if (backgroundImagePreviewUrl)
77
- setBackgroundPreviewUrl(backgroundImagePreviewUrl);
78
- }, [backgroundImagePreviewUrl]);
79
- // Sync state from initialData when it changes (e.g., when switching back to this question)
80
- useEffect(() => {
81
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
82
- if (initialDataChanged || !hasInitializedRef.current) {
83
- isInitializingRef.current = true;
84
- previousInitialDataRef.current = initialData;
85
- hasInitializedRef.current = true;
86
- // Update all state from initialData (handle both defined and undefined cases)
87
- if (initialData) {
88
- if (initialData.backgroundImageUrl !== undefined) {
89
- setBackgroundImageUrl(initialData.backgroundImageUrl);
90
- }
91
- if (initialData.colorNodes !== undefined) {
92
- setColorNodes(initialData.colorNodes);
93
- }
94
- if (initialData.regionNodes !== undefined) {
95
- setRegionNodes(initialData.regionNodes);
96
- }
97
- if (initialData.instructions !== undefined) {
98
- setInstructions(Array.isArray(initialData.instructions) ? initialData.instructions : []);
99
- }
100
- if (initialData.points !== undefined) {
101
- setPoints(initialData.points);
102
- }
103
- if (initialData.explanation !== undefined) {
104
- setExplanation(initialData.explanation);
105
- }
106
- }
107
- else {
108
- // Reset to empty state if initialData is undefined
109
- setBackgroundImageUrl('');
110
- setColorNodes([]);
111
- setRegionNodes([]);
112
- setInstructions([]);
113
- setPoints(1);
114
- setExplanation('');
115
- }
116
- // Reset flag after state updates
117
- setTimeout(() => {
118
- isInitializingRef.current = false;
119
- }, 150); // Increased timeout to ensure state is fully updated
120
- }
121
- }, [initialData]);
122
- // Store onChange callback in ref to avoid dependency issues
123
- const onChangeRef = useRef(onChange);
124
- useEffect(() => {
125
- onChangeRef.current = onChange;
126
- }, [onChange]);
127
- // Track previous values to prevent unnecessary onChange calls
128
- const previousPayloadRef = useRef('');
129
- useEffect(() => {
130
- // Skip onChange during initialization to prevent infinite loop
131
- if (isInitializingRef.current || !hasInitializedRef.current) {
132
- // Still update previous payload ref to track current state
133
- const currentPayload = JSON.stringify({
134
- backgroundImageUrl,
135
- colorNodes,
136
- regionNodes,
137
- instructions,
138
- explanation,
139
- });
140
- previousPayloadRef.current = currentPayload;
141
- return;
142
- }
143
- // Don't call onChange if we have no meaningful data yet
144
- // This prevents overwriting API data with empty values
145
- if (!backgroundImageUrl && colorNodes.length === 0 && regionNodes.length === 0) {
146
- return;
147
- }
148
- // Create payload
149
- const payload = {
150
- backgroundImageUrl,
151
- colorNodes,
152
- regionNodes,
153
- instructions,
154
- points,
155
- explanation,
156
- };
157
- // Serialize payload for comparison
158
- const payloadString = JSON.stringify(payload);
159
- // Only call onChange if payload actually changed
160
- if (previousPayloadRef.current !== payloadString) {
161
- previousPayloadRef.current = payloadString;
162
- // Use ref to call onChange to avoid dependency on onChange function reference
163
- onChangeRef.current?.(payload);
164
- }
165
- }, [backgroundImageUrl, colorNodes, regionNodes, instructions, points, explanation]);
166
- // UI State
167
- const [isClientMode, setIsClientMode] = useState(false);
168
- const [imageLoaded, setImageLoaded] = useState(false);
169
- // Refs
170
- const workspaceRef = useRef(null);
171
- // ── LACOIS-style custom drag state ────────────────────────────────────────
172
- const [draggingId, setDraggingId] = useState(null);
173
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
174
- const handleRegionMouseDown = useCallback((regionId, regionX, regionY, e) => {
175
- if (isClientMode)
176
- return;
177
- e.preventDefault();
178
- setDraggingId(regionId);
179
- if (workspaceRef.current) {
180
- const rect = workspaceRef.current.getBoundingClientRect();
181
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
182
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
183
- setDragOffset({ x: mouseXPercent - regionX, y: mouseYPercent - regionY });
184
- }
185
- }, [isClientMode]);
186
- const handleMouseMove = useCallback((e) => {
187
- if (!draggingId || !workspaceRef.current)
188
- return;
189
- const rect = workspaceRef.current.getBoundingClientRect();
190
- const mouseXPercent = ((e.clientX - rect.left) / rect.width) * 100;
191
- const mouseYPercent = ((e.clientY - rect.top) / rect.height) * 100;
192
- setRegionNodes((prev) => prev.map((region) => {
193
- if (region.id !== draggingId)
194
- return region;
195
- const x = Math.max(0, Math.min(100 - region.width, mouseXPercent - dragOffset.x));
196
- const y = Math.max(0, Math.min(100 - region.height, mouseYPercent - dragOffset.y));
197
- return { ...region, x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
198
- }));
199
- }, [draggingId, dragOffset]);
200
- const handleMouseUp = useCallback(() => {
201
- setDraggingId(null);
202
- }, []);
203
- useEffect(() => {
204
- if (draggingId) {
205
- window.addEventListener('mousemove', handleMouseMove);
206
- window.addEventListener('mouseup', handleMouseUp);
207
- return () => {
208
- window.removeEventListener('mousemove', handleMouseMove);
209
- window.removeEventListener('mouseup', handleMouseUp);
210
- };
211
- }
212
- }, [draggingId, handleMouseMove, handleMouseUp]);
213
- // ─────────────────────────────────────────────────────────────────────────
214
- // Remove color node and its linked region
215
- const removeColorNode = useCallback((id) => {
216
- // Find the region linked to this color node
217
- const linkedRegion = regionNodes.find((r) => r.linkedColorNodeId === id);
218
- if (linkedRegion) {
219
- setRegionNodes((prev) => prev.filter((r) => r.id !== linkedRegion.id));
220
- }
221
- setColorNodes((prev) => prev.filter((n) => n.id !== id));
222
- }, [regionNodes]);
223
- // Remove region
224
- const removeRegion = useCallback((id) => {
225
- setRegionNodes((prev) => prev.filter((r) => r.id !== id));
226
- }, []);
227
- // Add new color node with auto-generated region
228
- const addNewColorNode = useCallback(() => {
229
- if (!selectedColor) {
230
- form.setError('colorNodes', { message: 'Vui lòng chọn màu!' });
231
- return;
232
- }
233
- // Check if color already exists
234
- if (colorNodes.some((n) => n.color === selectedColor)) {
235
- form.setError('colorNodes', { message: 'Màu này đã được thêm!' });
236
- return;
237
- }
238
- if (!imageLoaded) {
239
- form.setError('backgroundImageUrl', { message: 'Vui lòng upload ảnh nền trước!' });
240
- return;
241
- }
242
- const timestamp = Date.now();
243
- // Create color node (with optional label)
244
- const newColorNode = {
245
- id: `color-${timestamp}`,
246
- color: selectedColor,
247
- ...(showColorLabel && colorLabelValue.trim() ? { label: colorLabelValue.trim() } : {}),
248
- };
249
- // Create region at random position (10-80% to avoid edges)
250
- const randomX = Math.random() * 70 + 10; // 10-80%
251
- const randomY = Math.random() * 70 + 10; // 10-80%
252
- const newRegion = {
253
- id: `region-${timestamp}`,
254
- x: randomX,
255
- y: randomY,
256
- width: 6, // Smaller default width (6% of image width)
257
- height: 5, // Smaller default height (5% of image height)
258
- color: selectedColor,
259
- linkedColorNodeId: newColorNode.id,
260
- };
261
- setColorNodes((prev) => [...prev, newColorNode]);
262
- setRegionNodes((prev) => [...prev, newRegion]);
263
- // Reset selection
264
- setSelectedColor('');
265
- setColorLabelValue('');
266
- setShowAddColorPanel(false);
267
- form.clearErrors();
268
- }, [selectedColor, colorLabelValue, showColorLabel, imageLoaded, colorNodes, form]);
269
- // Get validation errors
270
- const getErrors = () => {
271
- const errors = {};
272
- const bgError = form.formState.errors.backgroundImageUrl;
273
- if (bgError) {
274
- errors.backgroundImageUrl = bgError.message;
275
- }
276
- const colorNodesError = form.formState.errors.colorNodes;
277
- if (colorNodesError && typeof colorNodesError.message === 'string') {
278
- errors.colorNodes = colorNodesError.message;
279
- }
280
- const regionNodesError = form.formState.errors.regionNodes;
281
- if (regionNodesError && typeof regionNodesError.message === 'string') {
282
- errors.regionNodes = regionNodesError.message;
283
- }
284
- const instructionsError = form.formState.errors.instructions;
285
- if (instructionsError && typeof instructionsError.message === 'string') {
286
- errors.instructions = instructionsError.message;
287
- }
288
- // Check for individual instruction errors (array item errors)
289
- if (instructionsError && Array.isArray(instructionsError)) {
290
- errors.instructionItems = {};
291
- instructionsError.forEach((err, index) => {
292
- if (err && typeof err.message === 'string') {
293
- errors.instructionItems[index] = err.message;
294
- }
295
- });
296
- }
297
- return errors;
298
- };
299
- // Determine if instructions are required based on config (default: true)
300
- const instructionsRequired = config.instructionsRequired !== false;
301
- // Validate form data
302
- const validateForm = () => {
303
- // Update form values before validation
304
- form.setValue('backgroundImageUrl', backgroundImageUrl);
305
- form.setValue('colorNodes', colorNodes);
306
- form.setValue('regionNodes', regionNodes);
307
- form.setValue('instructions', instructions);
308
- // Trigger validation
309
- const result = readAndColorObjectsFormSchema.safeParse({
310
- backgroundImageUrl,
311
- colorNodes,
312
- regionNodes,
313
- instructions: instructionsRequired ? instructions : ['placeholder'],
314
- });
315
- if (!result.success) {
316
- // Set errors from Zod validation
317
- result.error.issues.forEach((issue) => {
318
- // Skip instructions errors when not required
319
- if (!instructionsRequired && issue.path[0] === 'instructions')
320
- return;
321
- const path = issue.path.join('.');
322
- form.setError(path, { message: issue.message });
323
- });
324
- // Check if there are any non-instructions errors
325
- const hasRealErrors = result.error.issues.some((issue) => instructionsRequired || issue.path[0] !== 'instructions');
326
- return !hasRealErrors;
327
- }
328
- return true;
329
- };
330
- // Handle save with validation
331
- const handleSave = () => {
332
- if (!validateForm()) {
333
- return;
334
- }
335
- // NOTE: Save status is now managed by ExamCreator via localStorage
336
- onSave?.({
337
- backgroundImageUrl,
338
- colorNodes,
339
- regionNodes,
340
- instructions,
341
- points,
342
- explanation,
343
- });
344
- };
345
- const errors = getErrors();
346
- // If in client mode, render the Client component
347
- if (isClientMode) {
348
- const questionData = {
349
- backgroundImageUrl: backgroundImagePreviewUrl || backgroundImageUrl,
350
- colorNodes,
351
- regionNodes,
352
- instructions,
353
- explanation,
354
- };
355
- return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { children: _jsx(CardTitle, { children: config.previewTitle }) }), _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(ClientComponent, { questionData: questionData, onSubmit: () => { }, isReviewMode: true, explanation: explanation })] }));
356
- }
357
- // Get available colors (not yet added)
358
- const availableColors = COLOR_PALETTE.filter((color) => !colorNodes.some((node) => node.color === color));
359
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-500 to-purple-600 shadow-md", children: _jsx(Palette, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: config.creatorTitle }), _jsx("p", { className: "text-sm text-gray-500", children: config.creatorSubtitle })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(!isClientMode), 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-6", children: [!isClientMode && (_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx(PointsInput, { value: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }) })), !isClientMode && (_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" }), _jsx("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: config.instructionsTips.map((tip, index) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), tip] }, index))) })] })] }) })), !isClientMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), config.instructionsLabel, " ", instructionsRequired && _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs(Button, { onClick: () => {
360
- setInstructions([...instructions, '']);
361
- form.clearErrors('instructions');
362
- }, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", size: "sm", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm c\u00E2u l\u1EC7nh"] })] }), errors.instructions && (_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("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.instructions })] })), _jsx("div", { className: "space-y-3", children: instructions.map((instruction, index) => {
363
- const hasError = errors.instructionItems?.[index];
364
- return (_jsxs("div", { className: `group relative rounded-xl border-2 p-3 transition-all duration-200 ${instruction.trim()
365
- ? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
366
- : 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all ${instruction.trim()
367
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
368
- : 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: instruction, onChange: (e) => {
369
- const newInstructions = [...instructions];
370
- newInstructions[index] = e.target.value;
371
- setInstructions(newInstructions);
372
- form.clearErrors('instructions');
373
- }, placeholder: `Ví dụ: ${config.instructionPlaceholder}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${hasError ? 'border-red-400' : ''}` }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
374
- setInstructions(instructions.filter((_, i) => i !== index));
375
- }, className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), hasError && (_jsx("p", { className: "ml-11 mt-2 text-sm text-red-600", children: hasError }))] }, index));
376
- }) }), instructions.length === 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 h\u01B0\u1EDBng d\u1EABn n\u00E0o. Nh\u1EA5n \"Th\u00EAm c\u00E2u l\u1EC7nh\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Image, { className: "h-4 w-4 text-indigo-500" }), "\u1EA2nh n\u1EC1n ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "image-upload", label: "\u1EA2nh n\u1EC1n", accept: "image/*", value: backgroundImageUrl, onChange: (key) => {
377
- setBackgroundImageUrl(key);
378
- setImageLoaded(false);
379
- form.clearErrors();
380
- }, onPresignedUrlChange: (url) => {
381
- setBackgroundPreviewUrl(url);
382
- setImageLoaded(false);
383
- }, disabled: isClientMode, maxSize: 5, placeholder: "Paste URL ho\u1EB7c upload file", required: true, autoUpload: true, prefix: "questions", onFileLoad: () => {
384
- setImageLoaded(false);
385
- } }), errors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.backgroundImageUrl }))] }), !isClientMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Palette, { className: "h-4 w-4 text-purple-500" }), config.addItemLabel || 'Thêm Màu', " ", _jsx("span", { className: "text-red-500", children: "*" })] }), !showAddColorPanel && (_jsxs(Button, { onClick: () => setShowAddColorPanel(true), className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700", size: "sm", variant: "outline", disabled: availableColors.length === 0, children: [_jsx(Plus, { className: "h-4 w-4" }), config.addItemLabel || 'Thêm màu'] }))] }), errors.colorNodes && (_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("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.colorNodes })] })), showAddColorPanel && (_jsxs("div", { className: "flex flex-col gap-3 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4", children: [showColorLabel && (_jsx(Input, { value: colorLabelValue, onChange: (e) => setColorLabelValue(e.target.value), placeholder: config.colorLabelPlaceholder || 'Nhập label (ví dụ: Tom, Apple...)', className: "h-9 border-gray-200 bg-white" })), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Select, { value: selectedColor, onValueChange: setSelectedColor, children: [_jsx(SelectTrigger, { className: "flex-1 border-gray-200 bg-white", children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn m\u00E0u..." }) }), _jsx(SelectContent, { children: availableColors.map((color) => (_jsx(SelectItem, { value: color, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-4 w-4 rounded border border-gray-300", style: { backgroundColor: color } }), _jsx("span", { className: "font-mono text-xs", children: color })] }) }, color))) })] }), _jsx(Button, { size: "sm", onClick: addNewColorNode, disabled: !selectedColor ||
386
- availableColors.length === 0 ||
387
- (showColorLabel && !colorLabelValue.trim()), className: "bg-purple-600 hover:bg-purple-700", children: "T\u1EA1o" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
388
- setShowAddColorPanel(false);
389
- setSelectedColor('');
390
- setColorLabelValue('');
391
- }, children: "H\u1EE7y" })] })] })), colorNodes.length === 0 && !showAddColorPanel && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: config.emptyItemMessage || 'Chưa có màu nào. Nhấn "Thêm màu" để bắt đầu.' })] }))] })), errors.regionNodes && (_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("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.regionNodes })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-400 to-purple-500" }), _jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Workspace \u2014 \u1EA2nh n\u1EC1n" }), _jsx("span", { className: "text-xs text-gray-500", children: "K\u00E9o th\u1EA3 region \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB" })] }) }), _jsxs(CardContent, { className: "p-0", children: [colorNodes.length > 0 && (_jsx("div", { className: "border-b border-gray-100 px-4 py-3 flex flex-wrap gap-2", children: colorNodes.map((node) => (_jsxs("div", { className: "inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-900", children: [_jsx("div", { className: "h-4 w-4 rounded border border-gray-300", style: { backgroundColor: node.color } }), node.label ? (_jsx("span", { className: "text-xs", children: node.label })) : (_jsx("span", { className: "font-mono text-xs", children: node.color })), !isClientMode && (_jsx("button", { onClick: () => removeColorNode(node.id), className: "group rounded p-0.5 transition-all hover:scale-110 hover:bg-red-100", type: "button", children: _jsx(X, { className: "h-3 w-3 text-gray-500 transition-colors group-hover:text-red-600" }) }))] }, node.id))) })), _jsx("div", { ref: workspaceRef, className: "relative w-full overflow-hidden select-none isolate", style: { cursor: draggingId ? 'grabbing' : 'default' }, children: backgroundPreviewUrl ? (_jsxs(_Fragment, { children: [_jsx("img", { src: backgroundPreviewUrl, alt: "Background", className: "block w-full h-auto", draggable: false, onLoad: () => setImageLoaded(true), onError: () => {
392
- form.setError('backgroundImageUrl', { message: 'Không thể load ảnh!' });
393
- setImageLoaded(false);
394
- } }), imageLoaded &&
395
- regionNodes.map((region) => {
396
- const linkedColorNode = colorNodes.find((n) => n.id === region.linkedColorNodeId);
397
- return (_jsx("div", { style: {
398
- position: 'absolute',
399
- left: `${region.x}%`,
400
- top: `${region.y}%`,
401
- width: `${region.width}%`,
402
- height: `${region.height}%`,
403
- cursor: isClientMode ? 'default' : 'grab',
404
- zIndex: draggingId === region.id ? 2 : 1,
405
- }, onMouseDown: (e) => handleRegionMouseDown(region.id, region.x, region.y, e), children: _jsx(ColorRegionNode, { data: {
406
- color: region.color,
407
- x: region.x,
408
- y: region.y,
409
- width: region.width,
410
- height: region.height,
411
- onRemove: () => removeRegion(region.id),
412
- isEditMode: !isClientMode,
413
- isClientMode: isClientMode,
414
- label: linkedColorNode?.label || '',
415
- } }) }, region.id));
416
- })] })) : (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-20 text-gray-400", children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-10 w-10 text-gray-300", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }), _jsx("p", { className: "text-sm", children: "Ch\u01B0a c\u00F3 \u1EA3nh n\u1EC1n. H\u00E3y upload \u1EA3nh \u1EDF tr\u00EAn." })] })) })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-gray-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-gray-300 to-gray-400" }), _jsx(CardHeader, { className: "pb-3", 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-gray-100", children: _jsx(MessageSquare, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { id: "explanation", value: explanation, onChange: (e) => setExplanation(e.target.value), placeholder: "Nh\u1EADp gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng...", rows: 3, className: "border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200" }) })] })] }));
417
- }
418
- export function ColorRegionCreator(props) {
419
- return _jsx(ColorRegionCreatorContent, { ...props });
420
- }
1
+ "use client";import{jsx as e,jsxs as r,Fragment as t}from"react/jsx-runtime";import{useState as o,useRef as a,useCallback as n,useEffect as s}from"react";import{Button as i}from"../../../components/ui/button";import{Label as l}from"../../../components/ui/label";import{Card as d,CardContent as c,CardHeader as m,CardTitle as g}from"../../../components/ui/card";import{PointsInput as h}from"../../../components/ui/points-input";import{X as u,Pencil as p,Eye as b,Plus as x,Trash2 as f,MessageSquare as N,Palette as v,Lightbulb as y,Image as w}from"lucide-react";import{Input as k}from"../../../components/ui/input";import{Textarea as C}from"../../../components/ui/textarea";import{FileUpload as I}from"../../../components/ui/file-upload";import{Select as U,SelectContent as E,SelectItem as M,SelectTrigger as L,SelectValue as j}from"../../../components/ui/select";import{ColorRegionNode as S}from"./ColorRegionNode";import{usePresignedFileUrl as z}from"../../../shared/lib/hooks";import{COLOR_PALETTE as T}from"../_shared/constants/color-palette";import{useReactHookForm as V}from"../../../shared/lib/hooks/useReactHookForm";import{readAndColorObjectsFormSchema as R}from"../../../shared/lib/validation/read-and-color-objects.validation";function A({initialData:A,onSave:$,onCancel:D,onChange:B,onUnsavedChangesChange:H,validationRef:P,config:q,ClientComponent:F}){const[J,O]=o(A?.backgroundImageUrl||""),[X,K]=o(A?.colorNodes||[]),[W,Y]=o(A?.regionNodes||[]),[G,Q]=o(Array.isArray(A?.instructions)?A.instructions:[]),[_,Z]=o(""),[ee,re]=o(""),[te,oe]=o(!1),ae=q.showColorLabel||!1,[ne,se]=o(A?.points||1),[ie,le]=o(A?.explanation||""),de=V({defaultValues:{backgroundImageUrl:J,colorNodes:X,regionNodes:W,instructions:G},zodSchema:R,mode:"onBlur"}),ce=a(A),me=a(!0),ge=a(!1);s(()=>{P&&(P.current={triggerValidation:async()=>Ve(),getFormData:()=>({backgroundImageUrl:J,colorNodes:X,regionNodes:W,instructions:G,points:ne,explanation:ie})})},[P,J,X,W,G,ne,ie]);const{previewUrl:he}=z(J),[ue,pe]=o("");s(()=>{he&&pe(he)},[he]),s(()=>{!(JSON.stringify(ce.current)!==JSON.stringify(A))&&ge.current||(me.current=!0,ce.current=A,ge.current=!0,A?(void 0!==A.backgroundImageUrl&&O(A.backgroundImageUrl),void 0!==A.colorNodes&&K(A.colorNodes),void 0!==A.regionNodes&&Y(A.regionNodes),void 0!==A.instructions&&Q(Array.isArray(A.instructions)?A.instructions:[]),void 0!==A.points&&se(A.points),void 0!==A.explanation&&le(A.explanation)):(O(""),K([]),Y([]),Q([]),se(1),le("")),setTimeout(()=>{me.current=!1},150))},[A]);const be=a(B);s(()=>{be.current=B},[B]);const xe=a("");s(()=>{if(me.current||!ge.current){const e=JSON.stringify({backgroundImageUrl:J,colorNodes:X,regionNodes:W,instructions:G,explanation:ie});return void(xe.current=e)}if(!J&&0===X.length&&0===W.length)return;const e={backgroundImageUrl:J,colorNodes:X,regionNodes:W,instructions:G,points:ne,explanation:ie},r=JSON.stringify(e);xe.current!==r&&(xe.current=r,be.current?.(e))},[J,X,W,G,ne,ie]);const[fe,Ne]=o(!1),[ve,ye]=o(!1),we=a(null),[ke,Ce]=o(null),[Ie,Ue]=o({x:0,y:0}),Ee=n((e,r,t,o)=>{if(!fe&&(o.preventDefault(),Ce(e),we.current)){const e=we.current.getBoundingClientRect(),a=(o.clientX-e.left)/e.width*100,n=(o.clientY-e.top)/e.height*100;Ue({x:a-r,y:n-t})}},[fe]),Me=n(e=>{if(!ke||!we.current)return;const r=we.current.getBoundingClientRect(),t=(e.clientX-r.left)/r.width*100,o=(e.clientY-r.top)/r.height*100;Y(e=>e.map(e=>{if(e.id!==ke)return e;const r=Math.max(0,Math.min(100-e.width,t-Ie.x)),a=Math.max(0,Math.min(100-e.height,o-Ie.y));return{...e,x:Math.round(10*r)/10,y:Math.round(10*a)/10}}))},[ke,Ie]),Le=n(()=>{Ce(null)},[]);s(()=>{if(ke)return window.addEventListener("mousemove",Me),window.addEventListener("mouseup",Le),()=>{window.removeEventListener("mousemove",Me),window.removeEventListener("mouseup",Le)}},[ke,Me,Le]);const je=n(e=>{const r=W.find(r=>r.linkedColorNodeId===e);r&&Y(e=>e.filter(e=>e.id!==r.id)),K(r=>r.filter(r=>r.id!==e))},[W]),Se=n(e=>{Y(r=>r.filter(r=>r.id!==e))},[]),ze=n(()=>{if(!_)return void de.setError("colorNodes",{message:"Vui lòng chọn màu!"});if(X.some(e=>e.color===_))return void de.setError("colorNodes",{message:"Màu này đã được thêm!"});if(!ve)return void de.setError("backgroundImageUrl",{message:"Vui lòng upload ảnh nền trước!"});const e=Date.now(),r={id:`color-${e}`,color:_,...ae&&ee.trim()?{label:ee.trim()}:{}},t={id:`region-${e}`,x:70*Math.random()+10,y:70*Math.random()+10,width:6,height:5,color:_,linkedColorNodeId:r.id};K(e=>[...e,r]),Y(e=>[...e,t]),Z(""),re(""),oe(!1),de.clearErrors()},[_,ee,ae,ve,X,de]),Te=!1!==q.instructionsRequired,Ve=()=>{de.setValue("backgroundImageUrl",J),de.setValue("colorNodes",X),de.setValue("regionNodes",W),de.setValue("instructions",G);const e=R.safeParse({backgroundImageUrl:J,colorNodes:X,regionNodes:W,instructions:Te?G:["placeholder"]});if(!e.success){e.error.issues.forEach(e=>{if(!Te&&"instructions"===e.path[0])return;const r=e.path.join(".");de.setError(r,{message:e.message})});return!e.error.issues.some(e=>Te||"instructions"!==e.path[0])}return!0},Re=(()=>{const e={},r=de.formState.errors.backgroundImageUrl;r&&(e.backgroundImageUrl=r.message);const t=de.formState.errors.colorNodes;t&&"string"==typeof t.message&&(e.colorNodes=t.message);const o=de.formState.errors.regionNodes;o&&"string"==typeof o.message&&(e.regionNodes=o.message);const a=de.formState.errors.instructions;return a&&"string"==typeof a.message&&(e.instructions=a.message),a&&Array.isArray(a)&&(e.instructionItems={},a.forEach((r,t)=>{r&&"string"==typeof r.message&&(e.instructionItems[t]=r.message)})),e})();if(fe){const t={backgroundImageUrl:he||J,colorNodes:X,regionNodes:W,instructions:G,explanation:ie};return r("div",{className:"space-y-6",children:[e(d,{children:e(m,{children:r("div",{className:"flex items-center justify-between",children:[e("div",{children:e(g,{children:q.previewTitle})}),r(i,{variant:"outline",size:"sm",onClick:()=>Ne(!1),children:[e(p,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(F,{questionData:t,onSubmit:()=>{},isReviewMode:!0,explanation:ie})]})}const Ae=T.filter(e=>!X.some(r=>r.color===e));return r("div",{className:"space-y-6",children:[r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-pink-500 to-purple-600 shadow-md",children:e(v,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(g,{className:"text-xl font-bold text-gray-900",children:q.creatorTitle}),e("p",{className:"text-sm text-gray-500",children:q.creatorSubtitle})]})]}),r(i,{variant:"outline",size:"sm",onClick:()=>Ne(!fe),className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(b,{className:"h-4 w-4"}),"Xem trước"]})]})}),r(c,{className:"space-y-6",children:[!fe&&e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e(h,{value:ne,onChange:e=>se(Number(e.target.value)||1)})}),!fe&&e("div",{className:"rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm",children:r("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(y,{className:"h-4 w-4 text-blue-600"})}),r("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"}),e("ul",{className:"mt-2 space-y-1.5 text-blue-700",children:q.instructionsTips.map((t,o)=>r("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),t]},o))})]})]})}),!fe&&r("div",{className:"space-y-3",children:[r("div",{className:"flex items-center justify-between",children:[r(l,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(N,{className:"h-4 w-4 text-indigo-500"}),q.instructionsLabel," ",Te&&e("span",{className:"text-red-500",children:"*"})]}),r(i,{onClick:()=>{Q([...G,""]),de.clearErrors("instructions")},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",size:"sm",variant:"outline",children:[e(x,{className:"h-4 w-4"}),"Thêm câu lệnh"]})]}),Re.instructions&&r("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("span",{className:"text-lg",children:"❌"}),e("p",{className:"text-sm text-red-600",children:Re.instructions})]}),e("div",{className:"space-y-3",children:G.map((t,o)=>{const a=Re.instructionItems?.[o];return r("div",{className:"group relative rounded-xl border-2 p-3 transition-all duration-200 "+(t.trim()?"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50":"border-gray-200 bg-white hover:border-gray-300"),children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all "+(t.trim()?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-gray-100 text-gray-600"),children:o+1}),e(k,{value:t,onChange:e=>{const r=[...G];r[o]=e.target.value,Q(r),de.clearErrors("instructions")},placeholder:`Ví dụ: ${q.instructionPlaceholder}`,className:"flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(a?"border-red-400":"")}),e(i,{size:"sm",variant:"ghost",onClick:()=>{Q(G.filter((e,r)=>r!==o))},className:"text-gray-400 hover:bg-red-50 hover:text-red-500",children:e(f,{className:"h-4 w-4"})})]}),a&&e("p",{className:"ml-11 mt-2 text-sm text-red-600",children:a})]},o)})}),0===G.length&&r("div",{className:"flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:[e("span",{className:"text-lg",children:"⚠️"}),e("p",{className:"text-sm text-amber-800",children:'Chưa có hướng dẫn nào. Nhấn "Thêm câu lệnh" để bắt đầu.'})]})]}),r("div",{className:"space-y-2",children:[r(l,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(w,{className:"h-4 w-4 text-indigo-500"}),"Ảnh nền ",e("span",{className:"text-red-500",children:"*"})]}),e(I,{id:"image-upload",label:"Ảnh nền",accept:"image/*",value:J,onChange:e=>{O(e),ye(!1),de.clearErrors()},onPresignedUrlChange:e=>{pe(e),ye(!1)},disabled:fe,maxSize:5,placeholder:"Paste URL hoặc upload file",required:!0,autoUpload:!0,prefix:"questions",onFileLoad:()=>{ye(!1)}}),Re.backgroundImageUrl&&e("p",{className:"mt-1 text-sm text-red-600",children:Re.backgroundImageUrl})]}),!fe&&r("div",{className:"space-y-3",children:[r("div",{className:"flex items-center justify-between",children:[r(l,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(v,{className:"h-4 w-4 text-purple-500"}),q.addItemLabel||"Thêm Màu"," ",e("span",{className:"text-red-500",children:"*"})]}),!te&&r(i,{onClick:()=>oe(!0),className:"gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700",size:"sm",variant:"outline",disabled:0===Ae.length,children:[e(x,{className:"h-4 w-4"}),q.addItemLabel||"Thêm màu"]})]}),Re.colorNodes&&r("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("span",{className:"text-lg",children:"❌"}),e("p",{className:"text-sm text-red-600",children:Re.colorNodes})]}),te&&r("div",{className:"flex flex-col gap-3 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4",children:[ae&&e(k,{value:ee,onChange:e=>re(e.target.value),placeholder:q.colorLabelPlaceholder||"Nhập label (ví dụ: Tom, Apple...)",className:"h-9 border-gray-200 bg-white"}),r("div",{className:"flex gap-2",children:[r(U,{value:_,onValueChange:Z,children:[e(L,{className:"flex-1 border-gray-200 bg-white",children:e(j,{placeholder:"Chọn màu..."})}),e(E,{children:Ae.map(t=>e(M,{value:t,children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-4 w-4 rounded border border-gray-300",style:{backgroundColor:t}}),e("span",{className:"font-mono text-xs",children:t})]})},t))})]}),e(i,{size:"sm",onClick:ze,disabled:!_||0===Ae.length||ae&&!ee.trim(),className:"bg-purple-600 hover:bg-purple-700",children:"Tạo"}),e(i,{size:"sm",variant:"outline",onClick:()=>{oe(!1),Z(""),re("")},children:"Hủy"})]})]}),0===X.length&&!te&&r("div",{className:"flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:[e("span",{className:"text-lg",children:"⚠️"}),e("p",{className:"text-sm text-amber-800",children:q.emptyItemMessage||'Chưa có màu nào. Nhấn "Thêm màu" để bắt đầu.'})]})]}),Re.regionNodes&&r("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("span",{className:"text-lg",children:"❌"}),e("p",{className:"text-sm text-red-600",children:Re.regionNodes})]})]})]}),r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-400 to-purple-500"}),e(m,{className:"pb-3",children:r("div",{className:"flex items-center gap-2",children:[e(g,{className:"text-base font-semibold text-gray-800",children:"Workspace — Ảnh nền"}),e("span",{className:"text-xs text-gray-500",children:"Kéo thả region để định vị"})]})}),r(c,{className:"p-0",children:[X.length>0&&e("div",{className:"border-b border-gray-100 px-4 py-3 flex flex-wrap gap-2",children:X.map(t=>r("div",{className:"inline-flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-900",children:[e("div",{className:"h-4 w-4 rounded border border-gray-300",style:{backgroundColor:t.color}}),t.label?e("span",{className:"text-xs",children:t.label}):e("span",{className:"font-mono text-xs",children:t.color}),!fe&&e("button",{onClick:()=>je(t.id),className:"group rounded p-0.5 transition-all hover:scale-110 hover:bg-red-100",type:"button",children:e(u,{className:"h-3 w-3 text-gray-500 transition-colors group-hover:text-red-600"})})]},t.id))}),e("div",{ref:we,className:"relative w-full overflow-hidden select-none isolate",style:{cursor:ke?"grabbing":"default"},children:ue?r(t,{children:[e("img",{src:ue,alt:"Background",className:"block w-full h-auto",draggable:!1,onLoad:()=>ye(!0),onError:()=>{de.setError("backgroundImageUrl",{message:"Không thể load ảnh!"}),ye(!1)}}),ve&&W.map(r=>{const t=X.find(e=>e.id===r.linkedColorNodeId);return e("div",{style:{position:"absolute",left:`${r.x}%`,top:`${r.y}%`,width:`${r.width}%`,height:`${r.height}%`,cursor:fe?"default":"grab",zIndex:ke===r.id?2:1},onMouseDown:e=>Ee(r.id,r.x,r.y,e),children:e(S,{data:{color:r.color,x:r.x,y:r.y,width:r.width,height:r.height,onRemove:()=>Se(r.id),isEditMode:!fe,isClientMode:fe,label:t?.label||""}})},r.id)})]}):r("div",{className:"flex flex-col items-center justify-center gap-2 py-20 text-gray-400",children:[e("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-10 w-10 text-gray-300",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:e("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:1.5,d:"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"})}),e("p",{className:"text-sm",children:"Chưa có ảnh nền. Hãy upload ảnh ở trên."})]})})]})]}),r(d,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-gray-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-gray-300 to-gray-400"}),e(m,{className:"pb-3",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-gray-100",children:e(N,{className:"h-3.5 w-3.5 text-gray-600"})}),e(g,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"})]})}),e(c,{children:e(C,{id:"explanation",value:ie,onChange:e=>le(e.target.value),placeholder:"Nhập giải thích cho đáp án đúng...",rows:3,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200"})})]})]})}export function ColorRegionCreator(r){return e(A,{...r})}
@@ -1,59 +1 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { memo, useState, useEffect, useRef } from 'react';
4
- import { ColorNode } from './ColorNode';
5
- export const ColorRegionNode = memo(({ data }) => {
6
- const { color: propColor, width, height, isClientMode, savedColor: propSavedColor, isUnassignedRegion, isHighlighted, label, } = data;
7
- // In creator mode, use propColor directly (no state needed to avoid re-render loops)
8
- // In client mode, use state to track propSavedColor changes from drag & drop
9
- const [savedColor, setSavedColor] = useState(propSavedColor || '');
10
- const prevPropSavedColorRef = useRef(propSavedColor || '');
11
- // Only sync state in client mode when propSavedColor actually changes
12
- // In creator mode, always use propColor directly - no state updates needed
13
- useEffect(() => {
14
- if (isClientMode) {
15
- // Only update if propSavedColor actually changed
16
- if (prevPropSavedColorRef.current !== propSavedColor) {
17
- prevPropSavedColorRef.current = propSavedColor || '';
18
- setSavedColor(propSavedColor || '');
19
- }
20
- }
21
- else {
22
- // Reset ref when switching to creator mode
23
- prevPropSavedColorRef.current = '';
24
- }
25
- }, [isClientMode, propSavedColor]);
26
- // Determine the color to use: in creator mode use propColor directly, in client mode use savedColor
27
- const displayColor = isClientMode ? savedColor : propColor || '';
28
- return (_jsx("div", { className: `relative rounded-2xl transition-all ${
29
- // Unassigned region has no visible styling (transparent)
30
- isUnassignedRegion
31
- ? '' // No border/background for unassigned region
32
- : // In client mode, show transparent region (ColorNode will be rendered separately)
33
- isClientMode
34
- ? displayColor || isHighlighted
35
- ? '' // No border/background when ColorNode is assigned or when highlighted (node đang drag vào)
36
- : 'rounded-2xl border border-dashed border-red-300 bg-black/10' // Client mode - subtle red border when empty
37
- : displayColor
38
- ? '' // Match client mode when region đã có màu
39
- : 'rounded-2xl border border-dashed border-red-300 bg-black/5' // Creator mode empty — thinner, lighter border
40
- }`, style: isUnassignedRegion
41
- ? {
42
- width: '100%',
43
- height: '100%',
44
- }
45
- : {
46
- width: `${width}%`,
47
- height: `${height}%`,
48
- minWidth: '60px',
49
- minHeight: '40px',
50
- borderRadius: '16px',
51
- }, children: !isClientMode && displayColor && (_jsx("div", { className: "pointer-events-none h-full w-full", children: _jsx(ColorNode, { data: {
52
- color: displayColor,
53
- isConnected: true,
54
- onRemove: () => { },
55
- displayMode: 'compact',
56
- label: label,
57
- }, variant: "client" }) })) }));
58
- });
59
- ColorRegionNode.displayName = 'ColorRegionNode';
1
+ "use client";import{jsx as e}from"react/jsx-runtime";import{memo as o,useState as r,useEffect as d,useRef as i}from"react";import{ColorNode as t}from"./ColorNode";export const ColorRegionNode=o(({data:o})=>{const{color:l,width:n,height:a,isClientMode:s,savedColor:c,isUnassignedRegion:h,isHighlighted:m,label:b}=o,[g,u]=r(c||""),p=i(c||"");d(()=>{s?p.current!==c&&(p.current=c||"",u(c||"")):p.current=""},[s,c]);const x=s?g:l||"";return e("div",{className:"relative rounded-2xl transition-all "+(h?"":s?x||m?"":"rounded-2xl border border-dashed border-red-300 bg-black/10":x?"":"rounded-2xl border border-dashed border-red-300 bg-black/5"),style:h?{width:"100%",height:"100%"}:{width:`${n}%`,height:`${a}%`,minWidth:"60px",minHeight:"40px",borderRadius:"16px"},children:!s&&x&&e("div",{className:"pointer-events-none h-full w-full",children:e(t,{data:{color:x,isConnected:!0,onRemove:()=>{},displayMode:"compact",label:b},variant:"client"})})})});ColorRegionNode.displayName="ColorRegionNode";