@tinyweb_dev/oe-exam-sdk 0.2.9 → 0.2.10
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/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +7 -3
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.d.ts +13 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.js +30 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
- package/dist/components/exams/take/components/question-renderers/index.js +1 -0
- package/dist/components/exams/take/utils/answer-transformers.js +8 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +16 -1
- package/dist/components/exams/take/utils/question-transformers.js +47 -2
- package/dist/components/questions/_shared/config/question-types.config.js +18 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/index.d.ts +1 -0
- package/dist/components/questions/_shared/types/index.js +1 -0
- package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.d.ts +56 -0
- package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.js +2 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +35 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +2 -0
- package/dist/components/questions/_shared/types/word-ordering-group.type.d.ts +34 -0
- package/dist/components/questions/_shared/types/word-ordering-group.type.js +2 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +3 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
- package/dist/components/questions/creator/question-type-registry.js +6 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +5 -3
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +16 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +74 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +222 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/index.d.ts +5 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/index.js +5 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.d.ts +8 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.js +199 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/register.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/register.js +36 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/transform.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/transform.js +66 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +65 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +95 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +6 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +16 -0
- package/dist/components/questions/types/match-word-to-picture-group/index.d.ts +5 -0
- package/dist/components/questions/types/match-word-to-picture-group/index.js +5 -0
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.d.ts +4 -0
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +77 -0
- package/dist/components/questions/types/match-word-to-picture-group/register.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/register.js +36 -0
- package/dist/components/questions/types/match-word-to-picture-group/transform.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/transform.js +36 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +25 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +106 -0
- package/dist/components/questions/types/word-ordering-group/index.d.ts +5 -0
- package/dist/components/questions/types/word-ordering-group/index.js +5 -0
- package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.d.ts +4 -0
- package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.js +74 -0
- package/dist/components/questions/types/word-ordering-group/register.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/register.js +36 -0
- package/dist/components/questions/types/word-ordering-group/transform.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/transform.js +33 -0
- package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.d.ts +8 -0
- package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.js +49 -0
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +2 -2
- package/dist/components/questions/viewer/QuestionViewer.js +6 -0
- package/dist/components/questions/viewer/WordOrderingGroupViewer.d.ts +7 -0
- package/dist/components/questions/viewer/WordOrderingGroupViewer.js +29 -0
- package/dist/components/questions/viewer/index.d.ts +2 -0
- package/dist/components/questions/viewer/index.js +2 -0
- package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
- package/dist/components/results/renderers/review-question-body-movers.js +6 -2
- package/dist/components/results/review-data.js +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +6 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +111 -2
- package/dist/shared/lib/utils/question-transform.js +6 -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 +3 -0
- package/dist/shared/types/questions/index.js +6 -0
- package/dist/shared/types/questions/match-columns-to-make-sentences.d.ts +50 -0
- package/dist/shared/types/questions/match-columns-to-make-sentences.js +1 -0
- package/dist/shared/types/questions/match-word-to-picture-group.d.ts +31 -0
- package/dist/shared/types/questions/match-word-to-picture-group.js +1 -0
- package/dist/shared/types/questions/word-ordering-group.d.ts +28 -0
- package/dist/shared/types/questions/word-ordering-group.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type WordOrderingGroupData, type WordOrderingGroupItemData } from '../../_shared/types/word-ordering-group.type';
|
|
2
|
+
export declare function createEmptyWordOrderingGroupItem(questionNumber: number): WordOrderingGroupItemData;
|
|
3
|
+
export declare function mapWordOrderingGroupItem(item: unknown, index: number, fallbackAnswer?: unknown): WordOrderingGroupItemData;
|
|
4
|
+
export declare function mapQuestionToWordOrderingGroupData(question: Record<string, unknown> | null | undefined): WordOrderingGroupData;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION, } from '../../_shared/types/word-ordering-group.type';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function asRecord(value) {
|
|
6
|
+
return isRecord(value) ? value : {};
|
|
7
|
+
}
|
|
8
|
+
function asString(value, fallback = '') {
|
|
9
|
+
return typeof value === 'string' ? value : fallback;
|
|
10
|
+
}
|
|
11
|
+
function asStringList(value) {
|
|
12
|
+
if (!Array.isArray(value))
|
|
13
|
+
return [];
|
|
14
|
+
return value
|
|
15
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
}
|
|
18
|
+
export function createEmptyWordOrderingGroupItem(questionNumber) {
|
|
19
|
+
return {
|
|
20
|
+
words: ['', ''],
|
|
21
|
+
answer: ['', ''],
|
|
22
|
+
isExample: false,
|
|
23
|
+
points: 1,
|
|
24
|
+
questionNumber,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function mapWordOrderingGroupItem(item, index, fallbackAnswer) {
|
|
28
|
+
const record = asRecord(item);
|
|
29
|
+
const content = asRecord(record.content);
|
|
30
|
+
const correctAnswer = asRecord(record.correctAnswer);
|
|
31
|
+
const isExample = record.isExample === true;
|
|
32
|
+
const words = asStringList(content.words).length
|
|
33
|
+
? asStringList(content.words)
|
|
34
|
+
: asStringList(record.words);
|
|
35
|
+
const answer = asStringList(correctAnswer.answer).length
|
|
36
|
+
? asStringList(correctAnswer.answer)
|
|
37
|
+
: asStringList(record.answer).length
|
|
38
|
+
? asStringList(record.answer)
|
|
39
|
+
: asStringList(fallbackAnswer);
|
|
40
|
+
return {
|
|
41
|
+
words,
|
|
42
|
+
answer,
|
|
43
|
+
isExample,
|
|
44
|
+
points: isExample ? 0 : (typeof record.points === 'number' ? record.points : 1),
|
|
45
|
+
questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function mapQuestionToWordOrderingGroupData(question) {
|
|
49
|
+
const source = asRecord(question);
|
|
50
|
+
const content = asRecord(source.content);
|
|
51
|
+
const meta = asRecord(content.meta);
|
|
52
|
+
const answer = asRecord(source.answer);
|
|
53
|
+
const correctAnswer = asRecord(source.correctAnswer ?? source.correct_answer ?? answer.correctAnswer);
|
|
54
|
+
const explanation = asString(source.explanation) || asString(answer.explanation);
|
|
55
|
+
const fallbackAnswers = Array.isArray(correctAnswer.answer) ? correctAnswer.answer : [];
|
|
56
|
+
if (Array.isArray(content.items) || Array.isArray(answer.items)) {
|
|
57
|
+
const rawItems = Array.isArray(content.items) ? content.items : answer.items;
|
|
58
|
+
const items = rawItems.map((item, index) => mapWordOrderingGroupItem(item, index, fallbackAnswers[index]));
|
|
59
|
+
return {
|
|
60
|
+
instruction: asString(meta.instruction) ||
|
|
61
|
+
asString(answer.instruction) ||
|
|
62
|
+
WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
|
|
63
|
+
items,
|
|
64
|
+
explanation,
|
|
65
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
instruction: WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
|
|
70
|
+
items: [createEmptyWordOrderingGroupItem(1)],
|
|
71
|
+
explanation,
|
|
72
|
+
points: 1,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WordOrderingGroupCreator } from './WordOrderingGroupCreator';
|
|
2
|
+
import { mapQuestionToWordOrderingGroupData } from './map-word-ordering-group-data';
|
|
3
|
+
export const wordOrderingGroupRegistration = {
|
|
4
|
+
component: WordOrderingGroupCreator,
|
|
5
|
+
transformInitialData: (initialData) => mapQuestionToWordOrderingGroupData(initialData),
|
|
6
|
+
getExtraProps: (ctx) => ({
|
|
7
|
+
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
8
|
+
validationRef: ctx.validationRef,
|
|
9
|
+
}),
|
|
10
|
+
wrapOnSave: (data, ctx) => {
|
|
11
|
+
const payload = data;
|
|
12
|
+
return {
|
|
13
|
+
type: ctx.questionType,
|
|
14
|
+
content: ctx.state.content,
|
|
15
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
16
|
+
level: ctx.state.level,
|
|
17
|
+
difficulty: ctx.state.difficulty,
|
|
18
|
+
skill: ctx.state.skill,
|
|
19
|
+
answer: payload,
|
|
20
|
+
explanation: payload.explanation || '',
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
wrapOnChange: (data, ctx) => {
|
|
24
|
+
const payload = data;
|
|
25
|
+
return {
|
|
26
|
+
type: ctx.questionType,
|
|
27
|
+
content: ctx.state.content,
|
|
28
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
29
|
+
level: ctx.state.level,
|
|
30
|
+
difficulty: ctx.state.difficulty,
|
|
31
|
+
skill: ctx.state.skill,
|
|
32
|
+
answer: payload,
|
|
33
|
+
explanation: payload.explanation || '',
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION, WORD_ORDERING_ITEM_TYPE, } from '../../_shared/types/word-ordering-group.type';
|
|
2
|
+
function tokens(value) {
|
|
3
|
+
if (!Array.isArray(value))
|
|
4
|
+
return [];
|
|
5
|
+
return value
|
|
6
|
+
.filter((item) => typeof item === 'string')
|
|
7
|
+
.map((item) => item.trim())
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
export const transformWordOrderingGroup = (question) => {
|
|
11
|
+
const answerData = question.answer;
|
|
12
|
+
const items = Array.isArray(answerData?.items) ? answerData.items : [];
|
|
13
|
+
const apiItems = items.map((item, index) => ({
|
|
14
|
+
questionType: WORD_ORDERING_ITEM_TYPE,
|
|
15
|
+
...(item.isExample ? { isExample: true } : {}),
|
|
16
|
+
content: { words: tokens(item.words) },
|
|
17
|
+
correctAnswer: { answer: tokens(item.answer) },
|
|
18
|
+
points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
19
|
+
questionNumber: item.questionNumber || index + 1,
|
|
20
|
+
}));
|
|
21
|
+
return {
|
|
22
|
+
apiContent: {
|
|
23
|
+
meta: {
|
|
24
|
+
instruction: answerData?.instruction || WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION,
|
|
25
|
+
},
|
|
26
|
+
items: apiItems,
|
|
27
|
+
},
|
|
28
|
+
apiCorrectAnswer: {
|
|
29
|
+
answer: apiItems.map((item) => item.correctAnswer.answer),
|
|
30
|
+
},
|
|
31
|
+
totalPoints: items.reduce((total, item) => (item.isExample ? total : total + (typeof item.points === 'number' ? item.points : 1)), 0),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { MatchColumnsStudentItemAnswer } from '../_shared/types/match-columns-to-make-sentences.type';
|
|
3
|
+
export declare function MatchColumnsToMakeSentencesViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: MatchColumnsStudentItemAnswer[]) => void;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { MatchColumnsToMakeSentencesClient } from '../types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient';
|
|
5
|
+
import { mapQuestionToMatchColumnsGroupData } from '../types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function parseAnswers(value, length) {
|
|
10
|
+
const source = Array.isArray(value)
|
|
11
|
+
? value
|
|
12
|
+
: isRecord(value) && Array.isArray(value.answer)
|
|
13
|
+
? value.answer
|
|
14
|
+
: null;
|
|
15
|
+
if (!source)
|
|
16
|
+
return undefined;
|
|
17
|
+
return Array.from({ length }, (_, index) => {
|
|
18
|
+
const raw = source[index];
|
|
19
|
+
if (!isRecord(raw))
|
|
20
|
+
return { fragmentIds: [], sentence: '' };
|
|
21
|
+
return {
|
|
22
|
+
fragmentIds: Array.isArray(raw.fragmentIds)
|
|
23
|
+
? raw.fragmentIds.filter((id) => typeof id === 'string')
|
|
24
|
+
: [],
|
|
25
|
+
sentence: typeof raw.sentence === 'string' ? raw.sentence : '',
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function MatchColumnsToMakeSentencesViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
30
|
+
const questionData = mapQuestionToMatchColumnsGroupData(question);
|
|
31
|
+
const itemCount = questionData.items.length;
|
|
32
|
+
const parsedUser = parseAnswers(userAnswer, itemCount);
|
|
33
|
+
const questionKey = `${question.id}:${itemCount}`;
|
|
34
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
35
|
+
const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? questionData.items.map((item) => (item.isExample
|
|
36
|
+
? { fragmentIds: item.fragmentIds, sentence: item.sentence }
|
|
37
|
+
: { fragmentIds: [item.stemFragmentId], sentence: '' })));
|
|
38
|
+
if (cachedKey !== questionKey) {
|
|
39
|
+
setCachedKey(questionKey);
|
|
40
|
+
setLocalAnswers(parsedUser ?? questionData.items.map((item) => (item.isExample
|
|
41
|
+
? { fragmentIds: item.fragmentIds, sentence: item.sentence }
|
|
42
|
+
: { fragmentIds: [item.stemFragmentId], sentence: '' })));
|
|
43
|
+
}
|
|
44
|
+
const userAnswers = parsedUser ?? localAnswers;
|
|
45
|
+
return (_jsx(MatchColumnsToMakeSentencesClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : (next) => {
|
|
46
|
+
setLocalAnswers(next);
|
|
47
|
+
onSubmitAnswer?.(next);
|
|
48
|
+
} }));
|
|
49
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { MatchWordToPictureGroupClient } from '../types/match-word-to-picture/MatchWordToPictureGroupClient';
|
|
4
|
-
import { mapQuestionToMatchWordToPictureGroupData } from '../types/match-word-to-picture/map-match-word-to-picture-group-data';
|
|
3
|
+
import { MatchWordToPictureGroupClient } from '../types/match-word-to-picture-group/MatchWordToPictureGroupClient';
|
|
4
|
+
import { mapQuestionToMatchWordToPictureGroupData } from '../types/match-word-to-picture-group/map-match-word-to-picture-group-data';
|
|
5
5
|
export function MatchWordToPictureGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
6
|
const questionData = mapQuestionToMatchWordToPictureGroupData(question);
|
|
7
7
|
const reviewAnswers = isReviewMode && !userAnswer
|
|
@@ -34,6 +34,8 @@ import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
|
34
34
|
import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
35
35
|
import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
36
36
|
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
37
|
+
import { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
38
|
+
import { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
37
39
|
export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, }) {
|
|
38
40
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
|
|
39
41
|
const [playCount, setPlayCount] = useState(0);
|
|
@@ -641,6 +643,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
641
643
|
return (_jsx(FillInBlankGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
642
644
|
case 'MATCHING_WITH_LINES_GROUP':
|
|
643
645
|
return (_jsx(MatchingWithLinesGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
646
|
+
case 'MATCH_COLUMNS_TO_MAKE_SENTENCES':
|
|
647
|
+
return (_jsx(MatchColumnsToMakeSentencesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
644
648
|
case 'CROSS_OUT_WORD_GROUP':
|
|
645
649
|
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
646
650
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
@@ -650,6 +654,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
650
654
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
651
655
|
case 'WORD_ORDER_AND_MATCH':
|
|
652
656
|
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
657
|
+
case 'WORD_ORDERING_GROUP':
|
|
658
|
+
return (_jsx(WordOrderingGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
653
659
|
case 'MATCH_WORD_TO_PICTURE_GROUP':
|
|
654
660
|
return (_jsx(MatchWordToPictureGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
655
661
|
case 'MATCH_WORD_TO_PICTURE':
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
export declare function WordOrderingGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: string[][]) => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { WordOrderingGroupClient } from '../types/word-ordering-group/WordOrderingGroupClient';
|
|
5
|
+
import { mapQuestionToWordOrderingGroupData } from '../types/word-ordering-group/map-word-ordering-group-data';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function parseAnswers(value, length) {
|
|
10
|
+
const source = Array.isArray(value)
|
|
11
|
+
? value
|
|
12
|
+
: isRecord(value) && Array.isArray(value.answer)
|
|
13
|
+
? value.answer
|
|
14
|
+
: null;
|
|
15
|
+
if (!source)
|
|
16
|
+
return undefined;
|
|
17
|
+
return Array.from({ length }, (_, index) => Array.isArray(source[index])
|
|
18
|
+
? source[index].filter((token) => typeof token === 'string')
|
|
19
|
+
: []);
|
|
20
|
+
}
|
|
21
|
+
export function WordOrderingGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
22
|
+
const questionData = mapQuestionToWordOrderingGroupData(question);
|
|
23
|
+
const parsedUser = parseAnswers(userAnswer, questionData.items.length);
|
|
24
|
+
const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? questionData.items.map((item) => (item.isExample ? item.answer : [])));
|
|
25
|
+
return (_jsx(WordOrderingGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: parsedUser ?? localAnswers, onAnswerChange: isReviewMode ? undefined : (next) => {
|
|
26
|
+
setLocalAnswers(next);
|
|
27
|
+
onSubmitAnswer?.(next);
|
|
28
|
+
} }));
|
|
29
|
+
}
|
|
@@ -12,3 +12,5 @@ export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
|
12
12
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
13
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
14
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
|
+
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
|
+
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
@@ -12,3 +12,5 @@ export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
|
12
12
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
13
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
14
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
|
+
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
|
+
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
@@ -40,7 +40,7 @@ export function renderDedicatedReviewBody(ctx) {
|
|
|
40
40
|
}
|
|
41
41
|
case 'CHOOSE_THE_CORRECT_ANSWER_GROUP': {
|
|
42
42
|
const data = transformChooseTheCorrectAnswerGroup(apiQuestions);
|
|
43
|
-
return (_jsx(ReviewChooseBestAnswerRenderer, { partNumber: partNo, questionCount: questionCount, partName: part.name, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl, questions: data.questions, answers: answers, isCorrectMap: isCorrectMap, correctOptionIdMap: correctOptionIdMap, example: data.example }));
|
|
43
|
+
return (_jsx(ReviewChooseBestAnswerRenderer, { partNumber: partNo, questionCount: questionCount, partName: part.name, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl, imageUrl: data.imageUrl, questions: data.questions, answers: answers, isCorrectMap: isCorrectMap, correctOptionIdMap: correctOptionIdMap, example: data.example }));
|
|
44
44
|
}
|
|
45
45
|
case 'CHOOSE_CORRECT_ADJECTIVE': {
|
|
46
46
|
const data = transformChooseCorrectAdjective(apiQuestions);
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Movers fallback review cases (isReviewMode=true).
|
|
5
5
|
* Source: oe-exam-fe ReviewQuestionRenderer.tsx
|
|
6
6
|
*/
|
|
7
|
-
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
-
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
|
|
7
|
+
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
+
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
|
|
9
9
|
import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
|
|
10
10
|
function asApiQuestions(questions) {
|
|
11
11
|
return questions;
|
|
@@ -189,6 +189,10 @@ export function renderMoversReviewBody(ctx) {
|
|
|
189
189
|
const data = transformMatchWordToPicture(apiQuestions);
|
|
190
190
|
return (_jsx(MoversMatchWordToPictureRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, wordBank: data.wordBank, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
|
|
191
191
|
}
|
|
192
|
+
case 'WORD_ORDERING_GROUP': {
|
|
193
|
+
const data = transformWordOrderingGroupTake(apiQuestions);
|
|
194
|
+
return (_jsx(MoversWordOrderingGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: data.questions.filter((q) => !q.isExample).length, data: data, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true }));
|
|
195
|
+
}
|
|
192
196
|
case 'WORD_ORDERING': {
|
|
193
197
|
const data = transformWordOrdering(apiQuestions);
|
|
194
198
|
return (_jsx(MoversWordOrderingRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, rawCorrectAnswerMap: rawCorrectAnswerMap, isCorrectMap: isCorrectMap }));
|
|
@@ -231,6 +231,19 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
231
231
|
return [`${questionId}-item-${index}`, { answers: [] }];
|
|
232
232
|
}));
|
|
233
233
|
}
|
|
234
|
+
if (questionType === 'WORD_ORDERING_GROUP') {
|
|
235
|
+
const answer = Array.isArray(record.answer)
|
|
236
|
+
? record.answer
|
|
237
|
+
: Array.isArray(value)
|
|
238
|
+
? value
|
|
239
|
+
: [];
|
|
240
|
+
return Object.fromEntries(answer.map((item, index) => [
|
|
241
|
+
`${questionId}-item-${index}`,
|
|
242
|
+
Array.isArray(item)
|
|
243
|
+
? item.filter((token) => typeof token === 'string')
|
|
244
|
+
: [],
|
|
245
|
+
]));
|
|
246
|
+
}
|
|
234
247
|
if (questionType === 'MATCHING_WITH_LINES_GROUP') {
|
|
235
248
|
const answer = Array.isArray(record.answer)
|
|
236
249
|
? record.answer
|
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
|
|
|
26
26
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
27
27
|
export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
|
|
28
28
|
export * from './components/questions/_shared/types';
|
|
29
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
|
|
29
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
package/dist/index.js
CHANGED
|
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
|
|
|
21
21
|
export { validateQuestion } from './components/questions/_shared/utils/question-validation';
|
|
22
22
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
23
23
|
export * from './components/questions/_shared/types';
|
|
24
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
|
|
24
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
|
@@ -84,6 +84,9 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
84
84
|
'WORD_FILL_STRUCTURED_FORM': SkillEnum.READING,
|
|
85
85
|
'CROSSWORD_PUZZLE': SkillEnum.READING,
|
|
86
86
|
'FIND_WORDS_IN_MATRIX': SkillEnum.READING,
|
|
87
|
+
'MATCH_COLUMNS_TO_MAKE_SENTENCES': SkillEnum.READING,
|
|
88
|
+
'MATCH_WORD_TO_PICTURE_GROUP': SkillEnum.READING,
|
|
89
|
+
'WORD_ORDERING_GROUP': SkillEnum.READING,
|
|
87
90
|
// ========================================
|
|
88
91
|
// WRITING SKILLS (Viết)
|
|
89
92
|
// ========================================
|
|
@@ -216,6 +219,9 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
216
219
|
'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
|
|
217
220
|
'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
|
|
218
221
|
'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
|
|
222
|
+
'MATCH_COLUMNS_TO_MAKE_SENTENCES': 'Ghép cột thành câu',
|
|
223
|
+
'MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh (nhóm)',
|
|
224
|
+
'WORD_ORDERING_GROUP': 'Sắp xếp từ (nhóm)',
|
|
219
225
|
// ========================================
|
|
220
226
|
// WRITING SKILLS (Viết)
|
|
221
227
|
// ========================================
|
|
@@ -226,7 +226,9 @@ function normalizeChooseAnswerGroupImageUrl(value) {
|
|
|
226
226
|
const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
227
227
|
const content = asRecord(apiQuestion.content);
|
|
228
228
|
const answer = readAnswer(apiQuestion);
|
|
229
|
-
const fallbackAnswers = asArray(answer.answer).map((item) => asString(item))
|
|
229
|
+
const fallbackAnswers = asArray(answer.answer).map((item) => (asString(item) || (Array.isArray(item)
|
|
230
|
+
? asString(item.find((token) => typeof token === 'string' && token.trim() !== ''))
|
|
231
|
+
: '')));
|
|
230
232
|
const items = asArray(content.items).map((rawItem, index) => {
|
|
231
233
|
const item = asRecord(rawItem);
|
|
232
234
|
const itemContent = asRecord(item.content);
|
|
@@ -251,7 +253,13 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
251
253
|
{ id: 'opt-2', text: '' },
|
|
252
254
|
{ id: 'opt-3', text: '' },
|
|
253
255
|
],
|
|
254
|
-
correctAnswer: asString(itemAnswer.answer)
|
|
256
|
+
correctAnswer: asString(itemAnswer.answer)
|
|
257
|
+
|| (Array.isArray(itemAnswer.answer)
|
|
258
|
+
? asString(itemAnswer.answer.find((token) => typeof token === 'string' && token.trim() !== ''))
|
|
259
|
+
: '')
|
|
260
|
+
|| fallbackAnswers[index]
|
|
261
|
+
|| options[0]?.id
|
|
262
|
+
|| 'opt-1',
|
|
255
263
|
isExample,
|
|
256
264
|
points: isExample ? 0 : asNumber(item.points, 1),
|
|
257
265
|
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
@@ -260,12 +268,14 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
260
268
|
};
|
|
261
269
|
});
|
|
262
270
|
const audioUrl = asString(asRecord(content.meta).audioUrl).trim();
|
|
271
|
+
const imageUrl = normalizeChooseAnswerGroupImageUrl(asRecord(content.meta).imageUrl);
|
|
263
272
|
return {
|
|
264
273
|
content: '',
|
|
265
274
|
answer: {
|
|
266
275
|
instruction: asString(asRecord(content.meta).instruction, 'Choose the correct answer.'),
|
|
267
276
|
passage: asString(asRecord(content.meta).passage),
|
|
268
277
|
...(audioUrl ? { audioUrl } : {}),
|
|
278
|
+
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
269
279
|
items,
|
|
270
280
|
explanation: asString(apiQuestion.explanation),
|
|
271
281
|
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
@@ -603,6 +613,102 @@ const reverseSimpleAnswer = (apiQuestion) => {
|
|
|
603
613
|
},
|
|
604
614
|
};
|
|
605
615
|
};
|
|
616
|
+
const reverseWordOrderingGroup = (apiQuestion) => {
|
|
617
|
+
const content = asRecord(apiQuestion.content);
|
|
618
|
+
const answer = readAnswer(apiQuestion);
|
|
619
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
620
|
+
const items = asArray(content.items);
|
|
621
|
+
const meta = asRecord(content.meta);
|
|
622
|
+
const mappedItems = items.map((rawItem, index) => {
|
|
623
|
+
const item = asRecord(rawItem);
|
|
624
|
+
const itemContent = asRecord(item.content);
|
|
625
|
+
const itemAnswer = asRecord(item.correctAnswer);
|
|
626
|
+
const isExample = item.isExample === true;
|
|
627
|
+
const words = asArray(itemContent.words).map((token) => asString(token)).filter(Boolean);
|
|
628
|
+
const itemTokens = asArray(itemAnswer.answer).map((token) => asString(token)).filter(Boolean);
|
|
629
|
+
const fallbackTokens = asArray(fallbackAnswers[index]).map((token) => asString(token)).filter(Boolean);
|
|
630
|
+
return {
|
|
631
|
+
words,
|
|
632
|
+
answer: itemTokens.length ? itemTokens : fallbackTokens,
|
|
633
|
+
isExample,
|
|
634
|
+
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
635
|
+
questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
return {
|
|
639
|
+
content: '',
|
|
640
|
+
answer: {
|
|
641
|
+
instruction: asString(meta.instruction, 'Order the words.'),
|
|
642
|
+
items: mappedItems,
|
|
643
|
+
explanation: asString(apiQuestion.explanation),
|
|
644
|
+
points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
const reverseMatchWordToPictureGroup = (apiQuestion) => {
|
|
649
|
+
const content = asRecord(apiQuestion.content);
|
|
650
|
+
const answer = readAnswer(apiQuestion);
|
|
651
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
652
|
+
const items = asArray(content.items);
|
|
653
|
+
const meta = asRecord(content.meta);
|
|
654
|
+
const mappedItems = items.map((rawItem, index) => {
|
|
655
|
+
const item = asRecord(rawItem);
|
|
656
|
+
const itemContent = asRecord(item.content);
|
|
657
|
+
const itemAnswer = asRecord(item.correctAnswer);
|
|
658
|
+
const isExample = item.isExample === true;
|
|
659
|
+
return {
|
|
660
|
+
imageUrl: asString(itemContent.imageUrl),
|
|
661
|
+
correctAnswer: asString(itemAnswer.answer) || asString(fallbackAnswers[index]),
|
|
662
|
+
isExample,
|
|
663
|
+
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
664
|
+
questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
|
|
665
|
+
};
|
|
666
|
+
});
|
|
667
|
+
return {
|
|
668
|
+
content: '',
|
|
669
|
+
answer: {
|
|
670
|
+
instruction: asString(meta.instruction, 'Look and complete.'),
|
|
671
|
+
wordBank: asArray(meta.wordBank).map((word) => asString(word)).filter(Boolean),
|
|
672
|
+
items: mappedItems,
|
|
673
|
+
explanation: asString(apiQuestion.explanation),
|
|
674
|
+
points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
const reverseMatchColumnsToMakeSentences = (apiQuestion) => {
|
|
679
|
+
const content = asRecord(apiQuestion.content);
|
|
680
|
+
const answer = readAnswer(apiQuestion);
|
|
681
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
682
|
+
const items = asArray(content.items);
|
|
683
|
+
const columns = asArray(content.columns);
|
|
684
|
+
const meta = asRecord(content.meta);
|
|
685
|
+
const mappedItems = items.map((rawItem, index) => {
|
|
686
|
+
const item = asRecord(rawItem);
|
|
687
|
+
const itemContent = asRecord(item.content);
|
|
688
|
+
const itemAnswer = asRecord(asRecord(item.correctAnswer).answer ?? fallbackAnswers[index]);
|
|
689
|
+
const fragmentIds = asArray(itemAnswer.fragmentIds).map((id) => asString(id)).filter(Boolean);
|
|
690
|
+
const isExample = item.isExample === true;
|
|
691
|
+
return {
|
|
692
|
+
stemFragmentId: asString(itemContent.stemFragmentId) || fragmentIds[0] || '',
|
|
693
|
+
fragmentIds,
|
|
694
|
+
sentence: asString(itemAnswer.sentence),
|
|
695
|
+
isExample,
|
|
696
|
+
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
697
|
+
questionNumber: typeof item.questionNumber === 'number' ? item.questionNumber : index + 1,
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
return {
|
|
701
|
+
content: '',
|
|
702
|
+
answer: {
|
|
703
|
+
title: asString(meta.title),
|
|
704
|
+
instruction: asString(meta.instruction, 'Match the information to make sentences. You can use the same words more than once.'),
|
|
705
|
+
columns,
|
|
706
|
+
items: mappedItems,
|
|
707
|
+
explanation: asString(apiQuestion.explanation),
|
|
708
|
+
points: mappedItems.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
};
|
|
606
712
|
const handlers = {
|
|
607
713
|
CHOOSE_THE_CORRECT_ANSWER: reverseChooseTheCorrectAnswer,
|
|
608
714
|
CHOOSE_THE_CORRECT_ANSWER_GROUP: reverseChooseTheCorrectAnswerGroup,
|
|
@@ -653,6 +759,9 @@ const handlers = {
|
|
|
653
759
|
GN_SPEAKING_INTERVIEW: reverseGroupPayload,
|
|
654
760
|
CROSSWORD_PUZZLE: reverseCrosswordPuzzle,
|
|
655
761
|
FIND_WORDS_IN_MATRIX: reverseFindWordsInMatrix,
|
|
762
|
+
MATCH_COLUMNS_TO_MAKE_SENTENCES: reverseMatchColumnsToMakeSentences,
|
|
763
|
+
MATCH_WORD_TO_PICTURE_GROUP: reverseMatchWordToPictureGroup,
|
|
764
|
+
WORD_ORDERING_GROUP: reverseWordOrderingGroup,
|
|
656
765
|
};
|
|
657
766
|
function reverseWriteAShortLetter(apiQuestion) {
|
|
658
767
|
const content = asRecord(apiQuestion.content);
|
|
@@ -47,6 +47,9 @@ import { transformGNSpeakingInterview } from '../../../components/questions/type
|
|
|
47
47
|
import { transformWordFillStructuredForm } from '../../../components/questions/types/word-fill-structured-form/transform';
|
|
48
48
|
import { transformCrosswordPuzzle } from '../../../components/questions/types/crossword-puzzle/transform';
|
|
49
49
|
import { transformFindWordsInMatrix } from '../../../components/questions/types/find-words-in-matrix/transform';
|
|
50
|
+
import { transformMatchColumnsToMakeSentences } from '../../../components/questions/types/match-columns-to-make-sentences/transform';
|
|
51
|
+
import { transformMatchWordToPictureGroup } from '../../../components/questions/types/match-word-to-picture-group/transform';
|
|
52
|
+
import { transformWordOrderingGroup } from '../../../components/questions/types/word-ordering-group/transform';
|
|
50
53
|
// ─── Handler map ────────────────────────────────────────────────────────────
|
|
51
54
|
const questionTransformHandlers = {
|
|
52
55
|
// Look picture
|
|
@@ -100,6 +103,9 @@ const questionTransformHandlers = {
|
|
|
100
103
|
WORD_FILL_STRUCTURED_FORM: transformWordFillStructuredForm,
|
|
101
104
|
CROSSWORD_PUZZLE: transformCrosswordPuzzle,
|
|
102
105
|
FIND_WORDS_IN_MATRIX: transformFindWordsInMatrix,
|
|
106
|
+
MATCH_COLUMNS_TO_MAKE_SENTENCES: transformMatchColumnsToMakeSentences,
|
|
107
|
+
MATCH_WORD_TO_PICTURE_GROUP: transformMatchWordToPictureGroup,
|
|
108
|
+
WORD_ORDERING_GROUP: transformWordOrderingGroup,
|
|
103
109
|
};
|
|
104
110
|
/**
|
|
105
111
|
* Transform frontend question format to API format.
|
|
@@ -29,7 +29,7 @@ export declare enum StudentSelectionType {
|
|
|
29
29
|
}
|
|
30
30
|
export type ResultStatus = 'PENDING' | 'PARTIAL_GRADED' | 'GRADED';
|
|
31
31
|
export type StudentStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'SUBMITTED';
|
|
32
|
-
export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX';
|
|
32
|
+
export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'MATCH_COLUMNS_TO_MAKE_SENTENCES' | 'MATCH_WORD_TO_PICTURE_GROUP' | 'WORD_ORDERING_GROUP';
|
|
33
33
|
export type Level = 'Pre-A1' | 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
|
|
34
34
|
export type Difficulty = 'EASY' | 'MEDIUM' | 'HARD';
|
|
35
35
|
export type Skill = 'LISTENING' | 'READING' | 'WRITING' | 'SPEAKING';
|
|
@@ -34,5 +34,8 @@ export * from './crossword-puzzle';
|
|
|
34
34
|
export * from './find-words-in-matrix';
|
|
35
35
|
export * from './match-word-to-picture';
|
|
36
36
|
export * from './matching-with-lines-group';
|
|
37
|
+
export * from './match-columns-to-make-sentences';
|
|
38
|
+
export * from './match-word-to-picture-group';
|
|
39
|
+
export * from './word-ordering-group';
|
|
37
40
|
export * from './true-false-correct-group';
|
|
38
41
|
export * from './fill-in-blank-group';
|