@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
380 lines (374 loc) • 15.2 kB
JavaScript
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
var _dec, _class, _FillBlankEdit;
function _callSuper(_this, derived, args) {
function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
return !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
} catch (e) {
return false;
}
}
derived = _getPrototypeOf(derived);
return _possibleConstructorReturn(_this, isNativeReflectConstruct() ? Reflect.construct(derived, args || [], _getPrototypeOf(_this).constructor) : derived.apply(_this, args));
}
/** @jsx jsx */
import { Component } 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 { Button } from '@instructure/ui-buttons';
import { Text } from '@instructure/ui-text';
import { jsx } from '@instructure/emotion';
import Errors from '../../common/edit/components/Errors';
import QuestionSettingsContainer from '../../common/edit/components/QuestionSettingsContainer';
import QuestionContainer from '../../common/edit/components/QuestionContainer';
import FillBlankInteractionType from '../../../records/interactions/fill_blank';
import BlankTypeSelect from './answer_field/BlankTypeSelect';
import EditStem from './EditStem';
import EditController from './editController';
import generateStyle from './styles';
import generateComponentTheme from './theme';
import withEditTools from '../../../util/withEditTools';
import t from '@instructure/quiz-i18n/es/format-message';
import QuestionSettingsPanel from '../../common/edit/components/QuestionSettingsPanel';
import CalculatorOptionWithOqaatAlert from '../../common/edit/components/CalculatorOptionWithOqaatAlert';
import { ScreenReaderContent } from '@instructure/ui-a11y-content';
import { withStyleOverrides, FormFieldGroup } from '@instructure/quiz-common';
var SPACE_KEY_CODE = 32;
var ENTER_KEY_CODE = 13;
/**
category: FillInTheBlank
Fill in the Blank Edit component
```jsx_example
function Example (props) {
const exampleProps = {
interactionData: {
prompt: '<p><strong>Please</strong> fill in all the blanks</p>',
blanks: [{
answerType: 'openEntry',
id: 'uuid-blank1'
}, {
answerType: 'openEntry',
id: 'uuid-blank2'
}, {
answerType: 'openEntry',
id: 'uuid-blank3'
}, {
answerType: 'openEntry',
id: 'uuid-blank4'
}, {
answerType: 'openEntry',
id: 'uuid-blank5'
}, {
answerType: 'dropdown',
id: 'uuid-blank6',
choices: [
{ id: 'uuid6-choice1', position: 1, itemBody: 'with' },
{ id: 'uuid6-choice2', position: 2, itemBody: 'on' },
{ id: 'uuid6-choice3', position: 3, itemBody: 'which' }
]
}, {
answerType: 'wordbank',
id: 'uuid-blank7',
choices: [
{ id: 'uuid7-choice1', position: 1, itemBody: 'bathwater' },
{ id: 'uuid7-choice2', position: 2, itemBody: 'bathtub' },
{ id: 'uuid7-choice3', position: 3, itemBody: 'water' }
]
}],
stemItems: [
{ id: 'uuid-stem0', position: 1, type: 'text', value: ' ' },
{ id: 'uuid-stem1', position: 2, type: 'blank', blankId: 'uuid-blank1' },
{ id: 'uuid-stem2', position: 3, type: 'text', value: ' ' },
{ id: 'uuid-stem3', position: 4, type: 'blank', blankId: 'uuid-blank2' },
{ id: 'uuid-stem4', position: 5, type: 'text', value: ' ' },
{ id: 'uuid-stem5', position: 6, type: 'blank', blankId: 'uuid-blank3' },
{ id: 'uuid-stem6', position: 7, type: 'text', value: ' ' },
{ id: 'uuid-stem7', position: 8, type: 'blank', blankId: 'uuid-blank4' },
{ id: 'uuid-stem8', position: 9, type: 'text', value: ' ' },
{ id: 'uuid-stem9', position: 10, type: 'blank', blankId: 'uuid-blank5' },
{ id: 'uuid-stem10', position: 11, type: 'text', value: ' ' },
{ id: 'uuid-stem11', position: 12, type: 'blank', blankId: 'uuid-blank6' },
{ id: 'uuid-stem12', position: 13, type: 'text', value: ' <the> ' },
{ id: 'uuid-stem13', position: 14, type: 'blank', blankId: 'uuid-blank7' },
{ id: 'uuid-stem14', position: 15, type: 'text', value: '.' }
]
},
scoringData: {
value: [{
id: 'uuid-blank1',
scoringAlgorithm: 'TextCloseEnough',
scoringData: {
blankText: 'Don\'t',
editDistance: '1',
ignoreCase: true,
value: 'Don\'t'
}
}, {
id: 'uuid-blank2',
scoringAlgorithm: 'TextEquivalence',
scoringData: {
blankText: 'throw',
value: 'throw'
}
}, {
id: 'uuid-blank3',
scoringAlgorithm: 'TextContainsAnswer',
scoringData: {
blankText: 'the',
value: 'the'
}
}, {
id: 'uuid-blank4',
scoringAlgorithm: 'TextInChoices',
scoringData: {
blankText: 'baby',
value: ['baby', 'kid', 'child']
}
}, {
id: 'uuid-blank5',
scoringAlgorithm: 'TextRegex',
scoringData: {
blankText: 'out',
value: 'out'
}
}, {
id: 'uuid-blank6',
scoringAlgorithm: 'Equivalence',
scoringData: {
blankText: 'with',
value: 'uuid6-choice1'
}
}, {
id: 'uuid-blank7',
scoringAlgorithm: 'Equivalence',
scoringData: {
blankText: 'bathwater',
value: 'uuid7-choice1'
}
}]
}
}
return (
<FillBlankEdit {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<EditStateProvider>
<Example />
</EditStateProvider>
</SettingsSwitcher>
```
**/
var FillBlankEdit = (_dec = withStyleOverrides(generateStyle, generateComponentTheme), withEditTools(_class = _dec(_class = (_FillBlankEdit = /*#__PURE__*/function (_Component) {
function FillBlankEdit(props) {
var _this2;
_classCallCheck(this, FillBlankEdit);
_this2 = _callSuper(this, FillBlankEdit, [props]);
_defineProperty(_this2, "state", {
selectedText: ''
});
// Other edit components use the `getErrors` helper passed in from the
// withEditTools higher-order component, but fill blank has its own error
// handling logic
_defineProperty(_this2, "getErrors", function (path) {
if (_this2.props.errorsAreShowing) {
return get(_this2.props.errors, path, null);
}
return null;
});
// ================
// HANDLERS
// ================
_defineProperty(_this2, "setSelectedText", function (text) {
_this2.setState({
selectedText: text
});
});
_defineProperty(_this2, "handleCreateBlank", function (event) {
_this2.controller.onCreateBlank(event);
_this2.setState({
selectedText: ''
});
});
_defineProperty(_this2, "handleCreateBlankHotkey", function (event) {
if (event.which === ENTER_KEY_CODE || event.which === SPACE_KEY_CODE && event.ctrlKey && event.altKey) {
event.preventDefault();
_this2.handleCreateBlank(event);
_this2.props.notifyScreenreader(t('Blank created. Navigate to edit blank below.'));
}
});
_defineProperty(_this2, "handleDescriptionChange", function (prompt) {
_this2.controller.updateInteractionData({
prompt: prompt
});
});
_defineProperty(_this2, "updateBlank", function (blankId) {
return function (blankMods) {
_this2.controller.updateBlank(blankId, blankMods);
};
});
_defineProperty(_this2, "updateBlankScoringData", function (blankId) {
return function (scoringDataModifications) {
_this2.controller.updateScoringDataForBlank(blankId, scoringDataModifications);
};
});
_this2.controller = new EditController(props);
return _this2;
}
_inherits(FillBlankEdit, _Component);
return _createClass(FillBlankEdit, [{
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
this.controller.UNSAFE_componentWillReceiveProps(nextProps);
}
}, {
key: "renderBlanksOptions",
value:
// =============
// RENDERING
// =============
function renderBlanksOptions() {
var _this3 = this;
return this.controller.sortedBlanks().map(function (blank, blankIndex) {
var blankScoringData = find({
id: blank.id
}, _this3.props.scoringData.value) || {};
var choicesErrors = concat(_this3.getErrors("scoringData.value[".concat(blankIndex, "].scoringData.value.$errors")) || [], _this3.getErrors("interactionData.blanks[".concat(blankIndex, "].choices.$errors")) || []);
return jsx(BlankTypeSelect, {
key: blank.id,
answerType: blank.answerType,
blankPosition: blankIndex + 1,
blankScoringAlgorithm: blankScoringData.scoringAlgorithm,
blankScoringData: blankScoringData.scoringData,
disabled: _this3.props.overrideEditableForRegrading,
choices: blank.choices,
fitbCommonWordbankEnabled: _this3.props.fitbCommonWordbankEnabled,
interactionDataErrors: _this3.getErrors("interactionData.blanks[".concat(blankIndex, "]")) || {},
name: blank.id,
notifyScreenreader: _this3.props.notifyScreenreader,
onModalClose: _this3.props.onModalClose,
onModalOpen: _this3.props.onModalOpen,
scoringDataErrors: _this3.getErrors("scoringData.value[".concat(blankIndex, "].scoringData")) || {},
choicesErrors: choicesErrors,
validationErrorsFromApi: _this3.props.validationErrorsFromApi[blank.id] || [],
updateBlank: _this3.updateBlank(blank.id),
updateBlankScoringData: _this3.updateBlankScoringData(blank.id),
useCommonWordbank: false
});
});
}
}, {
key: "renderCreateBlankButton",
value: function renderCreateBlankButton() {
if (this.state.selectedText.trim() && !this.props.overrideEditableForRegrading) {
return jsx("div", {
css: this.props.styles.createButton
}, jsx(Button, {
onClick: this.handleCreateBlank,
color: "primary-inverse"
}, jsx("span", {
css: this.props.styles.createBlankText
}, t('Create Blank Space')), jsx("span", {
css: this.props.styles.createBlankHotkeyText
}, t('(Enter)'))));
}
}
}, {
key: "renderOptionsDescription",
value: function renderOptionsDescription() {
return jsx(ScreenReaderContent, null, t('Fill in the blank options'));
}
}, {
key: "render",
value: function render() {
return jsx("div", null, jsx(QuestionContainer, {
disabled: this.props.overrideEditableForRegrading,
enableRichContentEditor: this.props.enableRichContentEditor,
itemBody: this.props.interactionData.prompt,
onDescriptionChange: this.handleDescriptionChange,
onModalClose: this.props.onModalClose,
onModalOpen: this.props.onModalOpen,
openImportModal: this.props.openImportModal,
placeholder: t('Add Question Stem (optional)...'),
stemErrors: []
}, jsx("div", {
css: this.props.styles.hint
}, jsx(Text, {
color: "primary"
}, t('Type a statement, select text, and press Enter to create a new blank.'))), jsx(EditStem, {
disabled: this.props.overrideEditableForRegrading,
errors: this.getErrors('itemBody') || [],
destroyBlank: this.controller.onDestroyBlank,
onCreateBlankHotkey: this.handleCreateBlankHotkey,
onStemChange: this.controller.onStemChange,
scoringData: this.props.scoringData,
setSelectedText: this.setSelectedText,
stemItems: this.controller.sortedStemItems()
}), jsx("div", null, this.renderCreateBlankButton(), jsx(Errors, {
errorList: this.getErrors(['interactionData', 'blanks', '$errors']) || []
}, this.renderBlanksOptions()))), jsx(QuestionSettingsContainer, {
additionalOptions: this.props.additionalOptions
}, this.props.showCalculatorOption && jsx(QuestionSettingsPanel, {
label: t('Options'),
defaultExpanded: true
}, jsx(FormFieldGroup, {
rowSpacing: "small",
description: this.renderOptionsDescription()
}, jsx(CalculatorOptionWithOqaatAlert, {
disabled: this.props.overrideEditableForRegrading,
calculatorValue: this.props.calculatorType,
onCalculatorTypeChange: this.controller.handleCalculatorTypeChange,
oqaatChecked: this.props.oneQuestionAtATime,
onOqaatChange: this.props.setOneQuestionAtATime
})))));
}
}]);
}(Component), _defineProperty(_FillBlankEdit, "displayName", 'FillBlankEdit'), _defineProperty(_FillBlankEdit, "componentId", "Quizzes".concat(_FillBlankEdit.displayName)), _defineProperty(_FillBlankEdit, "interactionType", FillBlankInteractionType), _defineProperty(_FillBlankEdit, "propTypes", {
additionalOptions: PropTypes.array,
calculatorType: PropTypes.string,
enableRichContentEditor: PropTypes.bool,
errors: PropTypes.object,
errorsAreShowing: PropTypes.bool,
fitbCommonWordbankEnabled: PropTypes.bool,
interactionData: PropTypes.object.isRequired,
notifyScreenreader: PropTypes.func.isRequired,
onModalClose: PropTypes.func,
onModalOpen: PropTypes.func,
oneQuestionAtATime: PropTypes.bool,
openImportModal: PropTypes.func,
overrideEditableForRegrading: PropTypes.bool,
scoringData: PropTypes.object.isRequired,
setOneQuestionAtATime: PropTypes.func,
validationErrorsFromApi: PropTypes.object,
styles: PropTypes.object,
showCalculatorOption: PropTypes.bool
}), _defineProperty(_FillBlankEdit, "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,
scoringData: void 0,
validationErrorsFromApi: {},
showCalculatorOption: true
}), _FillBlankEdit)) || _class) || _class);
export { FillBlankEdit as default };