@tinyweb_dev/oe-exam-sdk 0.2.4 → 0.2.6
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 +19 -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/MoversFillInBlankGroupRenderer.d.ts +22 -0
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +60 -0
- 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/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -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 +4 -0
- package/dist/components/exams/take/components/question-renderers/index.js +4 -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 +28 -0
- package/dist/components/exams/take/utils/question-transformers.d.ts +73 -1
- package/dist/components/exams/take/utils/question-transformers.js +188 -0
- package/dist/components/questions/_shared/config/question-types.config.js +24 -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/fill-in-blank-group.type.d.ts +50 -0
- package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
- package/dist/components/questions/_shared/types/index.d.ts +4 -0
- package/dist/components/questions/_shared/types/index.js +4 -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/matching-with-lines-group.type.d.ts +40 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.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 +6 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
- package/dist/components/questions/creator/question-type-registry.js +8 -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/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
- package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
- package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
- package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
- package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -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/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
- package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
- package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
- package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
- package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -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 +11 -1
- 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/ChooseTheCorrectAnswerGroupViewer.d.ts +2 -1
- package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.js +2 -2
- package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
- package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
- 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/MatchingWithLinesGroupViewer.d.ts +7 -0
- package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
- package/dist/components/questions/viewer/QuestionViewer.js +24 -3
- 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 +5 -0
- package/dist/components/questions/viewer/index.js +5 -0
- package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +4 -9
- package/dist/components/results/renderers/review-question-body-movers.js +74 -3
- package/dist/components/results/review-data.js +59 -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 +8 -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 +142 -0
- package/dist/shared/lib/utils/question-transform.js +8 -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/fill-in-blank-group.d.ts +45 -0
- package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +4 -0
- package/dist/shared/types/questions/index.js +8 -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/matching-with-lines-group.d.ts +39 -0
- package/dist/shared/types/questions/matching-with-lines-group.js +1 -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
|
@@ -46,7 +46,7 @@ export function EcWritingTask({ part, answers, onAnswerChange, isReviewMode = fa
|
|
|
46
46
|
if (!firstQuestion || !data) {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
-
const { prompt, requirements, guideImages } = data;
|
|
49
|
+
const { prompt, requirements, guideImages, passage } = data;
|
|
50
50
|
const meetsMin = minWords ? wordCount >= minWords : true;
|
|
51
51
|
const withinMax = maxWords ? wordCount <= maxWords : true;
|
|
52
52
|
const isValid = meetsMin && withinMax;
|
|
@@ -54,7 +54,7 @@ export function EcWritingTask({ part, answers, onAnswerChange, isReviewMode = fa
|
|
|
54
54
|
const progressPct = progressTarget
|
|
55
55
|
? Math.min(100, Math.round((wordCount / progressTarget) * 100))
|
|
56
56
|
: 0;
|
|
57
|
-
return (_jsxs("div", { id: englishCertQuestionDomId(firstQuestion.id), className: "scroll-mt-24 flex flex-col gap-5", children: [_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "overflow-hidden rounded-2xl border border-[#cfe0f7] bg-white shadow-[0_18px_40px_rgba(13,71,161,0.10)]", children: [prompt && (_jsx(HtmlOrText, { value: prompt, as: "div", className: "whitespace-pre-line px-5 pb-4 pt-5 text-base leading-relaxed text-[#0f1730]" })), requirements && requirements.length > 0 && (_jsx("div", { className: cn('px-5 pb-4', !prompt && 'pt-5'), children: _jsxs("div", { className: "rounded-xl border border-[#e3edfb] bg-[#f6faff] p-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-wider text-[#0b57d0]", children: [_jsx(ListChecks, { className: "h-3.5 w-3.5" }), "Requirements"] }), _jsx("ul", { className: "mt-2 list-inside list-disc space-y-1.5 text-sm text-[#0f1730]", children: requirements.map((req, idx) => (_jsx("li", { children: _jsx(HtmlOrText, { value: req, as: "span" }) }, idx))) })] }) })), minWords && (_jsxs("div", { className: "border-t border-[#e3edfb] bg-[#f9fbfe] px-5 py-3 text-sm font-semibold text-[#0f1730]", children: ["Write at least ", minWords, " words."] }))] }), guideImages && guideImages.length > 0 && (_jsx("div", { className: "flex flex-col items-center justify-center gap-4 rounded-2xl border border-[#d9e4f2] bg-white/60 p-4", children: guideImages.map((img, idx) => (_jsx(ResolvedImage, { src: img, alt: `Guide ${idx + 1}`, className: "w-full max-w-3xl rounded-xl bg-white object-contain shadow-sm" }, idx))) }))] }), _jsxs("div", { className: "overflow-hidden rounded-2xl border border-[#cfe0f7] bg-white shadow-[0_18px_40px_rgba(13,71,161,0.10)]", children: [_jsxs("div", { className: "flex items-center gap-1.5 border-b border-[#e3edfb] bg-[#f6faff] px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-[#0b57d0]", children: [_jsx(PenLine, { className: "h-3.5 w-3.5" }), "Your answer"] }), _jsx("textarea", { value: answer, disabled: isReviewMode, placeholder: "Write your essay here\u2026", onChange: (e) => onAnswerChange(firstQuestion.id, e.target.value), rows: 14, className: cn('w-full resize-y border-0 bg-white px-5 py-4 text-sm leading-relaxed text-[#0f1730] outline-none transition-all placeholder:text-[#9aa9c2] focus:bg-[#fbfdff]', isReviewMode && 'cursor-not-allowed opacity-80') }), _jsxs("div", { className: "flex flex-col gap-2 border-t border-[#e3edfb] bg-[#f9fbfe] px-4 py-3", children: [_jsxs("div", { className: "flex items-center justify-between text-xs", children: [_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 font-semibold', isValid
|
|
57
|
+
return (_jsxs("div", { id: englishCertQuestionDomId(firstQuestion.id), className: "scroll-mt-24 flex flex-col gap-5", children: [_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "overflow-hidden rounded-2xl border border-[#cfe0f7] bg-white shadow-[0_18px_40px_rgba(13,71,161,0.10)]", children: [prompt && (_jsx(HtmlOrText, { value: prompt, as: "div", className: "whitespace-pre-line px-5 pb-4 pt-5 text-base leading-relaxed text-[#0f1730]" })), passage?.trim() && (_jsx("div", { className: "px-5 pb-4", children: _jsxs("div", { className: "rounded-xl border border-[#e3edfb] bg-[#f6faff] p-4", children: [_jsx("p", { className: "text-[11px] font-bold uppercase tracking-wider text-[#0b57d0]", children: "Read" }), _jsx(HtmlOrText, { value: passage, as: "div", className: "mt-2 text-sm leading-relaxed text-[#0f1730]" })] }) })), requirements && requirements.length > 0 && (_jsx("div", { className: cn('px-5 pb-4', !prompt && 'pt-5'), children: _jsxs("div", { className: "rounded-xl border border-[#e3edfb] bg-[#f6faff] p-4", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-wider text-[#0b57d0]", children: [_jsx(ListChecks, { className: "h-3.5 w-3.5" }), "Requirements"] }), _jsx("ul", { className: "mt-2 list-inside list-disc space-y-1.5 text-sm text-[#0f1730]", children: requirements.map((req, idx) => (_jsx("li", { children: _jsx(HtmlOrText, { value: req, as: "span" }) }, idx))) })] }) })), minWords && (_jsxs("div", { className: "border-t border-[#e3edfb] bg-[#f9fbfe] px-5 py-3 text-sm font-semibold text-[#0f1730]", children: ["Write at least ", minWords, " words."] }))] }), guideImages && guideImages.length > 0 && (_jsx("div", { className: "flex flex-col items-center justify-center gap-4 rounded-2xl border border-[#d9e4f2] bg-white/60 p-4", children: guideImages.map((img, idx) => (_jsx(ResolvedImage, { src: img, alt: `Guide ${idx + 1}`, className: "w-full max-w-3xl rounded-xl bg-white object-contain shadow-sm" }, idx))) }))] }), _jsxs("div", { className: "overflow-hidden rounded-2xl border border-[#cfe0f7] bg-white shadow-[0_18px_40px_rgba(13,71,161,0.10)]", children: [_jsxs("div", { className: "flex items-center gap-1.5 border-b border-[#e3edfb] bg-[#f6faff] px-4 py-2.5 text-xs font-bold uppercase tracking-wider text-[#0b57d0]", children: [_jsx(PenLine, { className: "h-3.5 w-3.5" }), "Your answer"] }), _jsx("textarea", { value: answer, disabled: isReviewMode, placeholder: "Write your essay here\u2026", onChange: (e) => onAnswerChange(firstQuestion.id, e.target.value), rows: 14, className: cn('w-full resize-y border-0 bg-white px-5 py-4 text-sm leading-relaxed text-[#0f1730] outline-none transition-all placeholder:text-[#9aa9c2] focus:bg-[#fbfdff]', isReviewMode && 'cursor-not-allowed opacity-80') }), _jsxs("div", { className: "flex flex-col gap-2 border-t border-[#e3edfb] bg-[#f9fbfe] px-4 py-3", children: [_jsxs("div", { className: "flex items-center justify-between text-xs", children: [_jsxs("span", { className: cn('inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 font-semibold', isValid
|
|
58
58
|
? 'bg-[#ddf8e7] text-[#15803d]'
|
|
59
59
|
: 'bg-[#fde8e8] text-[#ef4444]'), children: [wordCount, " words"] }), (minWords || maxWords) && (_jsxs("span", { className: "font-medium text-[#64748b]", children: [minWords ? `Min ${minWords}` : '', minWords && maxWords ? ' · ' : '', maxWords ? `Max ${maxWords}` : ''] }))] }), progressTarget > 0 && (_jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[#e3edfb]", children: _jsx("div", { className: cn('h-full rounded-full transition-all', isValid ? 'bg-[#15803d]' : 'bg-[#1167e8]'), style: { width: `${progressPct}%` } }) })), statusMessage && (_jsxs("div", { className: cn('flex items-center gap-1.5 text-xs font-medium', statusMessage.ok ? 'text-[#15803d]' : 'text-[#64748b]'), children: [statusMessage.ok ? (_jsx(CheckCircle2, { className: "h-3.5 w-3.5 shrink-0" })) : (_jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0" })), statusMessage.text] }))] })] })] }));
|
|
60
60
|
}
|
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, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, } from './components/questions/viewer';
|
|
29
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } 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, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, } from './components/questions/viewer';
|
|
24
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, } from './components/questions/viewer';
|
|
@@ -45,6 +45,7 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
45
45
|
'CHOOSE_THE_CORRECT_ANSWER_GROUP': SkillEnum.READING,
|
|
46
46
|
'CHOOSE_CORRECT_ADJECTIVE': SkillEnum.READING,
|
|
47
47
|
'FILL_IN_BLANK': SkillEnum.READING,
|
|
48
|
+
'FILL_IN_BLANK_GROUP': SkillEnum.READING,
|
|
48
49
|
'FILL_MISSING_WORDS_IN_GRID': SkillEnum.READING,
|
|
49
50
|
'READ_PASSAGE_AND_ANSWER_QUESTIONS': SkillEnum.READING,
|
|
50
51
|
'ANSWER_THE_QUESTION': SkillEnum.READING,
|
|
@@ -52,6 +53,8 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
52
53
|
'WORD_ORDERING': SkillEnum.READING,
|
|
53
54
|
'WORD_FILL_PARAGRAPH': SkillEnum.READING,
|
|
54
55
|
'MATCH_BY_WRITING_ANSWER': SkillEnum.READING,
|
|
56
|
+
'MATCH_WORD_TO_PICTURE': SkillEnum.READING,
|
|
57
|
+
'MATCHING_WITH_LINES_GROUP': SkillEnum.READING,
|
|
55
58
|
// Visual-based reading questions
|
|
56
59
|
'LABEL_THE_PICTURE': SkillEnum.READING,
|
|
57
60
|
'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER': SkillEnum.READING,
|
|
@@ -62,6 +65,7 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
62
65
|
// Other reading types
|
|
63
66
|
'TRUE_FALSE': SkillEnum.READING,
|
|
64
67
|
'TRUE_FALSE_GROUP': SkillEnum.READING,
|
|
68
|
+
'TRUE_FALSE_CORRECT_GROUP': SkillEnum.READING,
|
|
65
69
|
'CROSS_OUT_WORD_GROUP': SkillEnum.READING,
|
|
66
70
|
'SPONTANEOUS_QA_GROUP': SkillEnum.SPEAKING,
|
|
67
71
|
'WORD_ORDER_AND_MATCH_GROUP': SkillEnum.READING,
|
|
@@ -175,6 +179,7 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
175
179
|
'CHOOSE_THE_CORRECT_ANSWER_GROUP': 'Chọn đáp án đúng (nhóm)',
|
|
176
180
|
'CHOOSE_CORRECT_ADJECTIVE': 'Chọn tính từ',
|
|
177
181
|
'FILL_IN_BLANK': 'Điền vào chỗ trống',
|
|
182
|
+
'FILL_IN_BLANK_GROUP': 'Điền chỗ trống (nhóm)',
|
|
178
183
|
'FILL_MISSING_WORDS_IN_GRID': 'Điền từ vào lưới',
|
|
179
184
|
'READ_PASSAGE_AND_ANSWER_QUESTIONS': 'Đọc hiểu - Trả lời',
|
|
180
185
|
'LABEL_THE_PICTURE': 'Gắn nhãn hình ảnh',
|
|
@@ -185,6 +190,7 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
185
190
|
'IMAGE_OBJECT_MATCHING': 'Ghép hình ảnh - Đối tượng',
|
|
186
191
|
'TRUE_FALSE': 'Đúng/Sai',
|
|
187
192
|
'TRUE_FALSE_GROUP': 'Đúng/Sai (nhóm)',
|
|
193
|
+
'TRUE_FALSE_CORRECT_GROUP': 'Đúng/Sai + sửa câu (nhóm)',
|
|
188
194
|
'CROSS_OUT_WORD_GROUP': 'Gạch từ / sửa từ (nhóm)',
|
|
189
195
|
'SPONTANEOUS_QA_GROUP': 'Hỏi đáp tự phát (nhóm)',
|
|
190
196
|
'WORD_ORDER_AND_MATCH_GROUP': 'Sắp xếp từ và nối nhãn (nhóm)',
|
|
@@ -205,6 +211,8 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
205
211
|
'WORD_ORDERING': 'Sắp xếp từ',
|
|
206
212
|
'WORD_FILL_PARAGRAPH': 'Điền từ vào đoạn văn',
|
|
207
213
|
'MATCH_BY_WRITING_ANSWER': 'Ghép đáp án bằng cách viết',
|
|
214
|
+
'MATCH_WORD_TO_PICTURE': 'Nối từ với tranh',
|
|
215
|
+
'MATCHING_WITH_LINES_GROUP': 'Nối bằng đường kẻ (nhóm)',
|
|
208
216
|
'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
|
|
209
217
|
'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
|
|
210
218
|
'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
|
|
@@ -9,7 +9,9 @@ export interface PresignedUrlResponse {
|
|
|
9
9
|
* - Empty/undefined → empty string.
|
|
10
10
|
* - Already a URL (http/https/blob/data) → returned unchanged.
|
|
11
11
|
* - An S3 storage key (e.g. `exams/:id/audio/x.wav`) → resolved to a presigned
|
|
12
|
-
* URL via the host API
|
|
12
|
+
* URL via the host API. Tries `/api/v1/exam-storage/:key` (oe-class proxy)
|
|
13
|
+
* then `/api/v1/storage/:key` (oe-exam-api). Override with
|
|
14
|
+
* `NEXT_PUBLIC_EXAM_STORAGE_PATH`.
|
|
13
15
|
*/
|
|
14
16
|
export declare function usePresignedFileUrl(fileKey: string | undefined): {
|
|
15
17
|
previewUrl: string;
|
|
@@ -6,7 +6,14 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
6
6
|
* owns them. Reads NEXT_PUBLIC_API_URL — inlined at build time in the host.
|
|
7
7
|
*/
|
|
8
8
|
const HOST_API_BASE_URL = (process.env.NEXT_PUBLIC_API_URL ?? '').replace(/\/+$/, '');
|
|
9
|
-
const
|
|
9
|
+
const DEFAULT_STORAGE_PATHS = ['/api/v1/exam-storage', '/api/v1/storage'];
|
|
10
|
+
function getStoragePaths() {
|
|
11
|
+
const configured = process.env.NEXT_PUBLIC_EXAM_STORAGE_PATH;
|
|
12
|
+
const paths = configured
|
|
13
|
+
? [configured, ...DEFAULT_STORAGE_PATHS]
|
|
14
|
+
: DEFAULT_STORAGE_PATHS;
|
|
15
|
+
return [...new Set(paths.map((path) => path.replace(/\/+$/, '')).filter(Boolean))];
|
|
16
|
+
}
|
|
10
17
|
/** A resolved key is displayable as-is when it's already a URL or inline data. */
|
|
11
18
|
function isDisplayableUrl(value) {
|
|
12
19
|
return (value.startsWith('http://') ||
|
|
@@ -33,17 +40,26 @@ async function resolvePresignedUrl(key) {
|
|
|
33
40
|
// caller at least doesn't crash. It won't load, but nothing throws.
|
|
34
41
|
return key;
|
|
35
42
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
let lastError = null;
|
|
44
|
+
for (const storagePath of getStoragePaths()) {
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(`${HOST_API_BASE_URL}${storagePath}/${encodeURIComponent(key)}`, { credentials: 'include' });
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Failed to resolve file URL (${response.status})`);
|
|
49
|
+
}
|
|
50
|
+
const body = (await response.json());
|
|
51
|
+
const resolved = 'data' in body ? body.data?.url : body.url;
|
|
52
|
+
if (!resolved) {
|
|
53
|
+
throw new Error('Presigned URL missing in response');
|
|
54
|
+
}
|
|
55
|
+
urlCache.set(key, resolved);
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
60
|
+
}
|
|
44
61
|
}
|
|
45
|
-
|
|
46
|
-
return resolved;
|
|
62
|
+
throw lastError ?? new Error('Failed to resolve file URL');
|
|
47
63
|
})().finally(() => {
|
|
48
64
|
inFlight.delete(key);
|
|
49
65
|
});
|
|
@@ -56,7 +72,9 @@ async function resolvePresignedUrl(key) {
|
|
|
56
72
|
* - Empty/undefined → empty string.
|
|
57
73
|
* - Already a URL (http/https/blob/data) → returned unchanged.
|
|
58
74
|
* - An S3 storage key (e.g. `exams/:id/audio/x.wav`) → resolved to a presigned
|
|
59
|
-
* URL via the host API
|
|
75
|
+
* URL via the host API. Tries `/api/v1/exam-storage/:key` (oe-class proxy)
|
|
76
|
+
* then `/api/v1/storage/:key` (oe-exam-api). Override with
|
|
77
|
+
* `NEXT_PUBLIC_EXAM_STORAGE_PATH`.
|
|
60
78
|
*/
|
|
61
79
|
export function usePresignedFileUrl(fileKey) {
|
|
62
80
|
const safeKey = typeof fileKey === 'string' ? fileKey : '';
|
|
@@ -145,6 +145,23 @@ const reverseGroupPayload = (apiQuestion) => {
|
|
|
145
145
|
},
|
|
146
146
|
};
|
|
147
147
|
};
|
|
148
|
+
function normalizeChooseAnswerGroupImageUrl(value) {
|
|
149
|
+
if (typeof value === 'string') {
|
|
150
|
+
const trimmed = value.trim();
|
|
151
|
+
return trimmed !== '' ? trimmed : undefined;
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(value)) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const urls = value
|
|
157
|
+
.filter((url) => typeof url === 'string')
|
|
158
|
+
.map((url) => url.trim())
|
|
159
|
+
.filter((url) => url !== '');
|
|
160
|
+
if (urls.length === 0) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
return urls.length === 1 ? urls[0] : urls;
|
|
164
|
+
}
|
|
148
165
|
const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
149
166
|
const content = asRecord(apiQuestion.content);
|
|
150
167
|
const answer = readAnswer(apiQuestion);
|
|
@@ -155,12 +172,15 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
155
172
|
const itemAnswer = asRecord(item.correctAnswer);
|
|
156
173
|
const options = asArray(itemContent.options).map((rawOption, optionIndex) => {
|
|
157
174
|
const option = asRecord(rawOption);
|
|
175
|
+
const imageUrl = asString(option.imageUrl);
|
|
158
176
|
return {
|
|
159
177
|
id: asString(option.id, `opt-${optionIndex + 1}`),
|
|
160
178
|
text: asString(option.text),
|
|
179
|
+
...(imageUrl ? { imageUrl } : {}),
|
|
161
180
|
};
|
|
162
181
|
});
|
|
163
182
|
const isExample = item.isExample === true;
|
|
183
|
+
const imageUrl = normalizeChooseAnswerGroupImageUrl(itemContent.imageUrl);
|
|
164
184
|
return {
|
|
165
185
|
question: asString(itemContent.question),
|
|
166
186
|
optionType: asString(itemContent.optionType, 'text') === 'image' ? 'image' : 'text',
|
|
@@ -173,6 +193,7 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
173
193
|
isExample,
|
|
174
194
|
points: isExample ? 0 : asNumber(item.points, 1),
|
|
175
195
|
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
196
|
+
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
176
197
|
};
|
|
177
198
|
});
|
|
178
199
|
return {
|
|
@@ -256,6 +277,121 @@ const reverseTrueFalseGroup = (apiQuestion) => {
|
|
|
256
277
|
},
|
|
257
278
|
};
|
|
258
279
|
};
|
|
280
|
+
const reverseTrueFalseCorrectGroup = (apiQuestion) => {
|
|
281
|
+
const content = asRecord(apiQuestion.content);
|
|
282
|
+
const answer = readAnswer(apiQuestion);
|
|
283
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
284
|
+
const items = asArray(content.items).map((rawItem, index) => {
|
|
285
|
+
const item = asRecord(rawItem);
|
|
286
|
+
const itemContent = asRecord(item.content);
|
|
287
|
+
const itemAnswer = asRecord(asRecord(item.correctAnswer).answer);
|
|
288
|
+
const fallback = asRecord(fallbackAnswers[index]);
|
|
289
|
+
const rawAnswer = Object.keys(itemAnswer).length > 0 ? itemAnswer : fallback;
|
|
290
|
+
const isExample = item.isExample === true;
|
|
291
|
+
const isTrue = rawAnswer.isTrue === true;
|
|
292
|
+
return {
|
|
293
|
+
question: asString(itemContent.question),
|
|
294
|
+
isTrue,
|
|
295
|
+
correction: isTrue ? '' : asString(rawAnswer.correction),
|
|
296
|
+
isExample,
|
|
297
|
+
points: isExample ? 0 : asNumber(item.points, 1),
|
|
298
|
+
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
content: '',
|
|
303
|
+
answer: {
|
|
304
|
+
instruction: asString(asRecord(content.meta).instruction, 'Tick (✓) the true and cross (✗) the false sentences. Correct the false sentences.'),
|
|
305
|
+
items,
|
|
306
|
+
explanation: asString(apiQuestion.explanation),
|
|
307
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
const reverseFillInBlankGroup = (apiQuestion) => {
|
|
312
|
+
const content = asRecord(apiQuestion.content);
|
|
313
|
+
const answer = readAnswer(apiQuestion);
|
|
314
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
315
|
+
const meta = asRecord(content.meta);
|
|
316
|
+
const items = asArray(content.items).map((rawItem, index) => {
|
|
317
|
+
const item = asRecord(rawItem);
|
|
318
|
+
const itemContent = asRecord(item.content);
|
|
319
|
+
const itemAnswer = asRecord(item.correctAnswer);
|
|
320
|
+
const fallback = asRecord(fallbackAnswers[index]);
|
|
321
|
+
const isExample = item.isExample === true;
|
|
322
|
+
const question = asString(itemContent.question, '{0}');
|
|
323
|
+
const rawAnswers = itemAnswer.answers ?? fallback.answers;
|
|
324
|
+
const answers = asArray(rawAnswers).map((blank) => {
|
|
325
|
+
const alts = Array.isArray(blank)
|
|
326
|
+
? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
|
|
327
|
+
: [String(blank ?? '').trim()].filter(Boolean);
|
|
328
|
+
return alts.length > 0 ? alts : [''];
|
|
329
|
+
});
|
|
330
|
+
return {
|
|
331
|
+
question,
|
|
332
|
+
answers: answers.length > 0 ? answers : [['']],
|
|
333
|
+
caseSensitive: itemAnswer.caseSensitive === true || fallback.caseSensitive === true,
|
|
334
|
+
imageUrl: asString(itemContent.imageUrl),
|
|
335
|
+
audioUrl: asString(itemContent.audioUrl),
|
|
336
|
+
showHints: itemContent.showHints === true,
|
|
337
|
+
hints: asArray(itemContent.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
|
|
338
|
+
isExample,
|
|
339
|
+
points: isExample ? 0 : asNumber(item.points, 1),
|
|
340
|
+
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
content: '',
|
|
345
|
+
answer: {
|
|
346
|
+
instruction: asString(meta.instruction, 'Complete the sentences.'),
|
|
347
|
+
audioUrl: asString(meta.audioUrl),
|
|
348
|
+
imageUrl: asString(meta.imageUrl),
|
|
349
|
+
showHints: meta.showHints === true,
|
|
350
|
+
hints: asArray(meta.hints).map((hint) => String(hint ?? '').trim()).filter(Boolean),
|
|
351
|
+
items,
|
|
352
|
+
explanation: asString(apiQuestion.explanation),
|
|
353
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
const reverseMatchingWithLinesGroup = (apiQuestion) => {
|
|
358
|
+
const content = asRecord(apiQuestion.content);
|
|
359
|
+
const answer = readAnswer(apiQuestion);
|
|
360
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
361
|
+
const meta = asRecord(content.meta);
|
|
362
|
+
const rightItems = asArray(content.rightItems).map((raw, index) => {
|
|
363
|
+
const item = asRecord(raw);
|
|
364
|
+
return {
|
|
365
|
+
id: asString(item.id, `r${index + 1}`),
|
|
366
|
+
text: asString(item.text),
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
const items = asArray(content.items).map((rawItem, index) => {
|
|
370
|
+
const item = asRecord(rawItem);
|
|
371
|
+
const itemContent = asRecord(item.content);
|
|
372
|
+
const itemAnswer = asRecord(item.correctAnswer);
|
|
373
|
+
const fallback = fallbackAnswers[index];
|
|
374
|
+
const isExample = item.isExample === true;
|
|
375
|
+
return {
|
|
376
|
+
imageUrl: asString(itemContent.imageUrl),
|
|
377
|
+
correctAnswer: asString(itemAnswer.answer, typeof fallback === 'string' ? fallback : ''),
|
|
378
|
+
isExample,
|
|
379
|
+
points: isExample ? 0 : asNumber(item.points, 1),
|
|
380
|
+
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
content: '',
|
|
385
|
+
answer: {
|
|
386
|
+
title: asString(meta.title),
|
|
387
|
+
instruction: asString(meta.instruction, 'Look and match.'),
|
|
388
|
+
rightItems,
|
|
389
|
+
items,
|
|
390
|
+
explanation: asString(apiQuestion.explanation),
|
|
391
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
};
|
|
259
395
|
const reverseCrossOutWordGroup = (apiQuestion) => {
|
|
260
396
|
const content = asRecord(apiQuestion.content);
|
|
261
397
|
const answer = readAnswer(apiQuestion);
|
|
@@ -407,6 +543,9 @@ const handlers = {
|
|
|
407
543
|
CHOOSE_THE_CORRECT_ANSWER_GROUP: reverseChooseTheCorrectAnswerGroup,
|
|
408
544
|
ANSWER_THE_QUESTION_GROUP: reverseAnswerTheQuestionGroup,
|
|
409
545
|
TRUE_FALSE_GROUP: reverseTrueFalseGroup,
|
|
546
|
+
TRUE_FALSE_CORRECT_GROUP: reverseTrueFalseCorrectGroup,
|
|
547
|
+
FILL_IN_BLANK_GROUP: reverseFillInBlankGroup,
|
|
548
|
+
MATCHING_WITH_LINES_GROUP: reverseMatchingWithLinesGroup,
|
|
410
549
|
CROSS_OUT_WORD_GROUP: reverseCrossOutWordGroup,
|
|
411
550
|
WORD_ORDER_AND_MATCH_GROUP: reverseWordOrderAndMatchGroup,
|
|
412
551
|
WORD_ORDER_AND_MATCH: reverseWordOrderAndMatchGroup,
|
|
@@ -424,6 +563,7 @@ const handlers = {
|
|
|
424
563
|
WORD_ORDERING: reverseSimpleAnswer,
|
|
425
564
|
WORD_FILL_PARAGRAPH: reverseSimpleAnswer,
|
|
426
565
|
MATCH_BY_WRITING_ANSWER: reverseGroupPayload,
|
|
566
|
+
MATCH_WORD_TO_PICTURE: reverseGroupPayload,
|
|
427
567
|
WRITE_SHORT_PARAGRAPH: reverseWriteShortParagraph,
|
|
428
568
|
WRITE_A_SHORT_LETTER: reverseWriteAShortLetter,
|
|
429
569
|
WRITE_CORRECT_VERB_FORM: reverseSimpleAnswer,
|
|
@@ -454,6 +594,7 @@ function reverseWriteAShortLetter(apiQuestion) {
|
|
|
454
594
|
const answer = readAnswer(apiQuestion);
|
|
455
595
|
const informations = asArray(content.informations ?? answer.informations).map((item) => asString(item)).filter(Boolean);
|
|
456
596
|
const guideImages = asArray(content.guideImages ?? answer.guideImages).map((item) => asString(item)).filter(Boolean);
|
|
597
|
+
const passage = asString(content.passage ?? answer.passage);
|
|
457
598
|
const contexts = asArray(content.contexts ?? answer.contexts);
|
|
458
599
|
const instructions = isRecord(content.instructions)
|
|
459
600
|
? content.instructions
|
|
@@ -471,6 +612,7 @@ function reverseWriteAShortLetter(apiQuestion) {
|
|
|
471
612
|
instruction: asString(instructions.mainInstruction || instructions.instruction),
|
|
472
613
|
informations,
|
|
473
614
|
guideImages,
|
|
615
|
+
...(passage ? { passage } : {}),
|
|
474
616
|
contexts,
|
|
475
617
|
minWords,
|
|
476
618
|
maxWords,
|
|
@@ -13,6 +13,8 @@ import { transformLabelThePicture } from '../../../components/questions/types/la
|
|
|
13
13
|
import { transformChooseTheCorrectAnswer } from '../../../components/questions/types/choose-the-correct-answer/transform';
|
|
14
14
|
import { transformChooseTheCorrectAnswerGroup } from '../../../components/questions/types/choose-the-correct-answer-group/transform';
|
|
15
15
|
import { transformCrossOutWordGroup } from '../../../components/questions/types/cross-out-word-group/transform';
|
|
16
|
+
import { transformTrueFalseCorrectGroup } from '../../../components/questions/types/true-false-correct-group/transform';
|
|
17
|
+
import { transformFillInBlankGroup } from '../../../components/questions/types/fill-in-blank-group/transform';
|
|
16
18
|
import { transformFillInBlank } from '../../../components/questions/types/fill-in-blank/transform';
|
|
17
19
|
import { transformWriteCorrectVerbForm } from '../../../components/questions/types/write-correct-verb-form/transform';
|
|
18
20
|
import { transformChooseCorrectAdjective } from '../../../components/questions/types/choose-correct-adjective/transform';
|
|
@@ -22,6 +24,8 @@ import { transformTrueFalse } from '../../../components/questions/types/true-fal
|
|
|
22
24
|
import { transformWordOrdering } from '../../../components/questions/types/word-ordering/transform';
|
|
23
25
|
import { transformWordFillParagraph } from '../../../components/questions/types/word-fill-paragraph/transform';
|
|
24
26
|
import { transformMatchByWritingAnswer } from '../../../components/questions/types/match-by-writing-answer/transform';
|
|
27
|
+
import { transformMatchWordToPicture } from '../../../components/questions/types/match-word-to-picture/transform';
|
|
28
|
+
import { transformMatchingWithLinesGroup } from '../../../components/questions/types/matching-with-lines-group/transform';
|
|
25
29
|
import { transformWriteSentences } from '../../../components/questions/types/write-sentences/transform';
|
|
26
30
|
import { transformListenAndChooseFromAnswerGroup } from '../../../components/questions/types/listen-and-choose-from-answer-group/transform';
|
|
27
31
|
import { transformListenAndChooseObjectsInScene } from '../../../components/questions/types/listen-and-choose-objects-in-scene/transform';
|
|
@@ -62,6 +66,8 @@ const questionTransformHandlers = {
|
|
|
62
66
|
CHOOSE_THE_CORRECT_ANSWER: transformChooseTheCorrectAnswer,
|
|
63
67
|
CHOOSE_THE_CORRECT_ANSWER_GROUP: transformChooseTheCorrectAnswerGroup,
|
|
64
68
|
CROSS_OUT_WORD_GROUP: transformCrossOutWordGroup,
|
|
69
|
+
TRUE_FALSE_CORRECT_GROUP: transformTrueFalseCorrectGroup,
|
|
70
|
+
FILL_IN_BLANK_GROUP: transformFillInBlankGroup,
|
|
65
71
|
FILL_IN_BLANK: transformFillInBlank,
|
|
66
72
|
WRITE_CORRECT_VERB_FORM: transformWriteCorrectVerbForm,
|
|
67
73
|
CHOOSE_CORRECT_ADJECTIVE: transformChooseCorrectAdjective,
|
|
@@ -71,6 +77,8 @@ const questionTransformHandlers = {
|
|
|
71
77
|
WORD_ORDERING: transformWordOrdering,
|
|
72
78
|
WORD_FILL_PARAGRAPH: transformWordFillParagraph,
|
|
73
79
|
MATCH_BY_WRITING_ANSWER: transformMatchByWritingAnswer,
|
|
80
|
+
MATCH_WORD_TO_PICTURE: transformMatchWordToPicture,
|
|
81
|
+
MATCHING_WITH_LINES_GROUP: transformMatchingWithLinesGroup,
|
|
74
82
|
WRITE_SENTENCES: transformWriteSentences,
|
|
75
83
|
LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP: transformListenAndChooseFromAnswerGroup,
|
|
76
84
|
LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE: transformListenAndChooseObjectsInScene,
|
|
@@ -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' | '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' | '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';
|
|
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';
|
|
@@ -7,6 +7,7 @@ export interface ChooseTheCorrectAnswerGroupItemContent {
|
|
|
7
7
|
question: string;
|
|
8
8
|
options: ChooseTheCorrectAnswerGroupOption[];
|
|
9
9
|
optionType?: 'text' | 'image';
|
|
10
|
+
imageUrl?: string | string[];
|
|
10
11
|
}
|
|
11
12
|
export interface ChooseTheCorrectAnswerGroupItem {
|
|
12
13
|
questionType?: 'CHOOSE_THE_CORRECT_ANSWER';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const FILL_IN_BLANK_ITEM_TYPE: "FILL_IN_BLANK";
|
|
2
|
+
export interface FillInBlankGroupItemAnswer {
|
|
3
|
+
answers: string[][];
|
|
4
|
+
caseSensitive?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface FillInBlankGroupItemContent {
|
|
7
|
+
question: string;
|
|
8
|
+
imageUrl?: string;
|
|
9
|
+
audioUrl?: string;
|
|
10
|
+
showHints?: boolean;
|
|
11
|
+
hints?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface FillInBlankGroupItem {
|
|
14
|
+
questionType?: typeof FILL_IN_BLANK_ITEM_TYPE;
|
|
15
|
+
isExample?: boolean;
|
|
16
|
+
content: FillInBlankGroupItemContent;
|
|
17
|
+
correctAnswer?: FillInBlankGroupItemAnswer;
|
|
18
|
+
points?: number;
|
|
19
|
+
questionNumber?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface FillInBlankGroupContent {
|
|
22
|
+
meta?: {
|
|
23
|
+
instruction?: string;
|
|
24
|
+
audioUrl?: string;
|
|
25
|
+
imageUrl?: string;
|
|
26
|
+
showHints?: boolean;
|
|
27
|
+
hints?: string[];
|
|
28
|
+
};
|
|
29
|
+
items: FillInBlankGroupItem[];
|
|
30
|
+
}
|
|
31
|
+
export interface FillInBlankGroupCorrectAnswer {
|
|
32
|
+
answer: FillInBlankGroupItemAnswer[];
|
|
33
|
+
}
|
|
34
|
+
export interface FillInBlankGroupAPIPayload {
|
|
35
|
+
id?: string;
|
|
36
|
+
questionType: 'FILL_IN_BLANK_GROUP';
|
|
37
|
+
content: FillInBlankGroupContent;
|
|
38
|
+
correctAnswer: FillInBlankGroupCorrectAnswer;
|
|
39
|
+
points: number;
|
|
40
|
+
explanation?: string;
|
|
41
|
+
questionNumber: number;
|
|
42
|
+
partId?: string;
|
|
43
|
+
partNo?: number;
|
|
44
|
+
}
|
|
45
|
+
export type FillInBlankGroupLocalStorage = FillInBlankGroupAPIPayload;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FILL_IN_BLANK_ITEM_TYPE = 'FILL_IN_BLANK';
|
|
@@ -32,3 +32,7 @@ export * from './spontaneous-qa';
|
|
|
32
32
|
export * from './word-order-and-match-group';
|
|
33
33
|
export * from './crossword-puzzle';
|
|
34
34
|
export * from './find-words-in-matrix';
|
|
35
|
+
export * from './match-word-to-picture';
|
|
36
|
+
export * from './matching-with-lines-group';
|
|
37
|
+
export * from './true-false-correct-group';
|
|
38
|
+
export * from './fill-in-blank-group';
|
|
@@ -50,3 +50,11 @@ export * from './word-order-and-match-group';
|
|
|
50
50
|
export * from './crossword-puzzle';
|
|
51
51
|
// FIND_WORDS_IN_MATRIX
|
|
52
52
|
export * from './find-words-in-matrix';
|
|
53
|
+
// MATCH_WORD_TO_PICTURE
|
|
54
|
+
export * from './match-word-to-picture';
|
|
55
|
+
// MATCHING_WITH_LINES_GROUP
|
|
56
|
+
export * from './matching-with-lines-group';
|
|
57
|
+
// TRUE_FALSE_CORRECT_GROUP
|
|
58
|
+
export * from './true-false-correct-group';
|
|
59
|
+
// FILL_IN_BLANK_GROUP
|
|
60
|
+
export * from './fill-in-blank-group';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Types for MATCH_WORD_TO_PICTURE question type
|
|
3
|
+
* Reference: .agents/templates/MATCH_WORD_TO_PICTURE.sample.json
|
|
4
|
+
*/
|
|
5
|
+
import { QuestionBankSettings } from './choose-the-correct-answer';
|
|
6
|
+
export interface MatchWordToPictureContent {
|
|
7
|
+
imageUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MatchWordToPictureCorrectAnswer {
|
|
10
|
+
answer: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MatchWordToPictureGroupPayload {
|
|
13
|
+
wordBank: string[];
|
|
14
|
+
instruction?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MatchWordToPictureAPIPayload {
|
|
17
|
+
id?: string;
|
|
18
|
+
questionType: 'MATCH_WORD_TO_PICTURE';
|
|
19
|
+
content: MatchWordToPictureContent;
|
|
20
|
+
correctAnswer: MatchWordToPictureCorrectAnswer;
|
|
21
|
+
isGroup: true;
|
|
22
|
+
isExample?: boolean;
|
|
23
|
+
questionGroup: {
|
|
24
|
+
groupNumber: number;
|
|
25
|
+
payload: MatchWordToPictureGroupPayload;
|
|
26
|
+
};
|
|
27
|
+
points: number;
|
|
28
|
+
explanation?: string;
|
|
29
|
+
questionNumber: number;
|
|
30
|
+
partId?: string;
|
|
31
|
+
partNo?: number;
|
|
32
|
+
addToQuestionBank?: boolean;
|
|
33
|
+
questionBank?: QuestionBankSettings;
|
|
34
|
+
}
|
|
35
|
+
export type MatchWordToPictureLocalStorage = Omit<MatchWordToPictureAPIPayload, 'addToQuestionBank' | 'questionBank'>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface MatchingWithLinesRightItem {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
export interface MatchingWithLinesGroupItemContent {
|
|
6
|
+
imageUrl: string;
|
|
7
|
+
}
|
|
8
|
+
export interface MatchingWithLinesGroupItem {
|
|
9
|
+
isExample?: boolean;
|
|
10
|
+
content: MatchingWithLinesGroupItemContent;
|
|
11
|
+
correctAnswer?: {
|
|
12
|
+
answer: string;
|
|
13
|
+
};
|
|
14
|
+
points?: number;
|
|
15
|
+
questionNumber?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MatchingWithLinesGroupContent {
|
|
18
|
+
meta?: {
|
|
19
|
+
instruction?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
};
|
|
22
|
+
rightItems: MatchingWithLinesRightItem[];
|
|
23
|
+
items: MatchingWithLinesGroupItem[];
|
|
24
|
+
}
|
|
25
|
+
export interface MatchingWithLinesGroupCorrectAnswer {
|
|
26
|
+
answer: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface MatchingWithLinesGroupAPIPayload {
|
|
29
|
+
id?: string;
|
|
30
|
+
questionType: 'MATCHING_WITH_LINES_GROUP';
|
|
31
|
+
content: MatchingWithLinesGroupContent;
|
|
32
|
+
correctAnswer: MatchingWithLinesGroupCorrectAnswer;
|
|
33
|
+
points: number;
|
|
34
|
+
explanation?: string;
|
|
35
|
+
questionNumber: number;
|
|
36
|
+
partId?: string;
|
|
37
|
+
partNo?: number;
|
|
38
|
+
}
|
|
39
|
+
export type MatchingWithLinesGroupLocalStorage = MatchingWithLinesGroupAPIPayload;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const TRUE_FALSE_CORRECT_ITEM_TYPE: "TRUE_FALSE_CORRECT";
|
|
2
|
+
export interface TrueFalseCorrectItemAnswer {
|
|
3
|
+
isTrue: boolean;
|
|
4
|
+
correction: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TrueFalseCorrectGroupItemContent {
|
|
7
|
+
question: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TrueFalseCorrectGroupItem {
|
|
10
|
+
questionType?: typeof TRUE_FALSE_CORRECT_ITEM_TYPE;
|
|
11
|
+
isExample?: boolean;
|
|
12
|
+
content: TrueFalseCorrectGroupItemContent;
|
|
13
|
+
correctAnswer?: {
|
|
14
|
+
answer: TrueFalseCorrectItemAnswer;
|
|
15
|
+
};
|
|
16
|
+
points?: number;
|
|
17
|
+
questionNumber?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface TrueFalseCorrectGroupContent {
|
|
20
|
+
meta?: {
|
|
21
|
+
instruction?: string;
|
|
22
|
+
};
|
|
23
|
+
items: TrueFalseCorrectGroupItem[];
|
|
24
|
+
}
|
|
25
|
+
export interface TrueFalseCorrectGroupCorrectAnswer {
|
|
26
|
+
answer: TrueFalseCorrectItemAnswer[];
|
|
27
|
+
}
|
|
28
|
+
export interface TrueFalseCorrectGroupAPIPayload {
|
|
29
|
+
id?: string;
|
|
30
|
+
questionType: 'TRUE_FALSE_CORRECT_GROUP';
|
|
31
|
+
content: TrueFalseCorrectGroupContent;
|
|
32
|
+
correctAnswer: TrueFalseCorrectGroupCorrectAnswer;
|
|
33
|
+
points: number;
|
|
34
|
+
explanation?: string;
|
|
35
|
+
questionNumber: number;
|
|
36
|
+
partId?: string;
|
|
37
|
+
partNo?: number;
|
|
38
|
+
}
|
|
39
|
+
export type TrueFalseCorrectGroupLocalStorage = TrueFalseCorrectGroupAPIPayload;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TRUE_FALSE_CORRECT_ITEM_TYPE = 'TRUE_FALSE_CORRECT';
|