Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const questionTypeIconMap: Record<Exclude<QuizQuestionType, 'single_choice' | 'i
scale: 'quizRange',
pin_image: 'quizPin',
coordinates: 'quizGraph',
puzzle: 'quizImageMatching',
h5p: 'quizH5p',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const questionTypes = {
label: __('Pin', 'tutor'),
icon: 'quizPin',
},
puzzle: {
label: __('Puzzle', 'tutor'),
icon: 'quizImageMatching',
},
h5p: {
label: __('H5P', 'tutor'),
icon: 'quizTrueFalse',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import DrawImage from '@CourseBuilderComponents/curriculum/question-types/DrawIm
import Scale from '@CourseBuilderComponents/curriculum/question-types/Scale';
import PinImage from '@CourseBuilderComponents/curriculum/question-types/PinImage';
import Coordinates from '@CourseBuilderComponents/curriculum/question-types/Coordinates';
import Puzzle from '@CourseBuilderComponents/curriculum/question-types/Puzzle';
import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';

import { tutorConfig } from '@TutorShared/config/config';
Expand Down Expand Up @@ -62,6 +63,7 @@ const QuestionForm = () => {
scale: <Scale key={activeQuestionId} />,
pin_image: <PinImage key={activeQuestionId} />,
coordinates: <Coordinates key={activeQuestionId} />,
puzzle: <Puzzle key={activeQuestionId} />,
} as const;

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ const questionTypeOptions: {
icon: 'quizGraph',
isPro: true,
},
{
label: __('Puzzle', 'tutor'),
value: 'puzzle',
icon: 'quizImageMatching',
isPro: true,
},
];

const isTutorPro = !!tutorConfig.tutor_pro_url;
Expand All @@ -139,7 +145,8 @@ const QuestionList = ({ isEditing }: { isEditing: boolean }) => {
option.value !== 'draw_image' &&
option.value !== 'pin_image' &&
option.value !== 'scale' &&
option.value !== 'coordinates',
option.value !== 'coordinates' &&
option.value !== 'puzzle',
);
}
return questionTypeOptions;
Expand Down Expand Up @@ -289,7 +296,50 @@ const QuestionList = ({ isEditing }: { isEditing: boolean }) => {
is_correct: '1',
},
]
: [],
: questionType === 'scale'
? [
{
_data_status: QuizDataStatus.NEW,
// Keep the initial default scale config valid for immediate save flow.
is_saved: true,
answer_id: nanoid(),
answer_title: '',
belongs_question_id: questionId,
belongs_question_type: 'scale',
answer_two_gap_match: JSON.stringify({
value: 50,
config: {
min: 0,
max: 100,
step: 1,
defaultValue: 50,
pxPerUnit: 10,
labelEvery: 10,
minorTickEvery: 5,
precision: 0,
},
}),
answer_view_format: 'scale',
answer_order: 0,
is_correct: '1',
},
]
: questionType === 'puzzle'
? [
{
_data_status: QuizDataStatus.NEW,
is_saved: true,
answer_id: nanoid(),
answer_title: '',
belongs_question_id: questionId,
belongs_question_type: 'puzzle',
answer_two_gap_match: '',
answer_view_format: 'puzzle',
answer_order: 0,
is_correct: '1',
},
]
: [],
answer_explanation: '',
question_mark: 1,
question_order: questionFields.length + 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { css } from '@emotion/react';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';
import type { QuizForm } from '@CourseBuilderServices/quiz';
import FormCoordinates from '@TutorShared/components/fields/quiz/questions/FormCoordinates';
import { spacing } from '@TutorShared/config/styles';
import { styleUtils } from '@TutorShared/utils/style-utils';
import { QuizDataStatus, type QuizQuestionOption } from '@TutorShared/utils/types';
import { nanoid } from '@TutorShared/utils/util';

const Coordinates = () => {
const form = useFormContext<QuizForm>();
Expand All @@ -21,30 +18,6 @@ const Coordinates = () => {
name: answersPath,
});

useEffect(() => {
if (!activeQuestionId) {
return;
}
if (optionsFields.length > 0) {
return;
}
const baseAnswer: QuizQuestionOption = {
_data_status: QuizDataStatus.NEW,
is_saved: true,
answer_id: nanoid(),
belongs_question_id: activeQuestionId,
belongs_question_type: 'coordinates' as QuizQuestionOption['belongs_question_type'],
answer_title: '',
is_correct: '1',
image_id: undefined,
image_url: '',
answer_two_gap_match: JSON.stringify([{ x: 0, y: 0 }]),
answer_view_format: 'coordinates',
answer_order: 0,
};
form.setValue(answersPath, [baseAnswer]);
}, [activeQuestionId, optionsFields.length, answersPath, form]);

if (optionsFields.length === 0) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { css } from '@emotion/react';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';
import type { QuizForm } from '@CourseBuilderServices/quiz';
import FormPinImage from '@TutorShared/components/fields/quiz/questions/FormPinImage';
import { spacing } from '@TutorShared/config/styles';
import { styleUtils } from '@TutorShared/utils/style-utils';
import { QuizDataStatus, type QuizQuestionOption } from '@TutorShared/utils/types';
import { nanoid } from '@TutorShared/utils/util';

const PinImage = () => {
const form = useFormContext<QuizForm>();
Expand All @@ -21,31 +18,6 @@ const PinImage = () => {
name: answersPath,
});

// Ensure there is always a single option for this question type.
useEffect(() => {
if (!activeQuestionId) {
return;
}
if (optionsFields.length > 0) {
return;
}
const baseAnswer: QuizQuestionOption = {
_data_status: QuizDataStatus.NEW,
is_saved: false,
answer_id: nanoid(),
belongs_question_id: activeQuestionId,
belongs_question_type: 'pin_image' as QuizQuestionOption['belongs_question_type'],
answer_title: '',
is_correct: '1',
image_id: undefined,
image_url: '',
answer_two_gap_match: '',
answer_view_format: 'pin_image',
answer_order: 0,
};
form.setValue(answersPath, [baseAnswer]);
}, [activeQuestionId, optionsFields.length, answersPath, form]);

// Only render Controller when the value exists to ensure field.value is always defined
if (optionsFields.length === 0) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { css } from '@emotion/react';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';
import type { QuizForm } from '@CourseBuilderServices/quiz';
import FormPuzzle from '@TutorShared/components/fields/quiz/questions/FormPuzzle';
import { spacing } from '@TutorShared/config/styles';
import { styleUtils } from '@TutorShared/utils/style-utils';

const Puzzle = () => {
const form = useFormContext<QuizForm>();
const { activeQuestionId, activeQuestionIndex, validationError, setValidationError } = useQuizModalContext();

const answersPath = `questions.${activeQuestionIndex}.question_answers` as 'questions.0.question_answers';
const gridSizePath =
`questions.${activeQuestionIndex}.question_settings.puzzle_grid_size` as 'questions.0.question_settings.puzzle_grid_size';

const { fields: optionsFields } = useFieldArray({
control: form.control,
name: answersPath,
});

useEffect(() => {
const currentValue = form.getValues(gridSizePath);
const validGridSizes = [2, 3, 4, 5, 6, 7];
if (!validGridSizes.includes(Number(currentValue))) {
form.setValue(gridSizePath, 4);
}
}, [form, gridSizePath]);

if (optionsFields.length === 0) {
return null;
}

return (
<div css={styles.optionWrapper}>
<Controller
key={optionsFields[0]?.id}
control={form.control}
name={`questions.${activeQuestionIndex}.question_answers.0` as 'questions.0.question_answers.0'}
render={(answerControllerProps) => (
<FormPuzzle
{...answerControllerProps}
questionId={activeQuestionId}
activeQuestionIndex={activeQuestionIndex}
validationError={validationError}
setValidationError={setValidationError}
/>
)}
/>
</div>
);
};

export default Puzzle;

const styles = {
optionWrapper: css`
${styleUtils.display.flex('column')};
gap: ${spacing[16]};
padding-left: ${spacing[40]};
`,
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { css } from '@emotion/react';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';
import type { QuizForm } from '@CourseBuilderServices/quiz';
import FormScale from '@TutorShared/components/fields/quiz/questions/FormScale';
import { spacing } from '@TutorShared/config/styles';
import { styleUtils } from '@TutorShared/utils/style-utils';
import { QuizDataStatus, type QuizQuestionOption } from '@TutorShared/utils/types';
import { nanoid } from '@TutorShared/utils/util';

const Scale = () => {
const form = useFormContext<QuizForm>();
Expand All @@ -21,45 +18,6 @@ const Scale = () => {
name: answersPath,
});

useEffect(() => {
if (!activeQuestionId) {
return;
}
if (optionsFields.length > 0) {
return;
}
const baseAnswer: QuizQuestionOption = {
_data_status: QuizDataStatus.NEW,
// Treat the initial default configuration as already saved so that
// validation doesn’t block adding another question when the instructor
// hasn’t interacted with the scale form yet.
is_saved: true,
answer_id: nanoid(),
belongs_question_id: activeQuestionId,
belongs_question_type: 'scale' as QuizQuestionOption['belongs_question_type'],
answer_title: '',
is_correct: '1',
image_id: undefined,
image_url: '',
answer_two_gap_match: JSON.stringify({
value: 50,
config: {
min: 0,
max: 100,
step: 1,
defaultValue: 50,
pxPerUnit: 10,
labelEvery: 10,
minorTickEvery: 5,
precision: 0,
},
}),
answer_view_format: 'scale',
answer_order: 0,
};
form.setValue(answersPath, [baseAnswer]);
}, [activeQuestionId, optionsFields.length, answersPath, form]);

if (optionsFields.length === 0) {
return null;
}
Expand Down
Loading
Loading