UNPKG

quiz-interactions

Version:

A React UI component Library for quiz interaction types.

503 lines (416 loc) 18.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.getPropsFromEditData = exports.getEditData = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _map = _interopRequireDefault(require("lodash/fp/map")); var _filter = _interopRequireDefault(require("lodash/fp/filter")); var _set = _interopRequireDefault(require("lodash/fp/set")); var _fromPairs = _interopRequireDefault(require("lodash/fp/fromPairs")); var _pullAt = _interopRequireDefault(require("lodash/fp/pullAt")); var _findIndex = _interopRequireDefault(require("lodash/fp/findIndex")); var _isEmpty = _interopRequireDefault(require("lodash/fp/isEmpty")); var _uniq = _interopRequireDefault(require("lodash/fp/uniq")); var _shuffle = _interopRequireDefault(require("lodash/fp/shuffle")); var _uuid = require("uuid"); var _FocusGroup = _interopRequireDefault(require("../../common/components/FocusGroup")); var _matching = _interopRequireDefault(require("../../../records/interactions/matching")); var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer")); var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer")); var _DistractorList = _interopRequireDefault(require("./DistractorList")); var _MatchListEdit = _interopRequireDefault(require("./MatchListEdit")); var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools")); var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/lib/format-message")); var _uiForms = require("@instructure/ui-forms"); var _uiFormField = require("@instructure/ui-form-field"); var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel")); var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert")); var _uiA11y = require("@instructure/ui-a11y"); var _class, _class2, _temp; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var QUESTION_SELECTOR = '[data-role=questionWrapper] input'; // return editData if supplied, otherwise reconstruct it from props var getEditData = function getEditData(_ref) { var scoringData = _ref.scoringData, interactionData = _ref.interactionData; var editData = scoringData.editData; if (!(0, _isEmpty.default)(editData)) { return { matches: editData.matches || [], distractors: editData.distractors || [] }; } var matches = (0, _map.default)(function (_ref2) { var id = _ref2.id, itemBody = _ref2.itemBody; return { questionId: id, questionBody: itemBody, answerBody: scoringData.value[id] }; }, interactionData.questions); var matchBodies = (0, _map.default)('answerBody', matches); var distractors = (0, _filter.default)(function (answerBody) { return !matchBodies.includes(answerBody); }, interactionData.answers); return { matches: matches, distractors: distractors }; }; // given editData, calculate the rest of the props exports.getEditData = getEditData; var getPropsFromEditData = function getPropsFromEditData(editData) { return { interactionData: { questions: (0, _map.default)(function (match) { return { id: match.questionId, itemBody: match.questionBody }; }, editData.matches), answers: (0, _shuffle.default)((0, _uniq.default)([].concat((0, _toConsumableArray2.default)((0, _map.default)('answerBody', editData.matches)), (0, _toConsumableArray2.default)(editData.distractors)))) }, scoringData: { value: (0, _fromPairs.default)((0, _map.default)(function (match) { return [match.questionId, match.answerBody]; }, editData.matches)), editData: editData } }; }; /** --- category: Matching --- Matching Edit component ```jsx_example function Example (props) { const exampleProps = { itemBody: 'Match the Secretary of State with the President they served under.', overrideEditableForRegrading: false, properties: { shuffleRules: { questions: { shuffled: true } } }, interactionData: { questions: [ { id: 'uuid1', itemBody: 'Condi Rice' }, { id: 'uuid2', itemBody: 'Alexander Haig' }, { id: 'uuid3', itemBody: 'John Kerry' } ], answers: [ 'Ronald Reagan', 'George W. Bush', 'Barack Obama', 'George H.W. Bush', 'Bill Clinton' ] }, scoringData: { value: { uuid1: 'George W. Bush', uuid2: 'Ronald Reagan', uuid3: 'Barack Obama' }, editData: {} } } return ( <MatchingEdit {...exampleProps} {...props} /> ) } <SettingsSwitcher locales={LOCALES}> <EditStateProvider> <Example /> </EditStateProvider> </SettingsSwitcher> ``` **/ exports.getPropsFromEditData = getPropsFromEditData; var MatchingEdit = (0, _withEditTools.default)(_class = (_temp = _class2 = /*#__PURE__*/ function (_Component) { (0, _inherits2.default)(MatchingEdit, _Component); function MatchingEdit() { var _getPrototypeOf2; var _this; (0, _classCallCheck2.default)(this, MatchingEdit); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = (0, _possibleConstructorReturn2.default)(this, (_getPrototypeOf2 = (0, _getPrototypeOf3.default)(MatchingEdit)).call.apply(_getPrototypeOf2, [this].concat(args))); _this._timeouts = []; _this.focusMatch = null; _this.focusDistractor = null; _this.stemElement = null; _this.questionErrors = function (index) { return _this.props.getErrors("scoringData.editData.matches[".concat(index, "].questionBody")).slice(0, 1); }; _this.answerErrors = function (index) { return _this.props.getErrors("scoringData.editData.matches[".concat(index, "].answerBody")).slice(0, 1); }; _this.distractorErrors = function (index) { return _this.props.getErrors("scoringData.editData.distractors[".concat(index, "]")).slice(0, 1); }; _this.editDistractor = function (index, text) { var editData = getEditData(_this.props); if (index >= 0 && index < editData.distractors.length) { _this.updateEditData((0, _set.default)("distractors[".concat(index, "]"), text, editData)); } }; _this.editQuestion = function (questionId, text) { var editData = getEditData(_this.props); var questionIndex = (0, _findIndex.default)({ questionId: questionId }, editData.matches); if (questionIndex !== -1) { _this.updateEditData((0, _set.default)("matches[".concat(questionIndex, "].questionBody"), text, editData)); } }; _this.editAnswer = function (questionId, text) { var editData = getEditData(_this.props); var questionIndex = (0, _findIndex.default)({ questionId: questionId }, editData.matches); if (questionIndex !== -1) { _this.updateEditData((0, _set.default)("matches[".concat(questionIndex, "].answerBody"), text, editData)); } }; _this.handleShuffleChange = function (event) { _this.props.changeItemState({ properties: (0, _set.default)('shuffleRules.questions.shuffled', event.target.checked, _this.props.properties) }); }; _this.handleRemoveMatch = function (questionId) { if (document.activeElement !== document.body) { // In FF clicking on a button doesn't focus it, so don't update focus in such case _this.updateFocusOnRemoveMatch(); } var editData = getEditData(_this.props); _this.updateEditData((0, _set.default)('matches', editData.matches.filter(function (match) { return match.questionId !== questionId; }), editData)); }; _this.handleCreateMatch = function () { _this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () { return _this.focusMatch.focusLast(QUESTION_SELECTOR); }, 100)]); var editData = getEditData(_this.props); _this.updateEditData((0, _set.default)('matches', [].concat((0, _toConsumableArray2.default)(editData.matches), [{ questionId: _this.props.newId(), questionBody: '', answerBody: '' }]), editData)); }; _this.handleRemoveDistractor = function (index) { if (_this.focusDistractor.previousExists('button')) { _this.focusDistractor.focusPrevious('button'); } else { _this.focusMatch.focusLast(); } var editData = getEditData(_this.props); _this.updateEditData((0, _set.default)('distractors', (0, _pullAt.default)([index], editData.distractors), editData)); }; _this.handleCreateDistractor = function () { _this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () { return _this.focusDistractor.focusLast('input'); }, 100)]); var editData = getEditData(_this.props); _this.updateEditData((0, _set.default)('distractors', [].concat((0, _toConsumableArray2.default)(editData.distractors), ['']), editData)); }; _this.handleCalculatorTypeChange = function (e, value) { _this.props.changeItemState({ calculatorType: value }); }; _this.handleDescriptionChange = function (itemBody) { _this.props.changeItemState({ itemBody: itemBody }); }; _this.handleFocusMatchRef = function (node) { _this.focusMatch = node; }; _this.handleFocusDistractorRef = function (node) { _this.focusDistractor = node; }; _this.handleStemRef = function (node) { _this.stemElement = node; }; return _this; } (0, _createClass2.default)(MatchingEdit, [{ key: "componentWillUnmount", value: function componentWillUnmount() { this._timeouts.forEach(clearTimeout); } }, { key: "updateEditData", // ============= // HELPERS // ============= value: function updateEditData(newEditData) { this.props.changeItemState(getPropsFromEditData(newEditData)); } }, { key: "updateFocusOnRemoveMatch", value: function updateFocusOnRemoveMatch() { var _this2 = this; if (!this.focusMatch.previousExists('button')) { // if removing the first choice, focus on stem this.stemElement.focus(); } else if (this.props.interactionData.questions.length === 2) { // if removing the second choice out of two choices this._timeouts = [].concat((0, _toConsumableArray2.default)(this._timeouts), [setTimeout(function () { return _this2.focusMatch.focusLast('input'); }, 100)]); } else { // all the other cases this.focusMatch.focusPrevious('button'); } } }, { key: "renderOptionsDescription", // ============= // RENDERING // ============= value: function renderOptionsDescription() { return _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Matching options')); } }, { key: "render", value: function render() { var _this3 = this; var _getEditData = getEditData(this.props), matches = _getEditData.matches, distractors = _getEditData.distractors; return _react.default.createElement("div", null, _react.default.createElement(_QuestionContainer.default, { disabled: this.props.overrideEditableForRegrading, enableRichContentEditor: this.props.enableRichContentEditor, itemBody: this.props.itemBody, onDescriptionChange: this.handleDescriptionChange, onModalClose: this.props.onModalClose, onModalOpen: this.props.onModalOpen, openImportModal: this.props.openImportModal, renderImportModal: this.props.renderImportModal, stemErrors: this.props.getErrors('itemBody'), textareaRef: this.handleStemRef }, _react.default.createElement("div", null, _react.default.createElement(_FocusGroup.default, { ref: this.handleFocusMatchRef }, _react.default.createElement(_MatchListEdit.default // Passed in as new function to force re-render , { answerErrors: function answerErrors(index) { return _this3.answerErrors(index); }, createNewMatch: this.handleCreateMatch, editAnswer: this.editAnswer, editQuestion: this.editQuestion // Passed in as new function to force re-render , questionErrors: function questionErrors(index) { return _this3.questionErrors(index); }, removeMatch: this.handleRemoveMatch, disabled: this.props.overrideEditableForRegrading, matches: matches })), _react.default.createElement(_FocusGroup.default, { ref: this.handleFocusDistractorRef }, _react.default.createElement(_DistractorList.default, { disabled: this.props.overrideEditableForRegrading, createNewDistractor: this.handleCreateDistractor, distractors: distractors, editDistractor: this.editDistractor // Passed in as new function to force re-render , distractorErrors: function distractorErrors(index) { return _this3.distractorErrors(index); }, removeDistractor: this.handleRemoveDistractor })))), _react.default.createElement(_QuestionSettingsContainer.default, { additionalOptions: this.props.additionalOptions }, _react.default.createElement(_QuestionSettingsPanel.default, { label: (0, _formatMessage.default)('Options'), defaultExpanded: true }, _react.default.createElement(_uiFormField.FormFieldGroup, { rowSpacing: "small", description: this.renderOptionsDescription() }, !this.props.hiddenOptions.calculator && _react.default.createElement(_CalculatorOptionWithOqaatAlert.default, { disabled: this.props.overrideEditableForRegrading, calculatorValue: this.props.calculatorType, onCalculatorTypeChange: this.handleCalculatorTypeChange, oqaatChecked: this.props.oneQuestionAtATime, onOqaatChange: this.props.setOneQuestionAtATime }), _react.default.createElement(_uiForms.Checkbox, { label: (0, _formatMessage.default)('Shuffle questions'), onChange: this.handleShuffleChange, checked: this.props.properties.shuffleRules.questions.shuffled, disabled: this.props.overrideEditableForRegrading }))))); } }]); MatchingEdit.displayName = "MatchingEdit"; return MatchingEdit; }(_react.Component), _class2.interactionType = _matching.default, _class2.propTypes = _objectSpread({ additionalOptions: _QuestionSettingsContainer.default.propTypes.additionalOptions, calculatorType: _propTypes.default.string, changeItemState: _propTypes.default.func, enableRichContentEditor: _propTypes.default.bool, hiddenOptions: _propTypes.default.shape({ calculator: _propTypes.default.bool }), interactionData: _propTypes.default.shape({ questions: _propTypes.default.arrayOf(_propTypes.default.shape({ id: _propTypes.default.string, itemBody: _propTypes.default.string })), answers: _propTypes.default.arrayOf(_propTypes.default.string) }).isRequired, itemBody: _propTypes.default.string, newId: _propTypes.default.func, onModalClose: _propTypes.default.func, onModalOpen: _propTypes.default.func, oneQuestionAtATime: _propTypes.default.bool, openImportModal: _propTypes.default.func, overrideEditableForRegrading: _propTypes.default.bool, properties: _propTypes.default.object, renderImportModal: _propTypes.default.func, scoringData: _propTypes.default.shape({ value: _propTypes.default.objectOf(_propTypes.default.string), editData: _propTypes.default.shape({ matches: _MatchListEdit.default.propTypes.matches, distractors: _DistractorList.default.propTypes.distractors }) }), setOneQuestionAtATime: _propTypes.default.func }, _withEditTools.default.injectedProps), _class2.defaultProps = { calculatorType: 'none', enableRichContentEditor: true, hiddenOptions: { calculator: true }, oneQuestionAtATime: false, overrideEditableForRegrading: false, newId: _uuid.v4, setOneQuestionAtATime: Function.prototype, additionalOptions: void 0, changeItemState: void 0, interactionData: void 0, itemBody: void 0, onModalClose: void 0, onModalOpen: void 0, openImportModal: void 0, properties: void 0, renderImportModal: void 0, scoringData: void 0 }, _temp)) || _class; exports.default = MatchingEdit;