@tinyweb_dev/oe-exam-sdk 0.2.3 → 0.2.5
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/take/components/QuestionRenderer.js +11 -3
- package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.d.ts +10 -0
- package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.js +38 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +5 -11
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.js +75 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.d.ts +18 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.js +92 -0
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +2 -1
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +2 -2
- package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/index.js +2 -0
- package/dist/components/exams/take/exam-taking.utils.d.ts +1 -0
- package/dist/components/exams/take/exam-taking.utils.js +9 -1
- package/dist/components/exams/take/types.d.ts +3 -2
- package/dist/components/exams/take/utils/answer-transformers.js +12 -0
- package/dist/components/exams/take/utils/question-transformers.d.ts +29 -1
- package/dist/components/exams/take/utils/question-transformers.js +98 -0
- package/dist/components/questions/_shared/config/question-types.config.js +12 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +9 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +49 -1
- package/dist/components/questions/_shared/types/index.d.ts +2 -0
- package/dist/components/questions/_shared/types/index.js +2 -0
- package/dist/components/questions/_shared/types/match-word-to-picture.type.d.ts +62 -0
- package/dist/components/questions/_shared/types/match-word-to-picture.type.js +1 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +42 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.js +2 -0
- package/dist/components/questions/_shared/types/write-a-short-letter.type.d.ts +4 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +2 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +4 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +13 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +32 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.d.ts +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +42 -6
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +40 -20
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.d.ts +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +5 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +31 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +267 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +65 -0
- package/dist/components/questions/types/match-word-to-picture/WordBankEditor.d.ts +8 -0
- package/dist/components/questions/types/match-word-to-picture/WordBankEditor.js +15 -0
- package/dist/components/questions/types/match-word-to-picture/index.d.ts +6 -0
- package/dist/components/questions/types/match-word-to-picture/index.js +6 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.js +53 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.d.ts +4 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.js +75 -0
- package/dist/components/questions/types/match-word-to-picture/register.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/register.js +55 -0
- package/dist/components/questions/types/match-word-to-picture/transform.d.ts +11 -0
- package/dist/components/questions/types/match-word-to-picture/transform.js +43 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +25 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +144 -0
- package/dist/components/questions/types/true-false-correct-group/index.d.ts +5 -0
- package/dist/components/questions/types/true-false-correct-group/index.js +5 -0
- package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.d.ts +4 -0
- package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.js +79 -0
- package/dist/components/questions/types/true-false-correct-group/register.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/register.js +36 -0
- package/dist/components/questions/types/true-false-correct-group/transform.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/transform.js +49 -0
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +27 -3
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +8 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +4 -0
- package/dist/components/questions/types/write-a-short-letter/transform.js +4 -0
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.d.ts +8 -0
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.js +11 -0
- package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.d.ts +2 -1
- package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.js +2 -2
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.d.ts +9 -0
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +11 -0
- package/dist/components/questions/viewer/MatchWordToPictureViewer.d.ts +9 -0
- package/dist/components/questions/viewer/MatchWordToPictureViewer.js +8 -0
- package/dist/components/questions/viewer/QuestionViewer.js +58 -9
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +9 -0
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -0
- package/dist/components/questions/viewer/index.d.ts +4 -0
- package/dist/components/questions/viewer/index.js +4 -0
- package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +4 -9
- package/dist/components/results/renderers/review-question-body-movers.js +39 -3
- package/dist/components/results/review-data.js +22 -0
- package/dist/components/themes/english-certification/EcWritingTask.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +4 -0
- package/dist/shared/lib/hooks/usePresignedFileUrl.d.ts +3 -1
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +30 -12
- package/dist/shared/lib/utils/question-reverse-transform.js +111 -2
- package/dist/shared/lib/utils/question-transform.js +4 -0
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
- package/dist/shared/types/questions/index.d.ts +2 -0
- package/dist/shared/types/questions/index.js +4 -0
- package/dist/shared/types/questions/match-word-to-picture.d.ts +35 -0
- package/dist/shared/types/questions/match-word-to-picture.js +5 -0
- package/dist/shared/types/questions/true-false-correct-group.d.ts +39 -0
- package/dist/shared/types/questions/true-false-correct-group.js +1 -0
- package/package.json +7 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Each transformer handles specific question type data structure.
|
|
6
6
|
*/
|
|
7
7
|
import type { FillBlankQuestion, ChooseBestAnswerQuestion, PictureChooseSubQuestion, PictureFillBlankSubQuestion, ReadingPassageQuestion, LabelThePictureLabel, FillWordHintSubQuestion, ColoringHintItem, ColoringRegion } from '../types';
|
|
8
|
-
export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
|
|
8
|
+
export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
|
|
9
9
|
export interface ApiQuestion {
|
|
10
10
|
id: string;
|
|
11
11
|
content: any;
|
|
@@ -184,6 +184,7 @@ export declare function transformWriteShortLetter(question: ApiQuestion): {
|
|
|
184
184
|
minWords?: number;
|
|
185
185
|
maxWords?: number;
|
|
186
186
|
guideImages?: string[];
|
|
187
|
+
passage?: string;
|
|
187
188
|
};
|
|
188
189
|
export declare function transformColorObjects(questions: ApiQuestion[]): {
|
|
189
190
|
instruction: string;
|
|
@@ -269,6 +270,21 @@ export declare function transformTrueFalseGroup(questions: ApiQuestion[]): {
|
|
|
269
270
|
imageUrl?: string;
|
|
270
271
|
groupContent?: string;
|
|
271
272
|
};
|
|
273
|
+
export interface TrueFalseCorrectItemAnswer {
|
|
274
|
+
isTrue: boolean | null;
|
|
275
|
+
correction: string;
|
|
276
|
+
}
|
|
277
|
+
export interface TrueFalseCorrectGroupQuestion {
|
|
278
|
+
id: string;
|
|
279
|
+
questionNumber: number;
|
|
280
|
+
question: string;
|
|
281
|
+
isExample: boolean;
|
|
282
|
+
exampleCorrectAnswer?: TrueFalseCorrectItemAnswer;
|
|
283
|
+
}
|
|
284
|
+
export declare function transformTrueFalseCorrectGroup(questions: ApiQuestion[]): {
|
|
285
|
+
questions: TrueFalseCorrectGroupQuestion[];
|
|
286
|
+
instruction: string;
|
|
287
|
+
};
|
|
272
288
|
export interface WordOrderingQuestion {
|
|
273
289
|
id: string;
|
|
274
290
|
questionNumber: number;
|
|
@@ -321,6 +337,18 @@ export declare function transformMatchByWritingAnswer(questions: ApiQuestion[]):
|
|
|
321
337
|
askerName?: string;
|
|
322
338
|
answererName?: string;
|
|
323
339
|
};
|
|
340
|
+
export interface MatchWordToPictureQuestion {
|
|
341
|
+
id: string;
|
|
342
|
+
questionNumber: number;
|
|
343
|
+
imageUrl?: string;
|
|
344
|
+
isExample: boolean;
|
|
345
|
+
exampleAnswer?: string;
|
|
346
|
+
}
|
|
347
|
+
export declare function transformMatchWordToPicture(questions: ApiQuestion[]): {
|
|
348
|
+
questions: MatchWordToPictureQuestion[];
|
|
349
|
+
wordBank: string[];
|
|
350
|
+
instruction: string;
|
|
351
|
+
};
|
|
324
352
|
export declare function transformWriteSentences(questions: ApiQuestion[]): {
|
|
325
353
|
questions: Array<{
|
|
326
354
|
id: string;
|
|
@@ -428,23 +428,43 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
|
428
428
|
...(isObj && opt.imageUrl ? { imageUrl: opt.imageUrl } : {}),
|
|
429
429
|
};
|
|
430
430
|
});
|
|
431
|
+
const toQuestionImageUrl = (value) => {
|
|
432
|
+
if (typeof value === 'string') {
|
|
433
|
+
const trimmed = value.trim();
|
|
434
|
+
return trimmed !== '' ? trimmed : undefined;
|
|
435
|
+
}
|
|
436
|
+
if (!Array.isArray(value)) {
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
const urls = value
|
|
440
|
+
.filter((url) => typeof url === 'string')
|
|
441
|
+
.map((url) => url.trim())
|
|
442
|
+
.filter((url) => url !== '');
|
|
443
|
+
if (urls.length === 0) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
return urls.length === 1 ? urls[0] : urls;
|
|
447
|
+
};
|
|
431
448
|
let example;
|
|
432
449
|
if (exampleItem) {
|
|
433
450
|
const exampleIndex = items.indexOf(exampleItem);
|
|
434
451
|
const exOptions = mapOptions(exampleItem.content?.options, `${parent.id}-ex`);
|
|
435
452
|
const exCorrect = exampleItem.correctAnswer?.answer || parentCorrect[exampleIndex] || exOptions[0]?.id || '';
|
|
453
|
+
const exampleImageUrl = toQuestionImageUrl(exampleItem.content?.imageUrl);
|
|
436
454
|
example = {
|
|
437
455
|
questionNumber: exampleItem.questionNumber || exampleIndex + 1,
|
|
438
456
|
questionText: exampleItem.content?.question || '',
|
|
439
457
|
options: exOptions,
|
|
440
458
|
correctOptionId: exCorrect,
|
|
441
459
|
optionType: (exampleItem.content?.optionType || 'text'),
|
|
460
|
+
...(exampleImageUrl !== undefined ? { imageUrl: exampleImageUrl } : {}),
|
|
442
461
|
};
|
|
443
462
|
}
|
|
444
463
|
const transformed = regularItems.map((item) => {
|
|
445
464
|
const itemIndex = items.indexOf(item);
|
|
446
465
|
const options = mapOptions(item.content?.options, `${parent.id}-item-${itemIndex}`);
|
|
447
466
|
const correctId = item.correctAnswer?.answer || parentCorrect[itemIndex] || '';
|
|
467
|
+
const itemImageUrl = toQuestionImageUrl(item.content?.imageUrl);
|
|
448
468
|
return {
|
|
449
469
|
id: `${parent.id}-item-${itemIndex}`,
|
|
450
470
|
questionNumber: item.questionNumber || itemIndex + 1,
|
|
@@ -453,6 +473,7 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
|
453
473
|
correctOptionId: correctId,
|
|
454
474
|
correctOptionIds: correctId ? [correctId] : [],
|
|
455
475
|
optionType: (item.content?.optionType || 'text'),
|
|
476
|
+
...(itemImageUrl !== undefined ? { imageUrl: itemImageUrl } : {}),
|
|
456
477
|
};
|
|
457
478
|
});
|
|
458
479
|
return {
|
|
@@ -486,8 +507,19 @@ export function transformChooseCorrectAdjective(questions) {
|
|
|
486
507
|
instruction: 'CHOOSE THE CORRECT ADJECTIVE',
|
|
487
508
|
};
|
|
488
509
|
}
|
|
510
|
+
function extractWriteLetterPassage(content) {
|
|
511
|
+
const raw = content?.passage ?? content?.passages;
|
|
512
|
+
if (typeof raw === 'string' && raw.trim())
|
|
513
|
+
return raw;
|
|
514
|
+
if (Array.isArray(raw)) {
|
|
515
|
+
const parts = raw.filter((item) => typeof item === 'string' && item.trim().length > 0);
|
|
516
|
+
return parts.length ? parts.join('\n\n') : undefined;
|
|
517
|
+
}
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
489
520
|
export function transformWriteShortLetter(question) {
|
|
490
521
|
const content = question.content;
|
|
522
|
+
const passage = extractWriteLetterPassage(content);
|
|
491
523
|
// New structure: use instructions.mainInstruction as prompt, informations as requirements
|
|
492
524
|
if (content.instructions?.mainInstruction || content.informations?.length) {
|
|
493
525
|
return {
|
|
@@ -496,6 +528,7 @@ export function transformWriteShortLetter(question) {
|
|
|
496
528
|
minWords: content.minWords,
|
|
497
529
|
maxWords: content.maxWords,
|
|
498
530
|
guideImages: content.guideImages,
|
|
531
|
+
passage,
|
|
499
532
|
};
|
|
500
533
|
}
|
|
501
534
|
// Legacy structure: build prompt from contexts array
|
|
@@ -508,6 +541,7 @@ export function transformWriteShortLetter(question) {
|
|
|
508
541
|
minWords: content.minWords,
|
|
509
542
|
maxWords: content.maxWords,
|
|
510
543
|
guideImages: content.guideImages,
|
|
544
|
+
passage,
|
|
511
545
|
};
|
|
512
546
|
}
|
|
513
547
|
export function transformColorObjects(questions) {
|
|
@@ -889,6 +923,45 @@ export function transformTrueFalseGroup(questions) {
|
|
|
889
923
|
instruction: content?.meta?.instruction || 'READ AND CHOOSE TRUE OR FALSE',
|
|
890
924
|
};
|
|
891
925
|
}
|
|
926
|
+
function parseTrueFalseCorrectAnswer(raw) {
|
|
927
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
928
|
+
return undefined;
|
|
929
|
+
}
|
|
930
|
+
const obj = raw;
|
|
931
|
+
if (typeof obj.isTrue !== 'boolean') {
|
|
932
|
+
return undefined;
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
isTrue: obj.isTrue,
|
|
936
|
+
correction: obj.isTrue ? '' : (typeof obj.correction === 'string' ? obj.correction : ''),
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
export function transformTrueFalseCorrectGroup(questions) {
|
|
940
|
+
const parent = questions[0];
|
|
941
|
+
const content = parent?.content;
|
|
942
|
+
const items = Array.isArray(content?.items) ? content.items : [];
|
|
943
|
+
const parentCorrect = Array.isArray(parent?.correct_answer?.answer)
|
|
944
|
+
? parent.correct_answer.answer
|
|
945
|
+
: [];
|
|
946
|
+
const transformed = items.map((item, index) => {
|
|
947
|
+
const isExample = item.isExample === true;
|
|
948
|
+
const exampleCorrectAnswer = isExample
|
|
949
|
+
? parseTrueFalseCorrectAnswer(item.correctAnswer?.answer ?? parentCorrect[index])
|
|
950
|
+
: undefined;
|
|
951
|
+
return {
|
|
952
|
+
id: `${parent.id}-item-${index}`,
|
|
953
|
+
questionNumber: item.questionNumber || index + 1,
|
|
954
|
+
question: item.content?.question || '',
|
|
955
|
+
isExample,
|
|
956
|
+
exampleCorrectAnswer,
|
|
957
|
+
};
|
|
958
|
+
});
|
|
959
|
+
return {
|
|
960
|
+
questions: transformed,
|
|
961
|
+
instruction: content?.meta?.instruction
|
|
962
|
+
|| 'Tick (✓) the true and cross (✗) the false sentences. Correct the false sentences.',
|
|
963
|
+
};
|
|
964
|
+
}
|
|
892
965
|
export function transformWordOrdering(questions) {
|
|
893
966
|
const transformed = questions.map((q) => {
|
|
894
967
|
const content = q.content;
|
|
@@ -993,6 +1066,31 @@ export function transformMatchByWritingAnswer(questions) {
|
|
|
993
1066
|
answererName: firstConversationQ?.answererName,
|
|
994
1067
|
};
|
|
995
1068
|
}
|
|
1069
|
+
export function transformMatchWordToPicture(questions) {
|
|
1070
|
+
const groupPayload = questions[0]?.questionGroup?.payload;
|
|
1071
|
+
const wordBank = Array.isArray(groupPayload?.wordBank)
|
|
1072
|
+
? groupPayload.wordBank.filter((word) => typeof word === 'string' && word.trim().length > 0)
|
|
1073
|
+
: [];
|
|
1074
|
+
const instruction = typeof groupPayload?.instruction === 'string' && groupPayload.instruction.trim()
|
|
1075
|
+
? groupPayload.instruction.trim()
|
|
1076
|
+
: 'Look and complete.';
|
|
1077
|
+
const transformed = questions.map((q) => {
|
|
1078
|
+
const content = q.content;
|
|
1079
|
+
const isExample = q.is_example || content.isExample || false;
|
|
1080
|
+
return {
|
|
1081
|
+
id: q.id,
|
|
1082
|
+
questionNumber: q.question_number,
|
|
1083
|
+
imageUrl: content.imageUrl,
|
|
1084
|
+
isExample,
|
|
1085
|
+
exampleAnswer: isExample ? q.correct_answer?.answer : undefined,
|
|
1086
|
+
};
|
|
1087
|
+
});
|
|
1088
|
+
return {
|
|
1089
|
+
questions: transformed,
|
|
1090
|
+
wordBank,
|
|
1091
|
+
instruction,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
996
1094
|
export function transformWriteSentences(questions) {
|
|
997
1095
|
const firstQuestion = questions[0];
|
|
998
1096
|
const content = firstQuestion?.content;
|
|
@@ -172,6 +172,11 @@ export const QUESTION_TYPE_CONFIG = {
|
|
|
172
172
|
requiresContent: true,
|
|
173
173
|
hasDedicatedComponent: true,
|
|
174
174
|
},
|
|
175
|
+
TRUE_FALSE_CORRECT_GROUP: {
|
|
176
|
+
label: 'Đúng/Sai + sửa câu (nhóm)',
|
|
177
|
+
requiresContent: true,
|
|
178
|
+
hasDedicatedComponent: true,
|
|
179
|
+
},
|
|
175
180
|
CROSS_OUT_WORD_GROUP: {
|
|
176
181
|
label: 'Cross out word (group)',
|
|
177
182
|
requiresContent: true,
|
|
@@ -227,6 +232,11 @@ export const QUESTION_TYPE_CONFIG = {
|
|
|
227
232
|
requiresContent: true,
|
|
228
233
|
hasDedicatedComponent: true,
|
|
229
234
|
},
|
|
235
|
+
MATCH_WORD_TO_PICTURE: {
|
|
236
|
+
label: 'Nối từ với tranh',
|
|
237
|
+
requiresContent: true,
|
|
238
|
+
hasDedicatedComponent: true,
|
|
239
|
+
},
|
|
230
240
|
WRITE_SENTENCES: {
|
|
231
241
|
label: 'Viết câu',
|
|
232
242
|
requiresContent: true,
|
|
@@ -343,6 +353,7 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
|
|
|
343
353
|
'CHOOSE_THE_CORRECT_ANSWER',
|
|
344
354
|
'CHOOSE_THE_CORRECT_ANSWER_GROUP',
|
|
345
355
|
'TRUE_FALSE_GROUP',
|
|
356
|
+
'TRUE_FALSE_CORRECT_GROUP',
|
|
346
357
|
'CROSS_OUT_WORD_GROUP',
|
|
347
358
|
'WRITE_A_SHORT_LETTER',
|
|
348
359
|
'WRITE_CORRECT_VERB_FORM',
|
|
@@ -353,6 +364,7 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
|
|
|
353
364
|
'WORD_ORDERING',
|
|
354
365
|
'WORD_FILL_PARAGRAPH',
|
|
355
366
|
'MATCH_BY_WRITING_ANSWER',
|
|
367
|
+
'MATCH_WORD_TO_PICTURE',
|
|
356
368
|
'WRITE_SENTENCES',
|
|
357
369
|
'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP',
|
|
358
370
|
'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE',
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export interface ChooseTheCorrectAnswerGroupOptionData {
|
|
2
2
|
id: string;
|
|
3
3
|
text: string;
|
|
4
|
+
imageUrl?: string;
|
|
4
5
|
}
|
|
6
|
+
export declare function getChooseAnswerGroupOptionImageSrc(option: Pick<ChooseTheCorrectAnswerGroupOptionData, 'imageUrl' | 'text'>): string;
|
|
7
|
+
export declare function isChooseAnswerGroupOptionFilled(option: Pick<ChooseTheCorrectAnswerGroupOptionData, 'imageUrl' | 'text'>, optionType?: 'text' | 'image'): boolean;
|
|
8
|
+
export type ChooseTheCorrectAnswerGroupImageUrl = string | string[];
|
|
9
|
+
export declare function toChooseAnswerGroupImageUrls(value?: ChooseTheCorrectAnswerGroupImageUrl): string[];
|
|
10
|
+
export declare function toEditableChooseAnswerGroupImageUrls(value?: ChooseTheCorrectAnswerGroupImageUrl): string[];
|
|
11
|
+
export declare function fromChooseAnswerGroupImageUrls(urls: string[]): ChooseTheCorrectAnswerGroupImageUrl | undefined;
|
|
5
12
|
export interface ChooseTheCorrectAnswerGroupItemData {
|
|
6
13
|
question: string;
|
|
7
14
|
optionType: 'text' | 'image';
|
|
@@ -10,6 +17,7 @@ export interface ChooseTheCorrectAnswerGroupItemData {
|
|
|
10
17
|
isExample?: boolean;
|
|
11
18
|
points: number;
|
|
12
19
|
questionNumber: number;
|
|
20
|
+
imageUrl?: ChooseTheCorrectAnswerGroupImageUrl;
|
|
13
21
|
}
|
|
14
22
|
export interface ChooseTheCorrectAnswerGroupData {
|
|
15
23
|
instruction: string;
|
|
@@ -34,4 +42,5 @@ export interface ChooseTheCorrectAnswerGroupClientProps {
|
|
|
34
42
|
questionData: ChooseTheCorrectAnswerGroupData;
|
|
35
43
|
isReviewMode?: boolean;
|
|
36
44
|
userAnswers?: string[];
|
|
45
|
+
onAnswerChange?: (answers: string[]) => void;
|
|
37
46
|
}
|
|
@@ -1 +1,49 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export function getChooseAnswerGroupOptionImageSrc(option) {
|
|
2
|
+
if (typeof option.imageUrl === 'string' && option.imageUrl.trim() !== '') {
|
|
3
|
+
return option.imageUrl.trim();
|
|
4
|
+
}
|
|
5
|
+
if (typeof option.text === 'string' && option.text.trim() !== '') {
|
|
6
|
+
return option.text.trim();
|
|
7
|
+
}
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
export function isChooseAnswerGroupOptionFilled(option, optionType = 'text') {
|
|
11
|
+
if (optionType === 'image') {
|
|
12
|
+
return getChooseAnswerGroupOptionImageSrc(option) !== '';
|
|
13
|
+
}
|
|
14
|
+
return typeof option.text === 'string' && option.text.trim() !== '';
|
|
15
|
+
}
|
|
16
|
+
export function toChooseAnswerGroupImageUrls(value) {
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value
|
|
19
|
+
.filter((url) => typeof url === 'string')
|
|
20
|
+
.map((url) => url.trim())
|
|
21
|
+
.filter((url) => url !== '');
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
24
|
+
return [value.trim()];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
export function toEditableChooseAnswerGroupImageUrls(value) {
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.filter((url) => typeof url === 'string');
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
return [value];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
export function fromChooseAnswerGroupImageUrls(urls) {
|
|
38
|
+
const cleaned = urls
|
|
39
|
+
.filter((url) => typeof url === 'string')
|
|
40
|
+
.map((url) => url.trim())
|
|
41
|
+
.filter((url) => url !== '');
|
|
42
|
+
if (cleaned.length === 0) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (cleaned.length === 1) {
|
|
46
|
+
return cleaned[0];
|
|
47
|
+
}
|
|
48
|
+
return cleaned;
|
|
49
|
+
}
|
|
@@ -11,4 +11,6 @@ export * from './read-and-color-objects.type';
|
|
|
11
11
|
export * from './fill-in-blank.type';
|
|
12
12
|
export * from './read-passage-and-answer-questions.type';
|
|
13
13
|
export * from './word-fill-structured-form.type';
|
|
14
|
+
export * from './true-false-correct-group.type';
|
|
15
|
+
export * from './match-word-to-picture.type';
|
|
14
16
|
export * from './spontaneous-qa-group.type';
|
|
@@ -11,4 +11,6 @@ export * from './read-and-color-objects.type';
|
|
|
11
11
|
export * from './fill-in-blank.type';
|
|
12
12
|
export * from './read-passage-and-answer-questions.type';
|
|
13
13
|
export * from './word-fill-structured-form.type';
|
|
14
|
+
export * from './true-false-correct-group.type';
|
|
15
|
+
export * from './match-word-to-picture.type';
|
|
14
16
|
export * from './spontaneous-qa-group.type';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ValidationRef } from './look-and-choose-answer.type';
|
|
3
|
+
export type { ValidationRef };
|
|
4
|
+
export declare const MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION = "Look and complete.";
|
|
5
|
+
export interface MatchWordToPictureGroup {
|
|
6
|
+
number: number;
|
|
7
|
+
questionCount: number;
|
|
8
|
+
}
|
|
9
|
+
export interface MatchWordToPictureData {
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
correctAnswer: string;
|
|
12
|
+
points?: number;
|
|
13
|
+
groupNumber?: number;
|
|
14
|
+
isExample?: boolean;
|
|
15
|
+
wordBank: string[];
|
|
16
|
+
instruction?: string;
|
|
17
|
+
groups?: MatchWordToPictureGroup[];
|
|
18
|
+
explanation?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MatchWordToPictureCreatorProps {
|
|
21
|
+
initialData?: MatchWordToPictureData;
|
|
22
|
+
onSave?: (data: MatchWordToPictureData) => void;
|
|
23
|
+
onSaveToQuestionBank?: (data: MatchWordToPictureData) => void;
|
|
24
|
+
onCancel?: () => void;
|
|
25
|
+
onChange?: (data: MatchWordToPictureData) => void;
|
|
26
|
+
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
27
|
+
externalErrors?: string[];
|
|
28
|
+
groups?: MatchWordToPictureGroup[];
|
|
29
|
+
groupConfig?: string[];
|
|
30
|
+
questionConfig?: string[];
|
|
31
|
+
questionIndexInPart?: number;
|
|
32
|
+
partId?: string;
|
|
33
|
+
validationRef?: React.MutableRefObject<ValidationRef | null>;
|
|
34
|
+
}
|
|
35
|
+
export interface MatchWordToPictureClientProps {
|
|
36
|
+
questionData: MatchWordToPictureData;
|
|
37
|
+
onSubmit?: (answer: string) => void;
|
|
38
|
+
isReviewMode?: boolean;
|
|
39
|
+
userAnswer?: string;
|
|
40
|
+
autoFillCorrectAnswers?: boolean;
|
|
41
|
+
explanation?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface MatchWordToPictureGroupItemData {
|
|
44
|
+
imageUrl: string;
|
|
45
|
+
correctAnswer: string;
|
|
46
|
+
isExample?: boolean;
|
|
47
|
+
points: number;
|
|
48
|
+
questionNumber: number;
|
|
49
|
+
}
|
|
50
|
+
export interface MatchWordToPictureGroupData {
|
|
51
|
+
instruction: string;
|
|
52
|
+
wordBank: string[];
|
|
53
|
+
items: MatchWordToPictureGroupItemData[];
|
|
54
|
+
explanation?: string;
|
|
55
|
+
points?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface MatchWordToPictureGroupClientProps {
|
|
58
|
+
questionData: MatchWordToPictureGroupData;
|
|
59
|
+
isReviewMode?: boolean;
|
|
60
|
+
userAnswers?: string[];
|
|
61
|
+
onAnswerChange?: (answers: string[]) => void;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION = 'Look and complete.';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MutableRefObject } from 'react';
|
|
2
|
+
export declare const TRUE_FALSE_CORRECT_ITEM_TYPE: "TRUE_FALSE_CORRECT";
|
|
3
|
+
export declare const TRUE_FALSE_CORRECT_GROUP_DEFAULT_INSTRUCTION = "Tick (\u2713) the true and cross (\u2717) the false sentences. Correct the false sentences.";
|
|
4
|
+
export interface TrueFalseCorrectItemAnswer {
|
|
5
|
+
isTrue: boolean;
|
|
6
|
+
correction: string;
|
|
7
|
+
}
|
|
8
|
+
export interface TrueFalseCorrectStudentItemAnswer {
|
|
9
|
+
isTrue: boolean | null;
|
|
10
|
+
correction: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TrueFalseCorrectGroupItemData {
|
|
13
|
+
question: string;
|
|
14
|
+
isTrue: boolean;
|
|
15
|
+
correction: string;
|
|
16
|
+
isExample?: boolean;
|
|
17
|
+
points: number;
|
|
18
|
+
questionNumber: number;
|
|
19
|
+
}
|
|
20
|
+
export interface TrueFalseCorrectGroupData {
|
|
21
|
+
instruction: string;
|
|
22
|
+
items: TrueFalseCorrectGroupItemData[];
|
|
23
|
+
explanation?: string;
|
|
24
|
+
points?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface TrueFalseCorrectGroupCreatorProps {
|
|
27
|
+
initialData?: TrueFalseCorrectGroupData;
|
|
28
|
+
onSave?: (data: TrueFalseCorrectGroupData) => void;
|
|
29
|
+
onCancel?: () => void;
|
|
30
|
+
onChange?: (data: TrueFalseCorrectGroupData) => void;
|
|
31
|
+
externalErrors?: string[];
|
|
32
|
+
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
33
|
+
validationRef?: MutableRefObject<{
|
|
34
|
+
triggerValidation: () => Promise<boolean>;
|
|
35
|
+
getFormData?: () => TrueFalseCorrectGroupData;
|
|
36
|
+
} | null>;
|
|
37
|
+
}
|
|
38
|
+
export interface TrueFalseCorrectGroupClientProps {
|
|
39
|
+
questionData: TrueFalseCorrectGroupData;
|
|
40
|
+
isReviewMode?: boolean;
|
|
41
|
+
userAnswers?: TrueFalseCorrectStudentItemAnswer[];
|
|
42
|
+
}
|
|
@@ -7,6 +7,8 @@ export interface WriteAShortLetterData {
|
|
|
7
7
|
maxWords?: number;
|
|
8
8
|
instructions?: WriteAShortLetterInstructions;
|
|
9
9
|
guideImages?: string[];
|
|
10
|
+
/** Optional HTML/text article the student must read before writing. */
|
|
11
|
+
passage?: string;
|
|
10
12
|
points?: number;
|
|
11
13
|
}
|
|
12
14
|
export interface WriteAShortLetterCreatorProps {
|
|
@@ -26,6 +28,8 @@ export interface WriteAShortLetterQuestionData {
|
|
|
26
28
|
maxWords?: number;
|
|
27
29
|
instructions?: WriteAShortLetterInstructions;
|
|
28
30
|
guideImages?: string[];
|
|
31
|
+
/** Optional HTML/text article the student must read before writing. */
|
|
32
|
+
passage?: string;
|
|
29
33
|
}
|
|
30
34
|
export interface WriteAShortLetterClientProps {
|
|
31
35
|
questionData: WriteAShortLetterQuestionData;
|
|
@@ -27,6 +27,7 @@ const IMAGE_BASED_TYPES = [
|
|
|
27
27
|
'READ_AND_COLOR_OBJECTS',
|
|
28
28
|
'IMAGE_OBJECT_MATCHING',
|
|
29
29
|
'FILL_IN_BLANK',
|
|
30
|
+
'MATCH_WORD_TO_PICTURE',
|
|
30
31
|
];
|
|
31
32
|
// Passage-based question types
|
|
32
33
|
const PASSAGE_BASED_TYPES = [
|
|
@@ -62,6 +63,7 @@ const getQuestionTypeName = (type) => {
|
|
|
62
63
|
'WRITE_CORRECT_VERB_FORM': 'Viết dạng động từ',
|
|
63
64
|
'FILL_IN_BLANK': 'Điền vào chỗ trống',
|
|
64
65
|
'READ_PASSAGE_AND_ANSWER_QUESTIONS': 'Đọc hiểu',
|
|
66
|
+
'MATCH_WORD_TO_PICTURE': 'Nối từ với tranh',
|
|
65
67
|
};
|
|
66
68
|
return type ? typeMap[type] || type.replace(/_/g, ' ').toLowerCase() : 'Chưa xác định';
|
|
67
69
|
};
|
|
@@ -26,6 +26,7 @@ export function QuestionTypeSelector({ value, onValueChange, disabled, }) {
|
|
|
26
26
|
'ANSWER_THE_QUESTION',
|
|
27
27
|
'WORD_ORDERING',
|
|
28
28
|
'MATCH_BY_WRITING_ANSWER',
|
|
29
|
+
'MATCH_WORD_TO_PICTURE',
|
|
29
30
|
'WRITE_SENTENCES',
|
|
30
31
|
];
|
|
31
32
|
return (_jsxs(Select, { value: value, onValueChange: onValueChange, disabled: disabled, children: [_jsx(SelectTrigger, { id: "questionType", className: "mt-1", children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn lo\u1EA1i c\u00E2u h\u1ECFi" }) }), _jsx(SelectContent, { children: questionTypes.map((type) => (_jsx(SelectItem, { value: type, children: getQuestionTypeLabel(type) }, type))) })] }));
|
|
@@ -13,6 +13,7 @@ import { readPassageAndAnswerQuestionsRegistration } from '../types/read-passage
|
|
|
13
13
|
import { chooseTheCorrectAnswerRegistration } from '../types/choose-the-correct-answer/register';
|
|
14
14
|
import { chooseTheCorrectAnswerGroupRegistration } from '../types/choose-the-correct-answer-group/register';
|
|
15
15
|
import { trueFalseGroupRegistration } from '../types/true-false-group/register';
|
|
16
|
+
import { trueFalseCorrectGroupRegistration } from '../types/true-false-correct-group/register';
|
|
16
17
|
import { crossOutWordGroupRegistration } from '../types/cross-out-word-group/register';
|
|
17
18
|
import { writeAShortLetterRegistration } from '../types/write-a-short-letter/register';
|
|
18
19
|
import { writeCorrectVerbFormRegistration } from '../types/write-correct-verb-form/register';
|
|
@@ -23,6 +24,7 @@ import { trueFalseRegistration } from '../types/true-false/register';
|
|
|
23
24
|
import { wordOrderingRegistration } from '../types/word-ordering/register';
|
|
24
25
|
import { wordFillParagraphRegistration } from '../types/word-fill-paragraph/register';
|
|
25
26
|
import { matchByWritingAnswerRegistration } from '../types/match-by-writing-answer/register';
|
|
27
|
+
import { matchWordToPictureRegistration } from '../types/match-word-to-picture/register';
|
|
26
28
|
import { writeSentencesRegistration } from '../types/write-sentences/register';
|
|
27
29
|
import { listenAndChooseFromAnswerGroupRegistration } from '../types/listen-and-choose-from-answer-group/register';
|
|
28
30
|
import { listenAndChooseObjectsInSceneRegistration } from '../types/listen-and-choose-objects-in-scene/register';
|
|
@@ -50,6 +52,7 @@ export const questionTypeRegistry = {
|
|
|
50
52
|
CHOOSE_THE_CORRECT_ANSWER: chooseTheCorrectAnswerRegistration,
|
|
51
53
|
CHOOSE_THE_CORRECT_ANSWER_GROUP: chooseTheCorrectAnswerGroupRegistration,
|
|
52
54
|
TRUE_FALSE_GROUP: trueFalseGroupRegistration,
|
|
55
|
+
TRUE_FALSE_CORRECT_GROUP: trueFalseCorrectGroupRegistration,
|
|
53
56
|
CROSS_OUT_WORD_GROUP: crossOutWordGroupRegistration,
|
|
54
57
|
WORD_ORDER_AND_MATCH_GROUP: wordOrderAndMatchGroupRegistration,
|
|
55
58
|
FILL_IN_BLANK: fillInBlankRegistration,
|
|
@@ -72,6 +75,7 @@ export const questionTypeRegistry = {
|
|
|
72
75
|
WORD_ORDERING: wordOrderingRegistration,
|
|
73
76
|
WORD_FILL_PARAGRAPH: wordFillParagraphRegistration, // Will need to handle viewMode mapping either here or in wrapper
|
|
74
77
|
MATCH_BY_WRITING_ANSWER: matchByWritingAnswerRegistration,
|
|
78
|
+
MATCH_WORD_TO_PICTURE: matchWordToPictureRegistration,
|
|
75
79
|
WRITE_SENTENCES: writeSentencesRegistration,
|
|
76
80
|
LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP: listenAndChooseFromAnswerGroupRegistration,
|
|
77
81
|
LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE: listenAndChooseObjectsInSceneRegistration,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function ChooseAnswerGroupImageUploadItem({ index, itemIndex, imageUrl, onChange, onRemove, }: {
|
|
2
|
+
index: number;
|
|
3
|
+
itemIndex: number;
|
|
4
|
+
imageUrl: string;
|
|
5
|
+
onChange: (url: string) => void;
|
|
6
|
+
onRemove: () => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export declare function ChooseAnswerGroupOptionImageField({ itemIndex, optionIndex, imageUrl, onChange, }: {
|
|
9
|
+
itemIndex: number;
|
|
10
|
+
optionIndex: number;
|
|
11
|
+
imageUrl: string;
|
|
12
|
+
onChange: (url: string) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Button } from '../../../../components/ui/button';
|
|
5
|
+
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
6
|
+
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
7
|
+
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
8
|
+
import { Trash2 } from 'lucide-react';
|
|
9
|
+
function resolveDisplayUrl(imageUrl, previewOverride, fetchedPreviewUrl) {
|
|
10
|
+
if (previewOverride) {
|
|
11
|
+
return previewOverride;
|
|
12
|
+
}
|
|
13
|
+
if (fetchedPreviewUrl) {
|
|
14
|
+
return fetchedPreviewUrl;
|
|
15
|
+
}
|
|
16
|
+
if (imageUrl.startsWith('http') || imageUrl.startsWith('blob:')) {
|
|
17
|
+
return imageUrl;
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
export function ChooseAnswerGroupImageUploadItem({ index, itemIndex, imageUrl, onChange, onRemove, }) {
|
|
22
|
+
const [previewOverride, setPreviewOverride] = useState('');
|
|
23
|
+
const { previewUrl: fetchedPreviewUrl } = usePresignedFileUrl(imageUrl);
|
|
24
|
+
const displayUrl = resolveDisplayUrl(imageUrl, previewOverride, fetchedPreviewUrl);
|
|
25
|
+
return (_jsxs("div", { className: "space-y-2 rounded-lg border border-gray-200 bg-gray-50/40 p-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-teal-100 text-xs font-semibold text-teal-700", children: index + 1 }), _jsx("div", { className: "flex-1", children: _jsx(FileUpload, { id: `choose-answer-group-image-${itemIndex}-${index}`, label: "", accept: "image/*", value: imageUrl, onChange: onChange, onPresignedUrlChange: setPreviewOverride, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: onRemove, title: "Xo\u00E1 \u1EA3nh n\u00E0y", className: "h-8 w-8 text-gray-400 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), displayUrl && (_jsx("div", { className: "pl-8", children: _jsx(ImagePreview, { src: displayUrl, alt: `Hình ảnh ${index + 1}`, className: "h-40 w-full max-w-sm rounded-lg border border-gray-200 bg-white object-contain" }) }))] }));
|
|
26
|
+
}
|
|
27
|
+
export function ChooseAnswerGroupOptionImageField({ itemIndex, optionIndex, imageUrl, onChange, }) {
|
|
28
|
+
const [previewOverride, setPreviewOverride] = useState('');
|
|
29
|
+
const { previewUrl: fetchedPreviewUrl } = usePresignedFileUrl(imageUrl);
|
|
30
|
+
const displayUrl = resolveDisplayUrl(imageUrl, previewOverride, fetchedPreviewUrl);
|
|
31
|
+
return (_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(FileUpload, { id: `choose-answer-group-option-${itemIndex}-${optionIndex}`, label: "", accept: "image/*", value: imageUrl, onChange: onChange, onPresignedUrlChange: setPreviewOverride, maxSize: 5, placeholder: `Upload ảnh đáp án ${optionIndex + 1}`, autoUpload: true, prefix: "questions/options" }), displayUrl && (_jsx(ImagePreview, { src: displayUrl, alt: `Đáp án ${optionIndex + 1}`, className: "h-28 w-28 rounded-lg border border-gray-200 bg-white object-contain" }))] }));
|
|
32
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChooseTheCorrectAnswerGroupClientProps } from '../../_shared/types/choose-the-correct-answer-group.type';
|
|
2
|
-
export declare function ChooseTheCorrectAnswerGroupClient({ questionData, isReviewMode, userAnswers, }: ChooseTheCorrectAnswerGroupClientProps): import("react").JSX.Element;
|
|
2
|
+
export declare function ChooseTheCorrectAnswerGroupClient({ questionData, isReviewMode, userAnswers, onAnswerChange, }: ChooseTheCorrectAnswerGroupClientProps): import("react").JSX.Element;
|