UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

346 lines (345 loc) • 15.8 kB
/** @jsx jsx */ function _array_like_to_array(arr, len) { if (len == null || len > arr.length) len = arr.length; for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; return arr2; } function _array_with_holes(arr) { if (Array.isArray(arr)) return arr; } function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _iterable_to_array_limit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally{ try { if (!_n && _i["return"] != null) _i["return"](); } finally{ if (_d) throw _e; } } return _arr; } function _non_iterable_rest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _sliced_to_array(arr, i) { return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); } function _unsupported_iterable_to_array(o, minLen) { if (!o) return; if (typeof o === "string") return _array_like_to_array(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); } import React, { useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import concat from 'lodash/fp/concat'; import find from 'lodash/fp/find'; import get from 'lodash/get'; import update from 'immutability-helper'; import { Checkbox } from '@instructure/ui-checkbox'; import { jsx } from '@instructure/emotion'; import QuestionSettingsContainer from '../../common/edit/components/QuestionSettingsContainer'; import QuestionContainer from '../../common/edit/components/QuestionContainer'; import RichFillBlankInteractionType from '../../../records/interactions/rich_fill_blank'; import BlankTypeSelect from '../../fill_blank/Edit/answer_field/BlankTypeSelect'; import WordBankDistractors from './WordBankDistractors'; import generateStyle from './styles'; import generateComponentTheme from './theme'; import withEditTools from '../../../util/withEditTools'; import t from '@instructure/quiz-i18n/format-message'; import QuestionSettingsPanel from '../../common/edit/components/QuestionSettingsPanel'; import CalculatorOptionWithOqaatAlert from '../../common/edit/components/CalculatorOptionWithOqaatAlert'; import { ScreenReaderContent } from '@instructure/ui-a11y-content'; import { Text } from '@instructure/ui-text'; import { Flex } from '@instructure/quiz-common/components/Flex/index'; import { withStyleOverrides } from '@instructure/quiz-common/util/withStyleOverrides'; import { FormFieldGroup } from '@instructure/quiz-common/components/FormFieldGroup/index'; import useBlankManagement from './useBlankManagement'; function BlanksOptions(param) { var blanks = param.blanks, scoringDataValue = param.scoringDataValue, fitbCommonWordbankEnabled = param.fitbCommonWordbankEnabled, overrideEditableForRegrading = param.overrideEditableForRegrading, wordBankChoices = param.wordBankChoices, notifyScreenreader = param.notifyScreenreader, onModalClose = param.onModalClose, onModalOpen = param.onModalOpen, validationErrorsFromApi = param.validationErrorsFromApi, getErrors = param.getErrors, updateBlank = param.updateBlank, updateScoringDataForBlank = param.updateScoringDataForBlank, isSurvey = param.isSurvey; var hasIndividualWordBanks = blanks.some(function(blank) { return blank.answerType === 'wordbank' && blank.choices && blank.choices.length; }); return blanks.map(function(blank, blankIndex) { var blankScoringData = find({ id: blank.id }, scoringDataValue) || {}; var choicesErrors = concat(getErrors("scoringData.value[".concat(blankIndex, "].scoringData.value.$errors")) || [], getErrors("interactionData.blanks[".concat(blankIndex, "].choices.$errors")) || []); return /*#__PURE__*/ jsx(BlankTypeSelect, { key: blank.id, answerType: blank.answerType, blankPosition: blankIndex + 1, blankScoringAlgorithm: blankScoringData.scoringAlgorithm, blankScoringData: blankScoringData.scoringData, choices: blank.choices, disabled: overrideEditableForRegrading, interactionDataErrors: getErrors("interactionData.blanks[".concat(blankIndex, "]")) || {}, name: blank.id, notifyScreenreader: notifyScreenreader, onModalClose: onModalClose, onModalOpen: onModalOpen, scoringDataErrors: getErrors("scoringData.value[".concat(blankIndex, "].scoringData")) || {}, choicesErrors: choicesErrors, validationErrorsFromApi: validationErrorsFromApi[blank.id] || [], wordBankChoices: wordBankChoices, updateBlank: updateBlank(blank.id), updateBlankScoringData: updateScoringDataForBlank(blank.id), useCommonWordbank: fitbCommonWordbankEnabled && !hasIndividualWordBanks, isSurvey: isSurvey }); }); } BlanksOptions.propTypes = { blanks: PropTypes.array.isRequired, scoringDataValue: PropTypes.array, fitbCommonWordbankEnabled: PropTypes.bool, overrideEditableForRegrading: PropTypes.bool, wordBankChoices: PropTypes.array, notifyScreenreader: PropTypes.func.isRequired, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, validationErrorsFromApi: PropTypes.object, getErrors: PropTypes.func.isRequired, updateBlank: PropTypes.func.isRequired, updateScoringDataForBlank: PropTypes.func.isRequired, isSurvey: PropTypes.bool }; var RichFillBlankEdit = /*#__PURE__*/ React.forwardRef(function(props, ref) { var _useState = _sliced_to_array(useState([]), 2), deletedBlanksPool = _useState[0], setDeletedBlanksPool = _useState[1]; var _useState1 = _sliced_to_array(useState([]), 2), deletedDistractors = _useState1[0], setDeletedDistractors = _useState1[1]; var validBlankRegex = useMemo(function() { return new RegExp(props.interactionType.validBlankRegex, 'g'); }, [ props.interactionType.validBlankRegex ]); // Other edit components use the `getErrors` helper passed in from the // withEditTools higher-order component, but fill blank has its own error // handling logic var getErrors = function(path) { if (props.errorsAreShowing) { return get(props.errors, path, null); } return null; }; var _useBlankManagement = useBlankManagement({ interactionData: props.interactionData, scoringData: props.scoringData, changeItemState: props.changeItemState, properties: props.properties, validBlankRegex: validBlankRegex, deletedBlanksPool: deletedBlanksPool, setDeletedBlanksPool: setDeletedBlanksPool, deletedDistractors: deletedDistractors, setDeletedDistractors: setDeletedDistractors, fitbCommonWordbankEnabled: props.fitbCommonWordbankEnabled }), handleDescriptionChange = _useBlankManagement.handleDescriptionChange, updateBlank = _useBlankManagement.updateBlank, updateScoringDataForBlank = _useBlankManagement.updateScoringDataForBlank; var indexForWordBankChoice = function(choiceId) { return props.interactionData.wordBankChoices.findIndex(function(wordBankChoice) { return wordBankChoice.id === choiceId; }); }; var makeNewProperties = function(interactionData) { // syncs shuffling properties data with new interaction data. // this runs each time we changeItemState so that we dont have // to manage syncing properties and interactionData on each individual // change to blank type or blank creation/deletion var blankRules = interactionData.blanks.reduce(function(memo, blank, blankIndex) { // eslint-disable-next-line no-param-reassign memo[blankIndex] = blank.answerType === 'openEntry' ? { children: null } : { children: { choices: { shuffled: true } } }; return memo; }, {}); var newShuffleRules = { shuffleRules: { blanks: { children: blankRules } } }; return Object.assign({}, props.properties, newShuffleRules); }; var changeItemState = function(modifications, blankId) { var newestIntData = Object.assign({}, props.interactionData, modifications.interactionData); var newProperties = makeNewProperties(newestIntData); var newMods = { properties: newProperties }; if (modifications.scoringData) { newMods.scoringData = modifications.scoringData; } var newModifications = Object.assign({}, modifications, newMods); props.changeItemState(newModifications, blankId); }; // ================ // HANDLERS // ================ var handleCheckboxChange = function(event) { var interactionData = update(props.interactionData, _define_property({}, event.target.value, { $set: !props.interactionData[event.target.value] })); changeItemState({ interactionData: interactionData }); }; var handleCalculatorTypeChange = function(e, value) { changeItemState({ calculatorType: value }); }; var stemErrors = getErrors('scoringData.workingItemBody') || []; var blankErrors = getErrors([ 'interactionData', 'blanks', '$errors' ]) || []; return /*#__PURE__*/ jsx("div", { ref: ref }, /*#__PURE__*/ jsx(Flex, { direction: "column", padding: "0 0 small 0" }, /*#__PURE__*/ jsx(Text, null, t('Type a statement and surround a word with backticks to indicate where a student will fill in the answer. (e.g., "Roses are `red`, violets are `blue`")'))), /*#__PURE__*/ jsx(QuestionContainer, { disabled: props.overrideEditableForRegrading, enableRichContentEditor: props.enableRichContentEditor, itemBody: props.scoringData.workingItemBody, onDescriptionChange: handleDescriptionChange, onModalClose: props.onModalClose, onModalOpen: props.onModalOpen, openImportModal: props.openImportModal, stemErrors: stemErrors.concat(blankErrors) || [] }, /*#__PURE__*/ jsx(BlanksOptions, { blanks: props.interactionData.blanks, scoringDataValue: props.scoringData.value, fitbCommonWordbankEnabled: props.fitbCommonWordbankEnabled, overrideEditableForRegrading: props.overrideEditableForRegrading, wordBankChoices: props.interactionData.wordBankChoices, notifyScreenreader: props.notifyScreenreader, onModalClose: props.onModalClose, onModalOpen: props.onModalOpen, validationErrorsFromApi: props.validationErrorsFromApi, getErrors: getErrors, updateBlank: updateBlank, updateScoringDataForBlank: updateScoringDataForBlank, isSurvey: props.isSurvey }), /*#__PURE__*/ jsx(WordBankDistractors, { changeItemState: props.changeItemState, indexForWordBankChoice: indexForWordBankChoice, interactionData: props.interactionData, interactionDataErrors: getErrors('interactionData.wordBankChoices') || {}, notifyScreenreader: props.notifyScreenreader, onModalClose: props.onModalClose, onModalOpen: props.onModalOpen, overrideEditableForRegrading: props.overrideEditableForRegrading, scoringData: props.scoringData, isSurvey: props.isSurvey })), /*#__PURE__*/ jsx(QuestionSettingsContainer, { additionalOptions: props.additionalOptions }, /*#__PURE__*/ jsx(QuestionSettingsPanel, { label: t('Options'), defaultExpanded: true }, /*#__PURE__*/ jsx(FormFieldGroup, { rowSpacing: "small", description: /*#__PURE__*/ jsx(ScreenReaderContent, null, t('Fill in the blank options')) }, props.interactionData.wordBankChoices && /*#__PURE__*/ jsx(Checkbox, { label: t('Allow word bank choices to be reused'), value: "reuseWordBankChoices", onChange: handleCheckboxChange, checked: props.interactionData.reuseWordBankChoices, disabled: props.overrideEditableForRegrading }), props.showCalculatorOption && /*#__PURE__*/ jsx(CalculatorOptionWithOqaatAlert, { disabled: props.overrideEditableForRegrading, calculatorValue: props.calculatorType, onCalculatorTypeChange: handleCalculatorTypeChange, oqaatChecked: props.oneQuestionAtATime, onOqaatChange: props.setOneQuestionAtATime }))))); }); RichFillBlankEdit.displayName = 'RichFillBlankEdit'; RichFillBlankEdit.interactionType = RichFillBlankInteractionType; RichFillBlankEdit.propTypes = { additionalOptions: PropTypes.array, calculatorType: PropTypes.string, changeItemState: PropTypes.func.isRequired, enableRichContentEditor: PropTypes.bool, errors: PropTypes.object, errorsAreShowing: PropTypes.bool, fitbCommonWordbankEnabled: PropTypes.bool, interactionData: PropTypes.object.isRequired, interactionType: PropTypes.shape({ validBlankRegex: PropTypes.string }).isRequired, notifyScreenreader: PropTypes.func.isRequired, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, oneQuestionAtATime: PropTypes.bool, openImportModal: PropTypes.func, overrideEditableForRegrading: PropTypes.bool, properties: PropTypes.shape({ shuffleRules: PropTypes.shape({ blanks: PropTypes.shape({ children: PropTypes.object }) }) }), scoringData: PropTypes.object.isRequired, setOneQuestionAtATime: PropTypes.func, validationErrorsFromApi: PropTypes.object, styles: PropTypes.object, showCalculatorOption: PropTypes.bool, isSurvey: PropTypes.bool }; RichFillBlankEdit.defaultProps = { calculatorType: 'none', enableRichContentEditor: true, fitbCommonWordbankEnabled: false, oneQuestionAtATime: false, overrideEditableForRegrading: false, setOneQuestionAtATime: Function.prototype, additionalOptions: void 0, errors: void 0, errorsAreShowing: void 0, onModalClose: void 0, onModalOpen: void 0, openImportModal: void 0, properties: {}, scoringData: void 0, validationErrorsFromApi: {}, showCalculatorOption: true }; var RichFillBlankEditWithTools = withEditTools(RichFillBlankEdit); var RichFillBlankEditWithStyles = withStyleOverrides(generateStyle, generateComponentTheme)(RichFillBlankEditWithTools); RichFillBlankEditWithStyles.componentId = "Quizzes".concat(RichFillBlankEdit.displayName); export default RichFillBlankEditWithStyles;