@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.
- package/dist/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.d.ts +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.d.ts +45 -1
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CompletionDialog.d.ts +8 -0
- package/dist/components/exams/CompletionDialog.js +1 -0
- package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.d.ts +9 -3
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.d.ts +1 -1
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
- package/dist/components/exams/ExamPreviewDialog.js +1 -0
- package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/ExamViewDialog.d.ts +24 -0
- package/dist/components/exams/ExamViewDialog.js +1 -0
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/exams/UnsavedChangesDialog.js +1 -0
- package/dist/components/exams/api.d.ts +1 -1
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.texts.d.ts +58 -0
- package/dist/components/exams/create-exam.texts.js +1 -0
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-question-status.d.ts +9 -0
- package/dist/components/exams/exam-question-status.js +1 -0
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.d.ts +11 -1
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
- package/dist/shared/lib/utils/question-transform-validation.js +1 -0
- package/dist/shared/lib/utils/question-transform.d.ts +6 -2
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +12 -3
|
@@ -1,545 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
-
import { toast } from 'sonner';
|
|
5
|
-
import { ReactFlow, Background, useNodesState, useEdgesState, ReactFlowProvider, useReactFlow, } from '@xyflow/react';
|
|
6
|
-
import '@xyflow/react/dist/style.css';
|
|
7
|
-
import { Lightbulb } from 'lucide-react';
|
|
8
|
-
import { ColorNode } from './ColorNode';
|
|
9
|
-
import { ColorRegionNode } from './ColorRegionNode';
|
|
10
|
-
import { usePresignedFileUrl } from '../../../shared/lib/hooks';
|
|
11
|
-
const nodeTypes = {
|
|
12
|
-
clientColorNode: (props) => _jsx(ColorNode, { ...props, variant: "client" }),
|
|
13
|
-
clientColorRegionNode: (props) => _jsx(ColorRegionNode, { ...props }),
|
|
14
|
-
};
|
|
15
|
-
function ColorRegionClientContent({ questionData, explanation, isReviewMode = false, config }) {
|
|
16
|
-
const [imageLoaded, setImageLoaded] = useState(false);
|
|
17
|
-
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });
|
|
18
|
-
// Get presigned URL for background image from S3
|
|
19
|
-
const { previewUrl: backgroundImagePreviewUrl, isLoading: isLoadingImage } = usePresignedFileUrl(questionData.backgroundImageUrl);
|
|
20
|
-
// Refs
|
|
21
|
-
const imageRef = useRef(null);
|
|
22
|
-
const flowRef = useRef(null);
|
|
23
|
-
// React Flow State
|
|
24
|
-
const [nodes, setNodes, reactFlowOnNodesChange] = useNodesState([]);
|
|
25
|
-
const [, , onEdgesChange] = useEdgesState([]);
|
|
26
|
-
// Track which color is assigned to which region
|
|
27
|
-
const [regionAssignments, setRegionAssignments] = useState({});
|
|
28
|
-
// Handle image load
|
|
29
|
-
const handleImageLoad = () => {
|
|
30
|
-
if (imageRef.current && flowRef.current) {
|
|
31
|
-
const flowWidth = flowRef.current.clientWidth - 100; // 50px padding on each side
|
|
32
|
-
const flowHeight = flowRef.current.clientHeight - 260; // top section and bottom margin
|
|
33
|
-
const naturalWidth = imageRef.current.naturalWidth || imageRef.current.clientWidth;
|
|
34
|
-
const naturalHeight = imageRef.current.naturalHeight || imageRef.current.clientHeight;
|
|
35
|
-
const widthRatio = flowWidth / naturalWidth;
|
|
36
|
-
const heightRatio = flowHeight / naturalHeight;
|
|
37
|
-
const scale = Math.min(widthRatio, heightRatio, 1);
|
|
38
|
-
setImageDimensions({
|
|
39
|
-
width: naturalWidth * scale,
|
|
40
|
-
height: naturalHeight * scale,
|
|
41
|
-
});
|
|
42
|
-
setImageLoaded(true);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
// Handle filling color into region
|
|
46
|
-
const handleFillRegionColor = useCallback((regionId, color) => {
|
|
47
|
-
setRegionAssignments((prev) => ({
|
|
48
|
-
...prev,
|
|
49
|
-
[regionId]: color,
|
|
50
|
-
}));
|
|
51
|
-
}, []);
|
|
52
|
-
// Handle removing color from region (for client mode delete)
|
|
53
|
-
const handleRemoveColorFromRegion = useCallback((regionId) => {
|
|
54
|
-
const removedColor = regionAssignments[regionId];
|
|
55
|
-
if (removedColor) {
|
|
56
|
-
// Remove assignment
|
|
57
|
-
setRegionAssignments((prev) => {
|
|
58
|
-
const newAssignments = { ...prev };
|
|
59
|
-
delete newAssignments[regionId];
|
|
60
|
-
return newAssignments;
|
|
61
|
-
});
|
|
62
|
-
// Restore color node visibility
|
|
63
|
-
setNodes((prevNodes) => prevNodes.map((n) => n.data?.color === removedColor ? { ...n, data: { ...n.data, isConnected: false } } : n));
|
|
64
|
-
}
|
|
65
|
-
}, [regionAssignments, setNodes]);
|
|
66
|
-
// State for visual feedback
|
|
67
|
-
const [highlightedRegionId, setHighlightedRegionId] = useState(null);
|
|
68
|
-
const { getIntersectingNodes } = useReactFlow();
|
|
69
|
-
const imageStartY = 200;
|
|
70
|
-
const imageStartX = (() => {
|
|
71
|
-
if (!imageLoaded || imageDimensions.width <= 0)
|
|
72
|
-
return 50;
|
|
73
|
-
const flowWidth = flowRef.current?.clientWidth || 0;
|
|
74
|
-
if (!flowWidth)
|
|
75
|
-
return 50;
|
|
76
|
-
const centered = (flowWidth - imageDimensions.width) / 2;
|
|
77
|
-
return Math.max(50, centered);
|
|
78
|
-
})();
|
|
79
|
-
// Track if a node is being dragged to prevent panning
|
|
80
|
-
const isDraggingNodeRef = useRef(false);
|
|
81
|
-
// Track the old region ID when dragging starts (for swap functionality)
|
|
82
|
-
const draggedNodeOldRegionRef = useRef(null);
|
|
83
|
-
// Handle node drag start - store old region for potential swap
|
|
84
|
-
const onNodeDragStart = useCallback((_, node) => {
|
|
85
|
-
// Only handle color nodes
|
|
86
|
-
if (node.type !== 'clientColorNode') {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Set flag to prevent panning
|
|
90
|
-
isDraggingNodeRef.current = true;
|
|
91
|
-
const draggedColor = node.data?.color;
|
|
92
|
-
if (!draggedColor || typeof draggedColor !== 'string') {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
// Check if this color node is from a region (has regionId in data)
|
|
96
|
-
const regionId = node.data?.regionId;
|
|
97
|
-
// Find the old region this color was assigned to (either from regionId or from assignments)
|
|
98
|
-
const assignedRegionId = regionId ||
|
|
99
|
-
Object.keys(regionAssignments).find((id) => regionAssignments[id] === draggedColor);
|
|
100
|
-
// Store the old region ID for potential swap
|
|
101
|
-
draggedNodeOldRegionRef.current = assignedRegionId || null;
|
|
102
|
-
if (assignedRegionId) {
|
|
103
|
-
// Temporarily remove the old assignment (will be restored or swapped)
|
|
104
|
-
setRegionAssignments((prev) => {
|
|
105
|
-
const newAssignments = { ...prev };
|
|
106
|
-
delete newAssignments[assignedRegionId];
|
|
107
|
-
return newAssignments;
|
|
108
|
-
});
|
|
109
|
-
// Ngay lập tức cập nhật lại region node để hiển thị vùng drop
|
|
110
|
-
setNodes((prevNodes) => prevNodes.map((n) => {
|
|
111
|
-
if (n.id !== assignedRegionId || n.type !== 'clientColorRegionNode') {
|
|
112
|
-
return n;
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
...n,
|
|
116
|
-
data: {
|
|
117
|
-
...n.data,
|
|
118
|
-
savedColor: '',
|
|
119
|
-
},
|
|
120
|
-
style: {
|
|
121
|
-
...n.style,
|
|
122
|
-
pointerEvents: 'auto',
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
}));
|
|
126
|
-
}
|
|
127
|
-
}, [regionAssignments, setNodes]);
|
|
128
|
-
const onNodeDrag = useCallback((_, node) => {
|
|
129
|
-
if (node.type !== 'clientColorNode') {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const draggedColor = node.data?.color;
|
|
133
|
-
if (!draggedColor || typeof draggedColor !== 'string') {
|
|
134
|
-
setHighlightedRegionId(null);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const parseDimension = (value) => {
|
|
138
|
-
if (typeof value === 'number') {
|
|
139
|
-
return Number.isFinite(value) ? value : null;
|
|
140
|
-
}
|
|
141
|
-
if (typeof value === 'string') {
|
|
142
|
-
const parsed = parseFloat(value);
|
|
143
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
};
|
|
147
|
-
const getNodeDimension = (targetNode, key) => {
|
|
148
|
-
const direct = key in targetNode ? targetNode[key] : null;
|
|
149
|
-
if (typeof direct === 'number' && Number.isFinite(direct)) {
|
|
150
|
-
return direct;
|
|
151
|
-
}
|
|
152
|
-
const styleValue = targetNode.measured
|
|
153
|
-
? targetNode.measured[key]
|
|
154
|
-
: undefined;
|
|
155
|
-
return parseDimension(styleValue);
|
|
156
|
-
};
|
|
157
|
-
const getRegionPixelSize = (targetRegion) => {
|
|
158
|
-
if (imageDimensions.width <= 0 || imageDimensions.height <= 0) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
const styleWidth = parseDimension(targetRegion.style?.width);
|
|
162
|
-
const styleHeight = parseDimension(targetRegion.style?.height);
|
|
163
|
-
if (styleWidth && styleHeight) {
|
|
164
|
-
return {
|
|
165
|
-
width: Math.max(styleWidth, 60),
|
|
166
|
-
height: Math.max(styleHeight, 40),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
const regionData = targetRegion.data;
|
|
170
|
-
if (regionData?.width === undefined ||
|
|
171
|
-
regionData?.height === undefined ||
|
|
172
|
-
typeof regionData.width !== 'number' ||
|
|
173
|
-
typeof regionData.height !== 'number') {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
width: Math.max((regionData.width / 100) * imageDimensions.width, 60),
|
|
178
|
-
height: Math.max((regionData.height / 100) * imageDimensions.height, 40),
|
|
179
|
-
};
|
|
180
|
-
};
|
|
181
|
-
const highlightedColorNodeClass = '';
|
|
182
|
-
const draggingColorNodeClass = ''; // Bỏ border khi đang drag
|
|
183
|
-
const intersectingNodes = getIntersectingNodes(node, true);
|
|
184
|
-
const colorNodeIds = intersectingNodes
|
|
185
|
-
.filter((n) => n.type === 'clientColorNode')
|
|
186
|
-
.map((n) => n.id);
|
|
187
|
-
const intersectingRegions = intersectingNodes.filter((n) => n.type === 'clientColorRegionNode' && n.id !== 'unassigned-region');
|
|
188
|
-
const nodeY = node.position.y;
|
|
189
|
-
const isInUnassignedRegion = nodeY >= 0 && nodeY <= 100;
|
|
190
|
-
if (intersectingRegions.length > 0) {
|
|
191
|
-
setHighlightedRegionId(intersectingRegions[0].id);
|
|
192
|
-
if (!colorNodeIds.includes(node.id)) {
|
|
193
|
-
colorNodeIds.push(node.id);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else if (isInUnassignedRegion) {
|
|
197
|
-
setHighlightedRegionId('unassigned-region');
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
setHighlightedRegionId(null);
|
|
201
|
-
}
|
|
202
|
-
setNodes((prevNodes) => prevNodes.map((n) => n.type === 'clientColorNode'
|
|
203
|
-
? {
|
|
204
|
-
...n,
|
|
205
|
-
data: {
|
|
206
|
-
...n.data,
|
|
207
|
-
highlightClass: colorNodeIds.includes(n.id)
|
|
208
|
-
? highlightedColorNodeClass
|
|
209
|
-
: n.id === node.id
|
|
210
|
-
? draggingColorNodeClass
|
|
211
|
-
: undefined,
|
|
212
|
-
highlightStyle: colorNodeIds.includes(n.id) &&
|
|
213
|
-
n.id === node.id &&
|
|
214
|
-
intersectingRegions.length > 0
|
|
215
|
-
? (() => {
|
|
216
|
-
const targetRegion = intersectingRegions[0];
|
|
217
|
-
const regionSize = getRegionPixelSize(targetRegion);
|
|
218
|
-
const nodeWidth = getNodeDimension(n, 'width');
|
|
219
|
-
const nodeHeight = getNodeDimension(n, 'height');
|
|
220
|
-
if (!regionSize || !nodeWidth || !nodeHeight) {
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
transform: `scale(${Number(regionSize.width) / Number(nodeWidth)}, ${Number(regionSize.height) / Number(nodeHeight)})`,
|
|
225
|
-
transformOrigin: 'center center',
|
|
226
|
-
transition: 'transform 180ms ease-out',
|
|
227
|
-
};
|
|
228
|
-
})()
|
|
229
|
-
: undefined,
|
|
230
|
-
isHighlighted: colorNodeIds.includes(n.id),
|
|
231
|
-
},
|
|
232
|
-
}
|
|
233
|
-
: n));
|
|
234
|
-
}, [getIntersectingNodes, imageDimensions, setNodes]);
|
|
235
|
-
// Handle node drag stop (drop) with intersection detection
|
|
236
|
-
const onNodeDragStop = useCallback((_, node) => {
|
|
237
|
-
if (node.type !== 'clientColorNode')
|
|
238
|
-
return;
|
|
239
|
-
setNodes((prevNodes) => prevNodes.map((n) => n.type === 'clientColorNode'
|
|
240
|
-
? {
|
|
241
|
-
...n,
|
|
242
|
-
data: {
|
|
243
|
-
...n.data,
|
|
244
|
-
highlightClass: undefined,
|
|
245
|
-
highlightStyle: undefined,
|
|
246
|
-
isHighlighted: false,
|
|
247
|
-
},
|
|
248
|
-
}
|
|
249
|
-
: n));
|
|
250
|
-
const draggedColor = node.data?.color;
|
|
251
|
-
if (!draggedColor || typeof draggedColor !== 'string') {
|
|
252
|
-
setHighlightedRegionId(null);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
// Get intersecting nodes using React Flow's built-in method with partial intersection
|
|
256
|
-
const intersectingNodes = getIntersectingNodes(node, true); // partially = true
|
|
257
|
-
// Find intersecting region nodes (excluding unassigned region - we'll check it manually)
|
|
258
|
-
const intersectingRegions = intersectingNodes.filter((n) => n.type === 'clientColorRegionNode' && n.id !== 'unassigned-region');
|
|
259
|
-
// Manually check if node is in unassigned region area (top 100px)
|
|
260
|
-
const nodeY = node.position.y;
|
|
261
|
-
const isInUnassignedRegion = nodeY >= 0 && nodeY <= 100;
|
|
262
|
-
// Clear highlight
|
|
263
|
-
setHighlightedRegionId(null);
|
|
264
|
-
if (intersectingRegions.length > 0) {
|
|
265
|
-
const targetRegion = intersectingRegions[0];
|
|
266
|
-
const oldRegionId = draggedNodeOldRegionRef.current;
|
|
267
|
-
// Drop into a regular region
|
|
268
|
-
const existingColorInTargetRegion = regionAssignments[targetRegion.id];
|
|
269
|
-
if (existingColorInTargetRegion && existingColorInTargetRegion !== draggedColor) {
|
|
270
|
-
// Swap: Target region already has a color node
|
|
271
|
-
// 1. Assign dragged color to target region
|
|
272
|
-
// 2. Assign existing color to old region (if exists) or remove it
|
|
273
|
-
setRegionAssignments((prev) => {
|
|
274
|
-
const newAssignments = { ...prev };
|
|
275
|
-
// Assign dragged color to target region
|
|
276
|
-
newAssignments[targetRegion.id] = draggedColor;
|
|
277
|
-
// If dragged color had an old region, assign existing color to old region
|
|
278
|
-
if (oldRegionId) {
|
|
279
|
-
newAssignments[oldRegionId] = existingColorInTargetRegion;
|
|
280
|
-
}
|
|
281
|
-
// If dragged color had no old region, existing color will be removed from assignments
|
|
282
|
-
// (it will go back to original position - already removed when we set newAssignments)
|
|
283
|
-
return newAssignments;
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// Simple assignment: Target region is empty or same color
|
|
288
|
-
setRegionAssignments((prev) => ({
|
|
289
|
-
...prev,
|
|
290
|
-
[targetRegion.id]: draggedColor,
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
// Clear the ref
|
|
294
|
-
draggedNodeOldRegionRef.current = null;
|
|
295
|
-
}
|
|
296
|
-
else if (isInUnassignedRegion) {
|
|
297
|
-
// Drop into unassigned region area - remove assignment and return to original position
|
|
298
|
-
const oldRegionId = draggedNodeOldRegionRef.current;
|
|
299
|
-
if (oldRegionId) {
|
|
300
|
-
setRegionAssignments((prev) => {
|
|
301
|
-
const newAssignments = { ...prev };
|
|
302
|
-
delete newAssignments[oldRegionId];
|
|
303
|
-
return newAssignments;
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
// Reset node position to original position
|
|
307
|
-
const originalNode = questionData.colorNodes.find((n) => n.color === draggedColor);
|
|
308
|
-
if (originalNode) {
|
|
309
|
-
const currentAssignments = oldRegionId
|
|
310
|
-
? Object.fromEntries(Object.entries(regionAssignments).filter(([id]) => id !== oldRegionId))
|
|
311
|
-
: regionAssignments;
|
|
312
|
-
const unassignedColors = questionData.colorNodes.filter((n) => !Object.values(currentAssignments).includes(n.color) || n.color === draggedColor);
|
|
313
|
-
const itemsPerRow = 4;
|
|
314
|
-
const nodeIndex = unassignedColors.findIndex((n) => n.id === originalNode.id);
|
|
315
|
-
const row = Math.floor(nodeIndex / itemsPerRow);
|
|
316
|
-
const col = nodeIndex % itemsPerRow;
|
|
317
|
-
const originalPosition = { x: 50 + col * 200, y: 20 + row * 80 };
|
|
318
|
-
// Update node position
|
|
319
|
-
setNodes((prevNodes) => prevNodes.map((n) => (n.id === node.id ? { ...n, position: originalPosition } : n)));
|
|
320
|
-
}
|
|
321
|
-
draggedNodeOldRegionRef.current = null;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// Drop outside any valid region - return to original position
|
|
325
|
-
// If it was assigned to a region, remove the assignment
|
|
326
|
-
if (draggedNodeOldRegionRef.current) {
|
|
327
|
-
setRegionAssignments((prev) => {
|
|
328
|
-
const newAssignments = { ...prev };
|
|
329
|
-
delete newAssignments[draggedNodeOldRegionRef.current];
|
|
330
|
-
return newAssignments;
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
// Reset node position to original position
|
|
334
|
-
const originalNode = questionData.colorNodes.find((n) => n.color === draggedColor);
|
|
335
|
-
if (originalNode) {
|
|
336
|
-
const currentAssignments = draggedNodeOldRegionRef.current
|
|
337
|
-
? Object.fromEntries(Object.entries(regionAssignments).filter(([id]) => id !== draggedNodeOldRegionRef.current))
|
|
338
|
-
: regionAssignments;
|
|
339
|
-
const unassignedColors = questionData.colorNodes.filter((n) => !Object.values(currentAssignments).includes(n.color) || n.color === draggedColor);
|
|
340
|
-
const itemsPerRow = 4;
|
|
341
|
-
const nodeIndex = unassignedColors.findIndex((n) => n.id === originalNode.id);
|
|
342
|
-
const row = Math.floor(nodeIndex / itemsPerRow);
|
|
343
|
-
const col = nodeIndex % itemsPerRow;
|
|
344
|
-
const originalPosition = { x: 50 + col * 200, y: 20 + row * 80 };
|
|
345
|
-
// Update node position
|
|
346
|
-
setNodes((prevNodes) => prevNodes.map((n) => (n.id === node.id ? { ...n, position: originalPosition } : n)));
|
|
347
|
-
}
|
|
348
|
-
// Clear the ref
|
|
349
|
-
draggedNodeOldRegionRef.current = null;
|
|
350
|
-
}
|
|
351
|
-
// Clear dragging flag
|
|
352
|
-
isDraggingNodeRef.current = false;
|
|
353
|
-
}, [getIntersectingNodes, regionAssignments, questionData.colorNodes, setNodes]);
|
|
354
|
-
// Memoize color nodes to avoid unnecessary recalculations
|
|
355
|
-
// Only show color nodes that are NOT assigned to any region (they will be shown in regions when assigned)
|
|
356
|
-
const colorFlowNodes = useMemo(() => {
|
|
357
|
-
if (!questionData.colorNodes || questionData.colorNodes.length === 0) {
|
|
358
|
-
return [];
|
|
359
|
-
}
|
|
360
|
-
// Filter out color nodes that are already assigned to regions
|
|
361
|
-
const unassignedColors = questionData.colorNodes.filter((node) => !Object.values(regionAssignments).includes(node.color));
|
|
362
|
-
const itemsPerRow = 4; // 4 items per row
|
|
363
|
-
return unassignedColors.map((node, index) => {
|
|
364
|
-
const row = Math.floor(index / itemsPerRow);
|
|
365
|
-
const col = index % itemsPerRow;
|
|
366
|
-
return {
|
|
367
|
-
id: node.id,
|
|
368
|
-
type: 'clientColorNode',
|
|
369
|
-
position: { x: 50 + col * 200, y: 20 + row * 80 },
|
|
370
|
-
data: {
|
|
371
|
-
color: node.color,
|
|
372
|
-
isEditMode: false,
|
|
373
|
-
isConnected: false, // These are unassigned, so always false
|
|
374
|
-
displayMode: 'compact',
|
|
375
|
-
onRemove: () => {
|
|
376
|
-
// Remove assignment when color node is removed
|
|
377
|
-
const regionId = Object.keys(regionAssignments).find((id) => regionAssignments[id] === node.color);
|
|
378
|
-
if (regionId) {
|
|
379
|
-
setRegionAssignments((prev) => {
|
|
380
|
-
const newAssignments = { ...prev };
|
|
381
|
-
delete newAssignments[regionId];
|
|
382
|
-
return newAssignments;
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
draggable: true, // Enable dragging in client mode
|
|
388
|
-
};
|
|
389
|
-
});
|
|
390
|
-
}, [questionData.colorNodes, regionAssignments]);
|
|
391
|
-
// Memoize color nodes that are assigned to regions (render them inside regions for dragging)
|
|
392
|
-
const colorNodesInRegions = useMemo(() => {
|
|
393
|
-
if (!imageLoaded || imageDimensions.width <= 0)
|
|
394
|
-
return [];
|
|
395
|
-
return questionData.regionNodes
|
|
396
|
-
.filter((region) => regionAssignments[region.id]) // Only regions with assigned colors
|
|
397
|
-
.map((region) => {
|
|
398
|
-
const assignedColor = regionAssignments[region.id];
|
|
399
|
-
const colorNodeData = questionData.colorNodes.find((n) => n.color === assignedColor);
|
|
400
|
-
if (!colorNodeData)
|
|
401
|
-
return null;
|
|
402
|
-
// Position and size color node relative to region
|
|
403
|
-
const flowWidth = flowRef.current?.clientWidth || 0;
|
|
404
|
-
const startX = flowWidth > 0 && imageDimensions.width > 0
|
|
405
|
-
? Math.max(50, (flowWidth - imageDimensions.width) / 2)
|
|
406
|
-
: 50;
|
|
407
|
-
const regionX = startX + (region.x / 100) * imageDimensions.width;
|
|
408
|
-
const regionY = imageStartY + (region.y / 100) * imageDimensions.height;
|
|
409
|
-
const regionWidth = (region.width / 100) * imageDimensions.width;
|
|
410
|
-
const regionHeight = (region.height / 100) * imageDimensions.height;
|
|
411
|
-
// Apply min dimensions to match ColorRegionNode behavior
|
|
412
|
-
const minWidth = 60;
|
|
413
|
-
const minHeight = 40;
|
|
414
|
-
const finalWidth = Math.max(regionWidth, minWidth);
|
|
415
|
-
const finalHeight = Math.max(regionHeight, minHeight);
|
|
416
|
-
return {
|
|
417
|
-
id: `color-in-region-${region.id}`,
|
|
418
|
-
type: 'clientColorNode',
|
|
419
|
-
position: {
|
|
420
|
-
x: regionX,
|
|
421
|
-
y: regionY,
|
|
422
|
-
},
|
|
423
|
-
data: {
|
|
424
|
-
color: assignedColor,
|
|
425
|
-
isEditMode: false,
|
|
426
|
-
isConnected: true,
|
|
427
|
-
regionId: region.id, // Store region ID for reference
|
|
428
|
-
onRemove: () => handleRemoveColorFromRegion(region.id),
|
|
429
|
-
},
|
|
430
|
-
draggable: true,
|
|
431
|
-
width: finalWidth,
|
|
432
|
-
height: finalHeight,
|
|
433
|
-
style: {
|
|
434
|
-
width: `${finalWidth}px`,
|
|
435
|
-
height: `${finalHeight}px`,
|
|
436
|
-
margin: 0,
|
|
437
|
-
padding: 0,
|
|
438
|
-
boxSizing: 'border-box',
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
|
-
})
|
|
442
|
-
.filter((node) => node !== null);
|
|
443
|
-
}, [
|
|
444
|
-
imageLoaded,
|
|
445
|
-
imageDimensions,
|
|
446
|
-
questionData.regionNodes,
|
|
447
|
-
questionData.colorNodes,
|
|
448
|
-
regionAssignments,
|
|
449
|
-
handleRemoveColorFromRegion,
|
|
450
|
-
]);
|
|
451
|
-
// Memoize region nodes to avoid unnecessary recalculations
|
|
452
|
-
// Regions will be positioned on the image in the middle section
|
|
453
|
-
const regionFlowNodes = useMemo(() => {
|
|
454
|
-
if (!imageLoaded || imageDimensions.width <= 0)
|
|
455
|
-
return [];
|
|
456
|
-
// Calculate image position in the middle section
|
|
457
|
-
// Image starts after color nodes section (top ~200px) and is centered
|
|
458
|
-
const imageStartY = 200; // Start image after color nodes
|
|
459
|
-
const imageStartX = 50; // Left margin
|
|
460
|
-
const flowWidth = flowRef.current?.clientWidth || 0;
|
|
461
|
-
const imageLeft = flowWidth > 0 ? (flowWidth - imageDimensions.width) / 2 : imageStartX;
|
|
462
|
-
return questionData.regionNodes.map((region) => {
|
|
463
|
-
const savedColor = regionAssignments[region.id] || '';
|
|
464
|
-
return {
|
|
465
|
-
id: region.id,
|
|
466
|
-
type: 'clientColorRegionNode',
|
|
467
|
-
position: {
|
|
468
|
-
x: imageLeft + (region.x / 100) * imageDimensions.width,
|
|
469
|
-
y: imageStartY + (region.y / 100) * imageDimensions.height,
|
|
470
|
-
},
|
|
471
|
-
data: {
|
|
472
|
-
color: region.color,
|
|
473
|
-
x: region.x,
|
|
474
|
-
y: region.y,
|
|
475
|
-
width: region.width,
|
|
476
|
-
height: region.height,
|
|
477
|
-
isEditMode: false,
|
|
478
|
-
isClientMode: true,
|
|
479
|
-
savedColor: savedColor, // Only use assignments from client interactions
|
|
480
|
-
onFillColor: (color) => handleFillRegionColor(region.id, color),
|
|
481
|
-
onRemove: () => handleRemoveColorFromRegion(region.id), // Add remove handler for client mode
|
|
482
|
-
isHighlighted: highlightedRegionId === region.id, // Pass highlight state
|
|
483
|
-
},
|
|
484
|
-
draggable: false,
|
|
485
|
-
style: {
|
|
486
|
-
width: `${(region.width / 100) * imageDimensions.width}px`,
|
|
487
|
-
height: `${(region.height / 100) * imageDimensions.height}px`,
|
|
488
|
-
pointerEvents: (savedColor ? 'none' : 'auto'), // Disable pointer events when ColorNode is assigned
|
|
489
|
-
},
|
|
490
|
-
zIndex: 0, // Ensure regions are below color nodes
|
|
491
|
-
};
|
|
492
|
-
});
|
|
493
|
-
}, [
|
|
494
|
-
imageLoaded,
|
|
495
|
-
imageDimensions,
|
|
496
|
-
questionData.regionNodes,
|
|
497
|
-
regionAssignments,
|
|
498
|
-
handleFillRegionColor,
|
|
499
|
-
handleRemoveColorFromRegion,
|
|
500
|
-
highlightedRegionId,
|
|
501
|
-
]);
|
|
502
|
-
// Update nodes when dependencies change
|
|
503
|
-
useEffect(() => {
|
|
504
|
-
// Skip updating nodes if we're currently dragging a node
|
|
505
|
-
// This prevents position reset during drag operations
|
|
506
|
-
if (isDraggingNodeRef.current) {
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
// Don't include unassigned region as a React Flow node - render it as background instead
|
|
510
|
-
const allNodes = [...regionFlowNodes, ...colorFlowNodes, ...colorNodesInRegions];
|
|
511
|
-
setNodes(allNodes);
|
|
512
|
-
}, [
|
|
513
|
-
colorFlowNodes,
|
|
514
|
-
colorNodesInRegions,
|
|
515
|
-
regionFlowNodes,
|
|
516
|
-
setNodes,
|
|
517
|
-
imageLoaded,
|
|
518
|
-
imageDimensions,
|
|
519
|
-
]);
|
|
520
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: config.clientTitle }), _jsx("p", { className: "text-sm text-gray-500", children: config.clientSubtitle })] }) }), questionData.instructions && questionData.instructions.length > 0 && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-2.5", children: [_jsx("svg", { className: "h-5 w-5 text-blue-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" }) }), _jsx("h3", { className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" })] }), _jsx("div", { className: "divide-y divide-gray-50 mt-2", children: questionData.instructions.map((instruction, index) => (_jsx("div", { className: "flex items-center gap-3 px-5 py-2 transition-colors hover:bg-gray-50", children: _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: [index + 1, ". ", instruction] }) }, index))) })] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg", children: [_jsx("div", { className: "border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-red-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-green-400" }), _jsx("span", { className: "ml-3 text-sm font-medium text-gray-500", children: "Workspace" })] }) }), _jsxs("div", { className: "relative bg-gradient-to-br from-slate-50 to-gray-100", style: { height: '800px', width: '100%' }, ref: flowRef, children: [_jsx("div", { className: "pointer-events-none absolute", style: {
|
|
521
|
-
left: `${imageStartX}px`,
|
|
522
|
-
top: `${imageStartY}px`,
|
|
523
|
-
width: imageDimensions.width ? `${imageDimensions.width}px` : 'auto',
|
|
524
|
-
height: imageDimensions.height ? `${imageDimensions.height}px` : 'auto',
|
|
525
|
-
zIndex: 0,
|
|
526
|
-
}, children: isLoadingImage ? (_jsx("div", { className: "flex h-full w-full items-center justify-center text-gray-500", children: "\u0110ang t\u1EA3i h\u00ECnh \u1EA3nh..." })) : backgroundImagePreviewUrl ? (_jsx("img", { ref: imageRef, src: backgroundImagePreviewUrl, alt: "Background", className: "h-full w-full object-contain", style: imageDimensions.width && imageDimensions.height
|
|
527
|
-
? {
|
|
528
|
-
width: `${imageDimensions.width}px`,
|
|
529
|
-
height: `${imageDimensions.height}px`,
|
|
530
|
-
}
|
|
531
|
-
: undefined, onLoad: handleImageLoad, onError: () => {
|
|
532
|
-
toast.error('Không thể load ảnh!');
|
|
533
|
-
setImageLoaded(false);
|
|
534
|
-
} })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center text-gray-500", children: "Kh\u00F4ng c\u00F3 h\u00ECnh \u1EA3nh" })) }), _jsx(ReactFlow, { nodes: nodes, edges: [], onNodesChange: reactFlowOnNodesChange, onNodeDragStart: onNodeDragStart, onNodeDrag: onNodeDrag, onNodeDragStop: onNodeDragStop, onEdgesChange: onEdgesChange, nodeTypes: nodeTypes, defaultViewport: { x: 0, y: 0, zoom: 1 }, fitView: false, nodesDraggable: true, nodesConnectable: false, elementsSelectable: false, panOnDrag: false, panOnScroll: false, zoomOnScroll: false, zoomOnDoubleClick: false, zoomOnPinch: false, preventScrolling: true, autoPanOnNodeDrag: false, autoPanOnConnect: false, proOptions: { hideAttribution: true }, style: {
|
|
535
|
-
width: '100%',
|
|
536
|
-
height: '100%',
|
|
537
|
-
position: 'absolute',
|
|
538
|
-
top: 0,
|
|
539
|
-
left: 0,
|
|
540
|
-
zIndex: 10,
|
|
541
|
-
}, children: _jsx(Background, {}) })] })] }), isReviewMode && explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] }));
|
|
542
|
-
}
|
|
543
|
-
export function ColorRegionClient(props) {
|
|
544
|
-
return (_jsx(ReactFlowProvider, { children: _jsx(ColorRegionClientContent, { ...props }) }));
|
|
545
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as i,useRef as r,useEffect as o,useCallback as n,useMemo as l}from"react";import{toast as d}from"sonner";import{ReactFlow as s,Background as a,useNodesState as c,useEdgesState as h,ReactFlowProvider as u,useReactFlow as g}from"@xyflow/react";import"@xyflow/react/dist/style.css";import{Lightbulb as m}from"lucide-react";import{ColorNode as f}from"./ColorNode";import{ColorRegionNode as p}from"./ColorRegionNode";import{usePresignedFileUrl as x}from"../../../shared/lib/hooks";const y={clientColorNode:t=>e(f,{...t,variant:"client"}),clientColorRegionNode:t=>e(p,{...t})};function N({questionData:u,explanation:f,isReviewMode:p=!1,config:N}){const[b,w]=i(!1),[v,C]=i({width:0,height:0}),{previewUrl:M,isLoading:j}=x(u.backgroundImageUrl),O=r(null),k=r(null),[R,S,$]=c([]),[,,E]=h([]),[I,z]=i({}),D=n((e,t)=>{z(i=>({...i,[e]:t}))},[]),H=n(e=>{const t=I[e];t&&(z(t=>{const i={...t};return delete i[e],i}),S(e=>e.map(e=>e.data?.color===t?{...e,data:{...e.data,isConnected:!1}}:e)))},[I,S]),[W,F]=i(null),{getIntersectingNodes:L}=g(),P=(()=>{if(!b||v.width<=0)return 50;const e=k.current?.clientWidth||0;if(!e)return 50;const t=(e-v.width)/2;return Math.max(50,t)})(),V=r(!1),B=r(null),K=n((e,t)=>{if("clientColorNode"!==t.type)return;V.current=!0;const i=t.data?.color;if(!i||"string"!=typeof i)return;const r=t.data?.regionId,o=r||Object.keys(I).find(e=>I[e]===i);B.current=o||null,o&&(z(e=>{const t={...e};return delete t[o],t}),S(e=>e.map(e=>e.id!==o||"clientColorRegionNode"!==e.type?e:{...e,data:{...e.data,savedColor:""},style:{...e.style,pointerEvents:"auto"}})))},[I,S]),T=n((e,t)=>{if("clientColorNode"!==t.type)return;const i=t.data?.color;if(!i||"string"!=typeof i)return void F(null);const r=e=>{if("number"==typeof e)return Number.isFinite(e)?e:null;if("string"==typeof e){const t=parseFloat(e);return Number.isFinite(t)?t:null}return null},o=(e,t)=>{const i=t in e?e[t]:null;if("number"==typeof i&&Number.isFinite(i))return i;const o=e.measured?e.measured[t]:void 0;return r(o)},n=L(t,!0),l=n.filter(e=>"clientColorNode"===e.type).map(e=>e.id),d=n.filter(e=>"clientColorRegionNode"===e.type&&"unassigned-region"!==e.id),s=t.position.y,a=s>=0&&s<=100;d.length>0?(F(d[0].id),l.includes(t.id)||l.push(t.id)):F(a?"unassigned-region":null),S(e=>e.map(e=>"clientColorNode"===e.type?{...e,data:{...e.data,highlightClass:l.includes(e.id)||e.id===t.id?"":void 0,highlightStyle:l.includes(e.id)&&e.id===t.id&&d.length>0?(()=>{const t=(e=>{if(v.width<=0||v.height<=0)return null;const t=r(e.style?.width),i=r(e.style?.height);if(t&&i)return{width:Math.max(t,60),height:Math.max(i,40)};const o=e.data;return void 0===o?.width||void 0===o?.height||"number"!=typeof o.width||"number"!=typeof o.height?null:{width:Math.max(o.width/100*v.width,60),height:Math.max(o.height/100*v.height,40)}})(d[0]),i=o(e,"width"),n=o(e,"height");if(t&&i&&n)return{transform:`scale(${Number(t.width)/Number(i)}, ${Number(t.height)/Number(n)})`,transformOrigin:"center center",transition:"transform 180ms ease-out"}})():void 0,isHighlighted:l.includes(e.id)}}:e))},[L,v,S]),U=n((e,t)=>{if("clientColorNode"!==t.type)return;S(e=>e.map(e=>"clientColorNode"===e.type?{...e,data:{...e.data,highlightClass:void 0,highlightStyle:void 0,isHighlighted:!1}}:e));const i=t.data?.color;if(!i||"string"!=typeof i)return void F(null);const r=L(t,!0).filter(e=>"clientColorRegionNode"===e.type&&"unassigned-region"!==e.id),o=t.position.y,n=o>=0&&o<=100;if(F(null),r.length>0){const e=r[0],t=B.current,o=I[e.id];z(o&&o!==i?r=>{const n={...r};return n[e.id]=i,t&&(n[t]=o),n}:t=>({...t,[e.id]:i})),B.current=null}else if(n){const e=B.current;e&&z(t=>{const i={...t};return delete i[e],i});const r=u.colorNodes.find(e=>e.color===i);if(r){const o=e?Object.fromEntries(Object.entries(I).filter(([t])=>t!==e)):I,n=4,l=u.colorNodes.filter(e=>!Object.values(o).includes(e.color)||e.color===i).findIndex(e=>e.id===r.id),d={x:50+200*(l%n),y:20+80*Math.floor(l/n)};S(e=>e.map(e=>e.id===t.id?{...e,position:d}:e))}B.current=null}else{B.current&&z(e=>{const t={...e};return delete t[B.current],t});const e=u.colorNodes.find(e=>e.color===i);if(e){const r=B.current?Object.fromEntries(Object.entries(I).filter(([e])=>e!==B.current)):I,o=4,n=u.colorNodes.filter(e=>!Object.values(r).includes(e.color)||e.color===i).findIndex(t=>t.id===e.id),l={x:50+200*(n%o),y:20+80*Math.floor(n/o)};S(e=>e.map(e=>e.id===t.id?{...e,position:l}:e))}B.current=null}V.current=!1},[L,I,u.colorNodes,S]),q=l(()=>{if(!u.colorNodes||0===u.colorNodes.length)return[];const e=u.colorNodes.filter(e=>!Object.values(I).includes(e.color));return e.map((e,t)=>{const i=Math.floor(t/4),r=t%4;return{id:e.id,type:"clientColorNode",position:{x:50+200*r,y:20+80*i},data:{color:e.color,isEditMode:!1,isConnected:!1,displayMode:"compact",onRemove:()=>{const t=Object.keys(I).find(t=>I[t]===e.color);t&&z(e=>{const i={...e};return delete i[t],i})}},draggable:!0}})},[u.colorNodes,I]),A=l(()=>!b||v.width<=0?[]:u.regionNodes.filter(e=>I[e.id]).map(e=>{const t=I[e.id];if(!u.colorNodes.find(e=>e.color===t))return null;const i=k.current?.clientWidth||0,r=(i>0&&v.width>0?Math.max(50,(i-v.width)/2):50)+e.x/100*v.width,o=200+e.y/100*v.height,n=e.width/100*v.width,l=e.height/100*v.height,d=Math.max(n,60),s=Math.max(l,40);return{id:`color-in-region-${e.id}`,type:"clientColorNode",position:{x:r,y:o},data:{color:t,isEditMode:!1,isConnected:!0,regionId:e.id,onRemove:()=>H(e.id)},draggable:!0,width:d,height:s,style:{width:`${d}px`,height:`${s}px`,margin:0,padding:0,boxSizing:"border-box"}}}).filter(e=>null!==e),[b,v,u.regionNodes,u.colorNodes,I,H]),G=l(()=>{if(!b||v.width<=0)return[];const e=k.current?.clientWidth||0,t=e>0?(e-v.width)/2:50;return u.regionNodes.map(e=>{const i=I[e.id]||"";return{id:e.id,type:"clientColorRegionNode",position:{x:t+e.x/100*v.width,y:200+e.y/100*v.height},data:{color:e.color,x:e.x,y:e.y,width:e.width,height:e.height,isEditMode:!1,isClientMode:!0,savedColor:i,onFillColor:t=>D(e.id,t),onRemove:()=>H(e.id),isHighlighted:W===e.id},draggable:!1,style:{width:e.width/100*v.width+"px",height:e.height/100*v.height+"px",pointerEvents:i?"none":"auto"},zIndex:0}})},[b,v,u.regionNodes,I,D,H,W]);return o(()=>{if(V.current)return;const e=[...G,...q,...A];S(e)},[q,A,G,S,b,v]),t("div",{className:"space-y-4",children:[e("div",{className:"flex items-center justify-between",children:t("div",{children:[e("h2",{className:"text-lg font-semibold text-gray-900",children:N.clientTitle}),e("p",{className:"text-sm text-gray-500",children:N.clientSubtitle})]})}),u.instructions&&u.instructions.length>0&&t("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm",children:[t("div",{className:"flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-2.5",children:[e("svg",{className:"h-5 w-5 text-blue-600",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"})}),e("h3",{className:"text-sm font-semibold text-gray-800",children:"Hướng dẫn"})]}),e("div",{className:"divide-y divide-gray-50 mt-2",children:u.instructions.map((i,r)=>e("div",{className:"flex items-center gap-3 px-5 py-2 transition-colors hover:bg-gray-50",children:t("span",{className:"text-sm font-semibold text-gray-700",children:[r+1,". ",i]})},r))})]}),t("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg",children:[e("div",{className:"border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"h-2.5 w-2.5 rounded-full bg-red-400"}),e("div",{className:"h-2.5 w-2.5 rounded-full bg-yellow-400"}),e("div",{className:"h-2.5 w-2.5 rounded-full bg-green-400"}),e("span",{className:"ml-3 text-sm font-medium text-gray-500",children:"Workspace"})]})}),t("div",{className:"relative bg-gradient-to-br from-slate-50 to-gray-100",style:{height:"800px",width:"100%"},ref:k,children:[e("div",{className:"pointer-events-none absolute",style:{left:`${P}px`,top:"200px",width:v.width?`${v.width}px`:"auto",height:v.height?`${v.height}px`:"auto",zIndex:0},children:j?e("div",{className:"flex h-full w-full items-center justify-center text-gray-500",children:"Đang tải hình ảnh..."}):M?e("img",{ref:O,src:M,alt:"Background",className:"h-full w-full object-contain",style:v.width&&v.height?{width:`${v.width}px`,height:`${v.height}px`}:void 0,onLoad:()=>{if(O.current&&k.current){const e=k.current.clientWidth-100,t=k.current.clientHeight-260,i=O.current.naturalWidth||O.current.clientWidth,r=O.current.naturalHeight||O.current.clientHeight,o=e/i,n=t/r,l=Math.min(o,n,1);C({width:i*l,height:r*l}),w(!0)}},onError:()=>{d.error("Không thể load ảnh!"),w(!1)}}):e("div",{className:"flex h-full w-full items-center justify-center text-gray-500",children:"Không có hình ảnh"})}),e(s,{nodes:R,edges:[],onNodesChange:$,onNodeDragStart:K,onNodeDrag:T,onNodeDragStop:U,onEdgesChange:E,nodeTypes:y,defaultViewport:{x:0,y:0,zoom:1},fitView:!1,nodesDraggable:!0,nodesConnectable:!1,elementsSelectable:!1,panOnDrag:!1,panOnScroll:!1,zoomOnScroll:!1,zoomOnDoubleClick:!1,zoomOnPinch:!1,preventScrolling:!0,autoPanOnNodeDrag:!1,autoPanOnConnect:!1,proOptions:{hideAttribution:!0},style:{width:"100%",height:"100%",position:"absolute",top:0,left:0,zIndex:10},children:e(a,{})})]})]}),p&&f&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:t("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white",children:e(m,{className:"h-3.5 w-3.5"})}),t("div",{className:"flex-1",children:[e("span",{className:"text-xs font-semibold text-blue-700 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-gray-700 leading-relaxed",children:f})]})]})})]})}export function ColorRegionClient(t){return e(u,{children:e(N,{...t})})}
|