@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.8
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/components/exams/ExamCreator.js +1 -16
- package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
- package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
- package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
- package/dist/components/exams/take/components/question-renderers/index.js +0 -3
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
- package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
- package/dist/components/exams/take/types.d.ts +0 -65
- package/dist/components/exams/take/utils/answer-transformers.js +6 -60
- package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
- package/dist/components/exams/take/utils/question-transformers.js +0 -143
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/index.js +6 -0
- package/dist/components/questions/_shared/config/question-types.config.js +0 -24
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +5 -1
- package/dist/components/questions/_shared/types/index.d.ts +0 -1
- package/dist/components/questions/_shared/types/index.js +0 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
- package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
- package/dist/components/questions/components/ColorRegionCreator.js +66 -63
- package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
- package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
- package/dist/components/questions/components/index.d.ts +1 -0
- package/dist/components/questions/components/index.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +0 -8
- package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
- package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
- package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
- package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
- package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
- package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
- package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +7 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +5 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -5
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +65 -62
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.d.ts +6 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.js +145 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +6 -15
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +5 -4
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +79 -3
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/index.js +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.d.ts +2 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +74 -0
- package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
- package/dist/components/questions/viewer/QuestionViewer.js +0 -98
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -79
- package/dist/components/results/renderers/review-question-body-movers.js +2 -26
- package/dist/components/results/review-data.js +0 -4
- package/dist/components/ui/points-input.d.ts +1 -0
- package/dist/components/ui/points-input.js +12 -2
- package/dist/shared/constants/question-skills.js +0 -11
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
- package/dist/shared/lib/utils/question-reverse-transform.js +4 -23
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
- package/dist/shared/lib/utils/question-transform-validation.js +0 -40
- package/dist/shared/lib/utils/question-transform.js +0 -9
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/entities/question.types.d.ts +1 -11
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -1
- package/dist/shared/types/questions/index.d.ts +0 -3
- package/dist/shared/types/questions/index.js +0 -6
- package/package.json +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
- package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
- package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { ZoomIn, Check, X } from 'lucide-react';
|
|
5
|
-
import { ResolvedImage } from '../../../../../components/common/ResolvedImage';
|
|
6
|
-
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
7
|
-
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
8
|
-
// Image with zoom functionality
|
|
9
|
-
function QuestionImage({ imageUrl, alt }) {
|
|
10
|
-
const [isZoomed, setIsZoomed] = useState(false);
|
|
11
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative flex items-center justify-center", children: [_jsx(ResolvedImage, { src: imageUrl, alt: alt, className: "max-h-[180px] cursor-pointer rounded-lg object-contain shadow-md", onClick: () => setIsZoomed(true) }), _jsx("button", { type: "button", onClick: () => setIsZoomed(true), className: "absolute right-2 top-2 rounded-full bg-white/80 p-1.5 shadow-md hover:bg-white", "aria-label": "Zoom image", children: _jsx(ZoomIn, { className: "h-4 w-4 text-gray-600" }) })] }), isZoomed && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70", onClick: () => setIsZoomed(false), children: _jsx(ResolvedImage, { src: imageUrl, alt: `${alt} (zoomed)`, className: "max-h-[90vh] max-w-[90vw] rounded-lg object-contain", showLoadingState: false }) }))] }));
|
|
12
|
-
}
|
|
13
|
-
// Single Sub-Question Component
|
|
14
|
-
function SubQuestionItem({ subQuestion, selectedOptionId, onSelect, isReviewMode, id, }) {
|
|
15
|
-
// Review mode states
|
|
16
|
-
const isUserCorrect = isReviewMode && selectedOptionId === subQuestion.correctOptionId;
|
|
17
|
-
const getOptionStyle = (optionId) => {
|
|
18
|
-
if (!isReviewMode) {
|
|
19
|
-
return selectedOptionId === optionId
|
|
20
|
-
? 'border-blue-500 bg-[#f4f9ff]'
|
|
21
|
-
: 'border-blue-200 bg-white hover:border-blue-300 hover:bg-blue-50/30';
|
|
22
|
-
}
|
|
23
|
-
// Review mode
|
|
24
|
-
const isSelected = selectedOptionId === optionId;
|
|
25
|
-
const isCorrect = subQuestion.correctOptionId === optionId;
|
|
26
|
-
if (isCorrect) {
|
|
27
|
-
// ✅ Green if user answered correctly
|
|
28
|
-
// ⚪ Blue if user didn't answer or was wrong (show correct answer)
|
|
29
|
-
return isUserCorrect
|
|
30
|
-
? 'border-green-500 bg-green-50'
|
|
31
|
-
: 'border-blue-500 bg-blue-50';
|
|
32
|
-
}
|
|
33
|
-
if (isSelected && !isCorrect) {
|
|
34
|
-
// ❌ Red for wrong answer
|
|
35
|
-
return 'border-red-500 bg-red-50';
|
|
36
|
-
}
|
|
37
|
-
return 'border-blue-200 bg-white';
|
|
38
|
-
};
|
|
39
|
-
const getRadioStyle = (optionId) => {
|
|
40
|
-
if (!isReviewMode) {
|
|
41
|
-
return selectedOptionId === optionId
|
|
42
|
-
? 'border-blue-500 bg-blue-500'
|
|
43
|
-
: 'border-gray-300 bg-white';
|
|
44
|
-
}
|
|
45
|
-
// Review mode
|
|
46
|
-
const isSelected = selectedOptionId === optionId;
|
|
47
|
-
const isCorrect = subQuestion.correctOptionId === optionId;
|
|
48
|
-
if (isCorrect) {
|
|
49
|
-
return isUserCorrect
|
|
50
|
-
? 'border-green-500 bg-green-500'
|
|
51
|
-
: 'border-blue-500 bg-blue-500';
|
|
52
|
-
}
|
|
53
|
-
if (isSelected && !isCorrect) {
|
|
54
|
-
return 'border-red-500 bg-red-500';
|
|
55
|
-
}
|
|
56
|
-
return 'border-gray-300 bg-white';
|
|
57
|
-
};
|
|
58
|
-
// Get icon for review mode
|
|
59
|
-
const getReviewIcon = (optionId) => {
|
|
60
|
-
if (!isReviewMode)
|
|
61
|
-
return null;
|
|
62
|
-
const isSelected = selectedOptionId === optionId;
|
|
63
|
-
const isCorrect = subQuestion.correctOptionId === optionId;
|
|
64
|
-
if (isCorrect) {
|
|
65
|
-
return _jsx(Check, { className: "h-4 w-4 text-white" });
|
|
66
|
-
}
|
|
67
|
-
if (isSelected && !isCorrect) {
|
|
68
|
-
return _jsx(X, { className: "h-4 w-4 text-white" });
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
};
|
|
72
|
-
return (_jsxs("div", { id: id, className: "rounded-lg border border-gray-200 bg-white p-4 shadow-sm", children: [_jsxs("div", { className: "mb-3 flex items-start gap-3", children: [_jsx("span", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white", children: subQuestion.questionNumber }), _jsx(QuestionImage, { imageUrl: subQuestion.imageUrl, alt: `Question ${subQuestion.questionNumber}` })] }), subQuestion.questionText && (_jsx("p", { className: "mb-3 text-[17px] font-bold text-[#001590]", children: subQuestion.questionText })), _jsx("div", { className: "flex flex-col gap-2", children: subQuestion.options.map((option) => (_jsxs("button", { type: "button", onClick: () => !isReviewMode && onSelect(option.id), disabled: isReviewMode, className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 text-left transition-colors ${getOptionStyle(option.id)} ${isReviewMode ? 'cursor-default' : 'cursor-pointer'}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx("span", { className: "flex-1 text-sm text-gray-700", children: option.text }), _jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full border-2 ${getRadioStyle(option.id)}`, children: isReviewMode ? (getReviewIcon(option.id)) : (selectedOptionId === option.id && (_jsx("div", { className: "h-2 w-2 rounded-full bg-white" }))) })] }, option.id))) })] }));
|
|
73
|
-
}
|
|
74
|
-
export function MoversPictureChooseRenderer({ partNumber, partName, questionCount, instruction, subQuestions, answers, onAnswerChange, isReviewMode = false, }) {
|
|
75
|
-
return (_jsxs("div", { className: "flex h-full flex-col gap-4 overflow-y-auto p-6", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, questionCount: questionCount, partName: partName, isReviewMode: isReviewMode }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction, isReviewMode: isReviewMode })] }), _jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: subQuestions.map((subQuestion) => (_jsx(SubQuestionItem, { id: `question-${subQuestion.id}`, subQuestion: subQuestion, selectedOptionId: answers[subQuestion.id], onSelect: (optionId) => onAnswerChange(subQuestion.id, optionId), isReviewMode: isReviewMode }, subQuestion.id))) })] }));
|
|
76
|
-
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import type { MoversPictureFillBlankChooseRendererProps } from '../../types';
|
|
2
|
-
export declare function MoversPictureFillBlankChooseRenderer({ partNumber, partName, questionCount, instruction, subQuestions, answers, onAnswerChange, isReviewMode, }: MoversPictureFillBlankChooseRendererProps): import("react").JSX.Element;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { ZoomIn } from 'lucide-react';
|
|
5
|
-
import { ResolvedImage } from '../../../../../components/common/ResolvedImage';
|
|
6
|
-
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
7
|
-
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
8
|
-
// Image with zoom functionality
|
|
9
|
-
function QuestionImage({ imageUrl, alt }) {
|
|
10
|
-
const [isZoomed, setIsZoomed] = useState(false);
|
|
11
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative flex items-center justify-center", children: [_jsx(ResolvedImage, { src: imageUrl, alt: alt, className: "max-h-[150px] cursor-pointer rounded-lg object-contain shadow-md", onClick: () => setIsZoomed(true) }), _jsx("button", { type: "button", onClick: () => setIsZoomed(true), className: "absolute right-1 top-1 rounded-full bg-white/80 p-1 shadow-md hover:bg-white", "aria-label": "Zoom image", children: _jsx(ZoomIn, { className: "h-3 w-3 text-gray-600" }) })] }), isZoomed && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70", onClick: () => setIsZoomed(false), children: _jsx(ResolvedImage, { src: imageUrl, alt: `${alt} (zoomed)`, className: "max-h-[90vh] max-w-[90vw] rounded-lg object-contain", showLoadingState: false }) }))] }));
|
|
12
|
-
}
|
|
13
|
-
// Render question text with blank highlight
|
|
14
|
-
function QuestionWithBlank({ question, selectedAnswer, isReviewMode, isCorrect, }) {
|
|
15
|
-
// Split by {0} placeholder
|
|
16
|
-
const parts = question.split('{0}');
|
|
17
|
-
const getBlankStyle = () => {
|
|
18
|
-
if (!isReviewMode || !selectedAnswer) {
|
|
19
|
-
return 'border-blue-400 text-blue-600';
|
|
20
|
-
}
|
|
21
|
-
return isCorrect
|
|
22
|
-
? 'border-green-500 text-green-600'
|
|
23
|
-
: 'border-red-500 text-red-600';
|
|
24
|
-
};
|
|
25
|
-
return (_jsxs("p", { className: "text-sm font-medium text-gray-700", children: [parts[0], _jsx("span", { className: `mx-1 inline-block min-w-[60px] border-b-2 border-dashed px-2 text-center font-semibold ${getBlankStyle()}`, children: selectedAnswer || '______' }), parts[1]] }));
|
|
26
|
-
}
|
|
27
|
-
// Single Sub-Question Component
|
|
28
|
-
function SubQuestionItem({ subQuestion, selectedOptionId, onSelect, isReviewMode, id, }) {
|
|
29
|
-
const selectedOption = subQuestion.options.find((o) => o.id === selectedOptionId);
|
|
30
|
-
// Review mode states
|
|
31
|
-
const isUserCorrect = isReviewMode && selectedOptionId === subQuestion.correctOptionId;
|
|
32
|
-
const getOptionStyle = (optionId) => {
|
|
33
|
-
if (!isReviewMode) {
|
|
34
|
-
return selectedOptionId === optionId
|
|
35
|
-
? 'border-blue-500 bg-blue-50'
|
|
36
|
-
: 'border-gray-300 bg-white hover:border-blue-300';
|
|
37
|
-
}
|
|
38
|
-
// Review mode
|
|
39
|
-
const isSelected = selectedOptionId === optionId;
|
|
40
|
-
const isCorrect = subQuestion.correctOptionId === optionId;
|
|
41
|
-
if (isCorrect) {
|
|
42
|
-
// ✅ Green if user answered correctly
|
|
43
|
-
// ⚪ Blue if user didn't answer or was wrong (show correct answer)
|
|
44
|
-
return isUserCorrect
|
|
45
|
-
? 'border-green-500 bg-green-50'
|
|
46
|
-
: 'border-blue-500 bg-blue-50';
|
|
47
|
-
}
|
|
48
|
-
if (isSelected && !isCorrect) {
|
|
49
|
-
// ❌ Red for wrong answer
|
|
50
|
-
return 'border-red-500 bg-red-50';
|
|
51
|
-
}
|
|
52
|
-
return 'border-gray-300 bg-white';
|
|
53
|
-
};
|
|
54
|
-
const getTextStyle = (optionId) => {
|
|
55
|
-
if (!isReviewMode)
|
|
56
|
-
return 'text-gray-700';
|
|
57
|
-
const isSelected = selectedOptionId === optionId;
|
|
58
|
-
const isCorrect = subQuestion.correctOptionId === optionId;
|
|
59
|
-
if (isCorrect || isSelected) {
|
|
60
|
-
return 'text-gray-800 font-bold';
|
|
61
|
-
}
|
|
62
|
-
return 'text-gray-600';
|
|
63
|
-
};
|
|
64
|
-
return (_jsxs("div", { id: id, className: "rounded-lg border border-gray-200 bg-white p-4 shadow-sm", children: [_jsxs("div", { className: "mb-3 flex items-start gap-3", children: [_jsx("span", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-sm font-bold text-white", children: subQuestion.questionNumber }), _jsx(QuestionImage, { imageUrl: subQuestion.imageUrl, alt: `Question ${subQuestion.questionNumber}` })] }), _jsx("div", { className: "mb-3", children: _jsx(QuestionWithBlank, { question: subQuestion.question, selectedAnswer: selectedOption?.text, isReviewMode: isReviewMode, isCorrect: selectedOptionId === subQuestion.correctOptionId }) }), _jsx("div", { className: "flex flex-wrap gap-2", children: subQuestion.options.map((option) => (_jsxs("button", { type: "button", onClick: () => !isReviewMode && onSelect(option.id), disabled: isReviewMode, className: `rounded-lg border-2 px-3 py-1.5 text-sm transition-colors ${getOptionStyle(option.id)} ${getTextStyle(option.id)} ${isReviewMode ? 'cursor-default' : 'cursor-pointer'}`, children: [option.label, ". ", option.text] }, option.id))) })] }));
|
|
65
|
-
}
|
|
66
|
-
export function MoversPictureFillBlankChooseRenderer({ partNumber, partName, questionCount, instruction, subQuestions, answers, onAnswerChange, isReviewMode = false, }) {
|
|
67
|
-
return (_jsxs("div", { className: "flex h-full flex-col gap-4 overflow-y-auto p-6", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, questionCount: questionCount, partName: partName, isReviewMode: isReviewMode }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction, isReviewMode: isReviewMode })] }), _jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: subQuestions.map((subQuestion) => (_jsx(SubQuestionItem, { id: `question-${subQuestion.id}`, subQuestion: subQuestion, selectedOptionId: answers[subQuestion.id], onSelect: (optionId) => onAnswerChange(subQuestion.id, optionId), isReviewMode: isReviewMode }, subQuestion.id))) })] }));
|
|
68
|
-
}
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { QuestionPart } from '../../../utils/question-transformers';
|
|
2
|
-
interface SpeakingGNInterviewRendererProps {
|
|
3
|
-
part: QuestionPart;
|
|
4
|
-
answers: Record<string, unknown>;
|
|
5
|
-
onAnswerChange: (questionId: string, value: unknown) => void;
|
|
6
|
-
isReviewMode?: boolean;
|
|
7
|
-
onExit: () => void;
|
|
8
|
-
onPartComplete?: () => void;
|
|
9
|
-
onPartBack?: () => void;
|
|
10
|
-
speakingTheme?: string;
|
|
11
|
-
teacherVideoUrl?: string;
|
|
12
|
-
onTeacherIntroPlayed?: () => void;
|
|
13
|
-
skipTeacherVideo?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function SpeakingGNInterviewRenderer(props: SpeakingGNInterviewRendererProps): import("react").JSX.Element;
|
|
16
|
-
export {};
|
package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* SpeakingGNInterviewRenderer
|
|
5
|
-
*
|
|
6
|
-
* Renderer for GN_SPEAKING_INTERVIEW (GN Speaking Part 1 & Part 2).
|
|
7
|
-
* Simulates 2 candidates: virtual (pre-recorded) + real (student records).
|
|
8
|
-
*
|
|
9
|
-
* Flow per question:
|
|
10
|
-
* - If targetCandidate === 'VIRTUAL' -> auto-play virtual answer, no mic
|
|
11
|
-
* - If targetCandidate === 'REAL' -> show mic, student records
|
|
12
|
-
*
|
|
13
|
-
* Part 2 extension: When content.imageUrl exists, displays a booklet
|
|
14
|
-
* image (collage) above the question text for Discussion with Pictures.
|
|
15
|
-
*
|
|
16
|
-
* UI: Summer Sky theme with two game-style candidate avatars at bottom,
|
|
17
|
-
* question text shown inside a teal card with hide/show question toggle badge,
|
|
18
|
-
* audio player for examiner question.
|
|
19
|
-
*/
|
|
20
|
-
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
21
|
-
import Image from 'next/image';
|
|
22
|
-
import { usePresignedFileUrl } from '../../../../../../shared/lib/hooks';
|
|
23
|
-
import { useSpeakingRendererSetup } from '../../../hooks/useSpeakingRendererSetup';
|
|
24
|
-
import { getSpeakingLayout } from '../../../../../../components/themes';
|
|
25
|
-
// Summer Sky theme assets (shared GN/KFS badges)
|
|
26
|
-
import questionBadge from '../../../../../../components/themes/summer-sky/assets/images/question-badge-sunset.png';
|
|
27
|
-
import redBadge from '../../../../../../components/themes/summer-sky/assets/images/red-badge-sunset.png';
|
|
28
|
-
import blueBadge from '../../../../../../components/themes/summer-sky/assets/images/blue-badge-sunset.png';
|
|
29
|
-
import redWrapper from '../../../../../../components/themes/summer-sky/assets/images/red-wrapper-sunset.png';
|
|
30
|
-
import blueWrapper from '../../../../../../components/themes/summer-sky/assets/images/blue-wrapper-sunset.png';
|
|
31
|
-
import bingoAvatar from '../../../../../../components/themes/summer-sky/assets/images/robot-avatar.png';
|
|
32
|
-
import userAvatar from '../../../../../../components/themes/summer-sky/assets/images/student-avatar.png';
|
|
33
|
-
import icPlay from '../../../../../../components/themes/summer-sky/assets/images/ic-play-sunset.png';
|
|
34
|
-
import icPlayViolet from '../../../../../../components/themes/summer-sky/assets/images/ic-play-violet.webp';
|
|
35
|
-
import nextSmall from '../../../../../../components/themes/summer-sky/assets/images/next-small.webp';
|
|
36
|
-
function ProgressDots({ total, currentIndex, answeredIds, questionIds, virtualIndices }) {
|
|
37
|
-
return (_jsx("div", { className: "flex items-center gap-1.5 flex-wrap justify-center", children: Array.from({ length: total }, (_, i) => {
|
|
38
|
-
const isCurrent = i === currentIndex;
|
|
39
|
-
const isAnswered = answeredIds.has(questionIds[i]);
|
|
40
|
-
const isVirtual = virtualIndices.has(i);
|
|
41
|
-
return (_jsx("div", { className: `h-2.5 w-2.5 rounded-full border-2 transition-all ${isCurrent
|
|
42
|
-
? 'scale-125 border-yellow-300 bg-yellow-400 shadow-lg'
|
|
43
|
-
: isAnswered
|
|
44
|
-
? 'border-green-400 bg-green-400'
|
|
45
|
-
: isVirtual
|
|
46
|
-
? 'border-white/30 bg-white/10'
|
|
47
|
-
: 'border-white/50 bg-white/20'}`, title: isVirtual ? 'Câu HS ảo' : `Câu ${i + 1}` }, i));
|
|
48
|
-
}) }));
|
|
49
|
-
}
|
|
50
|
-
// ============================================
|
|
51
|
-
// Main component
|
|
52
|
-
// ============================================
|
|
53
|
-
export function SpeakingGNInterviewRenderer(props) {
|
|
54
|
-
const [isQuestionVisible, setIsQuestionVisible] = useState(true);
|
|
55
|
-
const [isVirtualPlaying, setIsVirtualPlaying] = useState(false);
|
|
56
|
-
const virtualAudioRef = useRef(null);
|
|
57
|
-
// Get group payload for virtual candidate config
|
|
58
|
-
const groupPayload = props.part.groupPayload;
|
|
59
|
-
const virtualName = groupPayload?.virtualStudentName || 'Há»c sinh AI';
|
|
60
|
-
// Determine which questions are virtual
|
|
61
|
-
const virtualIndices = useMemo(() => {
|
|
62
|
-
const indices = new Set();
|
|
63
|
-
props.part.questions.forEach((q, i) => {
|
|
64
|
-
const content = q.content;
|
|
65
|
-
if (content?.targetCandidate === 'VIRTUAL') {
|
|
66
|
-
indices.add(i);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return indices;
|
|
70
|
-
}, [props.part.questions]);
|
|
71
|
-
// Setup with mic always enabled - we'll conditionally show/hide based on virtual/real
|
|
72
|
-
const setup = useSpeakingRendererSetup({
|
|
73
|
-
...props,
|
|
74
|
-
maxDurationMs: 60000, // 60 seconds
|
|
75
|
-
hasMic: true,
|
|
76
|
-
});
|
|
77
|
-
const Layout = getSpeakingLayout('SUMMER_SKY');
|
|
78
|
-
const content = setup.currentQuestion?.content;
|
|
79
|
-
const isVirtualTurn = content?.targetCandidate === 'VIRTUAL';
|
|
80
|
-
const isRealTurn = !isVirtualTurn;
|
|
81
|
-
// Resolve presigned URLs
|
|
82
|
-
const { previewUrl: virtualAnswerAudioUrl } = usePresignedFileUrl(content?.virtualAnswer?.audioUrl ?? '');
|
|
83
|
-
const { previewUrl: bookletImageUrl } = usePresignedFileUrl(content?.imageUrl ?? '');
|
|
84
|
-
const hasBookletImage = !!content?.imageUrl && !!bookletImageUrl;
|
|
85
|
-
// Stop virtual audio when navigating to a different question
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (virtualAudioRef.current) {
|
|
88
|
-
virtualAudioRef.current.pause();
|
|
89
|
-
virtualAudioRef.current.currentTime = 0;
|
|
90
|
-
}
|
|
91
|
-
setIsVirtualPlaying(false);
|
|
92
|
-
}, [setup.currentQuestion?.id]);
|
|
93
|
-
// Play virtual answer audio
|
|
94
|
-
const handlePlayVirtualAnswer = useCallback(() => {
|
|
95
|
-
if (!virtualAnswerAudioUrl)
|
|
96
|
-
return;
|
|
97
|
-
if (isVirtualPlaying && virtualAudioRef.current) {
|
|
98
|
-
virtualAudioRef.current.pause();
|
|
99
|
-
setIsVirtualPlaying(false);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!virtualAudioRef.current) {
|
|
103
|
-
virtualAudioRef.current = new Audio(virtualAnswerAudioUrl);
|
|
104
|
-
virtualAudioRef.current.onended = () => setIsVirtualPlaying(false);
|
|
105
|
-
virtualAudioRef.current.onerror = () => setIsVirtualPlaying(false);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
virtualAudioRef.current.src = virtualAnswerAudioUrl;
|
|
109
|
-
}
|
|
110
|
-
virtualAudioRef.current.play();
|
|
111
|
-
setIsVirtualPlaying(true);
|
|
112
|
-
}, [virtualAnswerAudioUrl, isVirtualPlaying]);
|
|
113
|
-
// Cleanup audio on unmount
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
return () => {
|
|
116
|
-
if (virtualAudioRef.current) {
|
|
117
|
-
virtualAudioRef.current.pause();
|
|
118
|
-
virtualAudioRef.current = null;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}, []);
|
|
122
|
-
// Auto-play virtual answer after teacher audio ends (1s delay)
|
|
123
|
-
const handleTeacherAudioEnded = useCallback(() => {
|
|
124
|
-
if (!isVirtualTurn || !virtualAnswerAudioUrl)
|
|
125
|
-
return;
|
|
126
|
-
const timer = setTimeout(() => {
|
|
127
|
-
handlePlayVirtualAnswer();
|
|
128
|
-
}, 1000);
|
|
129
|
-
return () => clearTimeout(timer);
|
|
130
|
-
}, [isVirtualTurn, virtualAnswerAudioUrl, handlePlayVirtualAnswer]);
|
|
131
|
-
if (!setup.currentQuestion || !content)
|
|
132
|
-
return null;
|
|
133
|
-
// Progress tracking
|
|
134
|
-
const answeredIds = new Set(props.part.questions.map((q) => q.id).filter((id) => !!props.answers[id]));
|
|
135
|
-
const questionIds = props.part.questions.map((q) => q.id);
|
|
136
|
-
const headerRight = (_jsx(ProgressDots, { total: props.part.questions.length, currentIndex: setup.nav.currentIndex, answeredIds: answeredIds, questionIds: questionIds, virtualIndices: virtualIndices }));
|
|
137
|
-
return (_jsxs(Layout, { onExit: props.onExit, partName: props.part.name || `Part ${props.part.partNo}`, instruction: props.part.instructions || 'LISTEN AND ANSWER', audioUrl: content.audioUrl ?? '', questionNumber: setup.nav.questionNumberInPart, onPrev: setup.handlePrev, onNext: setup.handleNext, showPrev: setup.nav.showPrev, showNext: setup.nav.showNext, canGoPrev: setup.nav.canGoPrev, canGoNext: setup.nav.canGoNext, onAudioPlayingChange: setup.onAudioPlayingChange, recorder: props.isReviewMode || isVirtualTurn ? null : setup.recorder, showExistingBadge: isRealTurn && setup.showExistingBadge, teacherVideoUrl: props.teacherVideoUrl, onTeacherVideoEnded: props.onTeacherIntroPlayed, skipTeacherVideo: props.skipTeacherVideo, headerRight: headerRight, onAudioEnded: handleTeacherAudioEnded, children: [_jsxs("div", { className: "w-full h-full flex flex-col items-center relative", children: [_jsx("div", { className: "w-full flex-1", style: { minHeight: 0 }, children: _jsx("div", { className: "mx-auto", style: { width: '850px', maxWidth: '90%', height: hasBookletImage ? 'auto' : 'calc(100% - 100px)' }, children: _jsxs("div", { className: `w-full ${hasBookletImage ? '' : 'h-full'} flex flex-col items-center`, style: hasBookletImage ? {} : { paddingTop: '30px' }, children: [isQuestionVisible && content.questionText && (_jsxs("div", { className: "relative flex flex-col justify-center items-center", style: {
|
|
138
|
-
width: '882px',
|
|
139
|
-
maxWidth: '90%',
|
|
140
|
-
maxHeight: '80%',
|
|
141
|
-
paddingLeft: '77px',
|
|
142
|
-
paddingRight: '77px',
|
|
143
|
-
paddingTop: '26px',
|
|
144
|
-
paddingBottom: '26px',
|
|
145
|
-
backgroundColor: 'rgba(232, 136, 154, 0.15)',
|
|
146
|
-
borderRadius: '20px',
|
|
147
|
-
}, children: [_jsxs("div", { className: "absolute cursor-pointer", style: { top: '-19px' }, onClick: () => setIsQuestionVisible(false), children: [_jsx(Image, { src: questionBadge, alt: "", style: { width: '169px', height: '38px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center select-none", children: _jsx("span", { className: "font-normal text-center leading-5 text-white", style: { fontSize: '18px' }, children: "\u00E1\u00BA\u00A8n c\u00C3\u00A2u h\u00E1\u00BB\u008Fi" }) })] }), _jsx("div", { className: "max-w-full", children: _jsx("div", { className: "text-black font-bold leading-[26px] text-center max-w-full", style: { fontSize: '18px', fontFamily: '"Open Sans", sans-serif' }, children: _jsx("p", { children: content.questionText }) }) })] })), !isQuestionVisible && (_jsxs("div", { className: "cursor-pointer", onClick: () => setIsQuestionVisible(true), children: [_jsx(Image, { src: questionBadge, alt: "", style: { width: '169px', height: '38px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center select-none", style: { position: 'relative', top: '-30px' }, children: _jsx("span", { className: "font-normal text-center leading-5 text-white", style: { fontSize: '18px' }, children: "Hi\u00E1\u00BB\u2021n c\u00C3\u00A2u h\u00E1\u00BB\u008Fi" }) })] })), hasBookletImage && (_jsx("div", { className: "flex justify-center", style: { marginTop: '12px' }, children: _jsx("div", { className: "relative overflow-hidden", style: {
|
|
148
|
-
maxWidth: '100%',
|
|
149
|
-
borderRadius: '16px',
|
|
150
|
-
border: '3px solid rgba(230, 99, 93, 0.3)',
|
|
151
|
-
boxShadow: '0 4px 15px rgba(0,0,0,0.08)',
|
|
152
|
-
}, children: _jsx("img", { src: bookletImageUrl, alt: "Booklet", style: {
|
|
153
|
-
width: '100%',
|
|
154
|
-
height: '100%',
|
|
155
|
-
objectFit: 'cover',
|
|
156
|
-
} }) }) }))] }) }) }), _jsxs("div", { className: "flex justify-between select-none items-end", style: { width: '990px', maxWidth: '95%', paddingBottom: '40px', paddingLeft: '20px', paddingRight: '20px' }, children: [_jsxs("div", { className: `relative select-none transition-opacity duration-300 ${isVirtualTurn ? 'opacity-100' : 'opacity-50 grayscale'}`, children: [_jsx("div", { children: _jsxs("div", { className: "relative flex", children: [_jsx(Image, { src: redBadge, alt: "", style: { height: '57px', width: '258px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center", style: { paddingTop: '12px' }, children: _jsx("div", { className: "text-white leading-5 font-normal text-center m-0 w-full select-none", children: _jsx("p", { className: "m-0 truncate select-none", style: { fontSize: '18px', paddingLeft: '60px', paddingRight: '60px' }, children: virtualName }) }) })] }) }), _jsxs("div", { className: "absolute", style: { top: '7px', right: '-58px' }, children: [_jsx(Image, { src: redWrapper, alt: "", className: "relative", style: { width: '115px', height: '84px', zIndex: 0 } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center w-full h-full", children: _jsxs("div", { className: "relative", style: { width: '68px', height: '68px' }, children: [_jsx(Image, { src: bingoAvatar, alt: virtualName, width: 68, height: 68, className: "object-cover rounded-full select-none", style: { width: '68px', height: '68px' }, draggable: false }), _jsx("div", { className: "flex items-center justify-center absolute w-full h-full top-0 rounded-full text-white cursor-pointer", onClick: isVirtualTurn ? handlePlayVirtualAnswer : undefined, children: _jsx(Image, { src: icPlay, alt: "play", width: 16, height: 16, className: "opacity-80" }) })] }) })] }), isVirtualPlaying && (_jsx("div", { className: "absolute pointer-events-none", style: { top: '0px', right: '-130px', width: '70px', height: '84px', display: 'flex', alignItems: 'center' }, children: _jsxs("div", { className: "relative", style: { width: '70px', height: '70px' }, children: [_jsx("div", { style: {
|
|
157
|
-
position: 'absolute',
|
|
158
|
-
left: '0px',
|
|
159
|
-
top: '50%',
|
|
160
|
-
transform: 'translateY(-50%)',
|
|
161
|
-
width: '20px',
|
|
162
|
-
height: '34px',
|
|
163
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.95)',
|
|
164
|
-
borderRadius: '0 50% 50% 0',
|
|
165
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
166
|
-
animationDelay: '0s',
|
|
167
|
-
} }), _jsx("div", { style: {
|
|
168
|
-
position: 'absolute',
|
|
169
|
-
left: '12px',
|
|
170
|
-
top: '50%',
|
|
171
|
-
transform: 'translateY(-50%)',
|
|
172
|
-
width: '28px',
|
|
173
|
-
height: '50px',
|
|
174
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.7)',
|
|
175
|
-
borderRadius: '0 50% 50% 0',
|
|
176
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
177
|
-
animationDelay: '0.2s',
|
|
178
|
-
} }), _jsx("div", { style: {
|
|
179
|
-
position: 'absolute',
|
|
180
|
-
left: '24px',
|
|
181
|
-
top: '50%',
|
|
182
|
-
transform: 'translateY(-50%)',
|
|
183
|
-
width: '36px',
|
|
184
|
-
height: '66px',
|
|
185
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.45)',
|
|
186
|
-
borderRadius: '0 50% 50% 0',
|
|
187
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
188
|
-
animationDelay: '0.4s',
|
|
189
|
-
} })] }) }))] }), isVirtualPlaying && (_jsx("div", { className: "flex-1 flex justify-center items-center text-center", style: { paddingLeft: '40px', paddingRight: '40px', paddingBottom: '40px' }, children: _jsxs("p", { className: "m-0 select-none", style: {
|
|
190
|
-
fontSize: '16px',
|
|
191
|
-
fontWeight: 700,
|
|
192
|
-
fontStyle: 'italic',
|
|
193
|
-
color: '#1a1a2e',
|
|
194
|
-
lineHeight: '1.5',
|
|
195
|
-
fontFamily: '"Open Sans", sans-serif',
|
|
196
|
-
}, children: ["Ng\u00C6\u00B0\u00E1\u00BB\u009Di ch\u00C6\u00A1i kh\u00C3\u00A1c \u00C4\u2018ang tr\u00E1\u00BA\u00A3 l\u00E1\u00BB\u009Di.", _jsx("br", {}), "Vui l\u00C3\u00B2ng nghe xong \u0111\u1EC3 ti\u1EBFp t\u1EE5c."] }) })), isVirtualTurn && !isVirtualPlaying && (_jsxs("div", { className: "flex-1 flex flex-col justify-center items-center text-center", style: { paddingLeft: '40px', paddingRight: '40px', paddingBottom: '40px' }, children: [_jsxs("p", { className: "m-0 whitespace-nowrap", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: ["\u00E1\u00BA\u00A4n bi\u00E1\u00BB\u0192u t\u00C6\u00B0\u00E1\u00BB\u00A3ng", ' ', _jsx(Image, { src: icPlayViolet, alt: "", width: 20, height: 20, className: "inline-block" }), ' ', "tr\u00EAn avatar \u0111\u1EC3 nghe l\u1EA1i."] }), _jsxs("div", { className: "flex items-center", style: { gap: '4px' }, children: [_jsx("p", { className: "m-0", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: "\u1EA4n n\u00FAt" }), _jsx(Image, { src: nextSmall, alt: "next", width: 34, height: 34 }), _jsx("p", { className: "m-0", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: "\u0111\u1EC3 ti\u1EBFp t\u1EE5c." })] })] })), _jsxs("div", { className: `relative select-none transition-opacity duration-300 ${isRealTurn ? 'opacity-100' : 'opacity-50 grayscale'}`, children: [_jsx("div", { children: _jsxs("div", { className: "relative flex", children: [_jsx(Image, { src: blueBadge, alt: "", style: { height: '57px', width: '258px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center", style: { paddingTop: '12px' }, children: _jsx("div", { className: "text-white leading-5 font-normal text-center m-0 w-full select-none", children: _jsx("p", { className: "m-0 truncate select-none", style: { fontSize: '18px', paddingLeft: '60px', paddingRight: '60px' }, children: "B\u1EA1n" }) }) })] }) }), _jsxs("div", { className: "absolute", style: { top: '7px', left: '-58px' }, children: [_jsx(Image, { src: blueWrapper, alt: "", className: "relative", style: { width: '115px', height: '84px', zIndex: 0 } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center w-full h-full", children: _jsx("div", { className: "relative", style: { width: '68px', height: '68px' }, children: _jsx(Image, { src: userAvatar, alt: "B\u1EA1n", width: 68, height: 68, className: "object-cover rounded-full select-none", style: { width: '68px', height: '68px' }, draggable: false }) }) })] })] })] })] }), _jsx("style", { jsx: true, children: `
|
|
197
|
-
@keyframes soundWavePulse {
|
|
198
|
-
0% {
|
|
199
|
-
opacity: 0.2;
|
|
200
|
-
transform: translateY(-50%) scaleX(0.7);
|
|
201
|
-
}
|
|
202
|
-
50% {
|
|
203
|
-
opacity: 1;
|
|
204
|
-
transform: translateY(-50%) scaleX(1);
|
|
205
|
-
}
|
|
206
|
-
100% {
|
|
207
|
-
opacity: 0.2;
|
|
208
|
-
transform: translateY(-50%) scaleX(0.7);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
` })] }));
|
|
212
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ValidationRef } from './look-and-choose-answer.type';
|
|
2
|
-
export interface GNSpeakingInterviewData {
|
|
3
|
-
/** How admin provides the question prompt */
|
|
4
|
-
inputType: 'AUDIO' | 'TEXT';
|
|
5
|
-
/** Audio file URL — required when inputType is 'AUDIO' */
|
|
6
|
-
audioUrl?: string;
|
|
7
|
-
/** Question text — required when inputType is 'TEXT', backend converts via TTS */
|
|
8
|
-
questionText?: string;
|
|
9
|
-
/** Which candidate this question targets */
|
|
10
|
-
targetCandidate: 'A' | 'B';
|
|
11
|
-
/** Virtual student's pre-recorded answer text */
|
|
12
|
-
virtualAnswerText?: string;
|
|
13
|
-
/** Virtual student's pre-recorded answer audio URL */
|
|
14
|
-
virtualAnswerAudioUrl?: string;
|
|
15
|
-
/** Whether this is an Extended Response question */
|
|
16
|
-
isExtendedResponse?: boolean;
|
|
17
|
-
/** Optional sample answers for AI grading reference (for real candidate questions) */
|
|
18
|
-
sampleAnswers?: string[];
|
|
19
|
-
/** Explanation for review */
|
|
20
|
-
explanation?: string;
|
|
21
|
-
/** Points per question (0 for virtual) */
|
|
22
|
-
points?: number;
|
|
23
|
-
}
|
|
24
|
-
export interface GNSpeakingInterviewCreatorProps {
|
|
25
|
-
initialData?: GNSpeakingInterviewData;
|
|
26
|
-
onSave?: (data: GNSpeakingInterviewData) => void;
|
|
27
|
-
onCancel?: () => void;
|
|
28
|
-
onChange?: (data: GNSpeakingInterviewData) => void;
|
|
29
|
-
externalErrors?: string[];
|
|
30
|
-
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
31
|
-
validationRef?: React.MutableRefObject<ValidationRef | null>;
|
|
32
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { ValidationRef } from './look-and-choose-answer.type';
|
|
2
|
-
export type { ValidationRef };
|
|
3
|
-
export interface LookPictureFillWordHintSubQuestion {
|
|
4
|
-
id: string;
|
|
5
|
-
imageUrl: string;
|
|
6
|
-
word: string;
|
|
7
|
-
correctAnswer: string;
|
|
8
|
-
points?: number;
|
|
9
|
-
groupNumber?: number;
|
|
10
|
-
}
|
|
11
|
-
export interface LookPictureFillWordHintGroup {
|
|
12
|
-
number: number;
|
|
13
|
-
questionCount: number;
|
|
14
|
-
}
|
|
15
|
-
export interface LookPictureFillWordHintData {
|
|
16
|
-
subQuestions: LookPictureFillWordHintSubQuestion[];
|
|
17
|
-
hintLetters?: string[];
|
|
18
|
-
hintLettersByGroup?: Record<number, string[]>;
|
|
19
|
-
caseSensitive?: boolean;
|
|
20
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
21
|
-
}
|
|
22
|
-
export interface LookPictureFillWordHintCreatorProps {
|
|
23
|
-
initialData?: LookPictureFillWordHintData;
|
|
24
|
-
onSave?: (data: LookPictureFillWordHintData) => void;
|
|
25
|
-
onSaveToQuestionBank?: (data: LookPictureFillWordHintData) => void;
|
|
26
|
-
onCancel?: () => void;
|
|
27
|
-
onChange?: (data: LookPictureFillWordHintData) => void;
|
|
28
|
-
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
29
|
-
externalErrors?: string[];
|
|
30
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
31
|
-
questionIndexInPart?: number;
|
|
32
|
-
partId?: string;
|
|
33
|
-
validationRef?: React.MutableRefObject<ValidationRef | null>;
|
|
34
|
-
}
|
|
35
|
-
export interface LookPictureFillWordHintQuestionData {
|
|
36
|
-
subQuestions: LookPictureFillWordHintSubQuestion[];
|
|
37
|
-
hintLetters?: string[];
|
|
38
|
-
hintLettersByGroup?: Record<number, string[]>;
|
|
39
|
-
caseSensitive?: boolean;
|
|
40
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
41
|
-
}
|
|
42
|
-
export interface LookPictureFillWordHintClientProps {
|
|
43
|
-
questionData: LookPictureFillWordHintQuestionData;
|
|
44
|
-
onSubmit?: (answers: Record<string, string>) => void;
|
|
45
|
-
isReviewMode?: boolean;
|
|
46
|
-
userAnswer?: Record<string, string>;
|
|
47
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import type { GNSpeakingInterviewCreatorProps } from '../../_shared/types/gn-speaking-interview.type';
|
|
2
|
-
export declare function GNSpeakingInterviewCreator({ initialData, onSave, onChange, externalErrors, validationRef, }: GNSpeakingInterviewCreatorProps): import("react").JSX.Element;
|