quiz-interactions
Version:
A React UI component Library for quiz interaction types.
503 lines (416 loc) • 18.7 kB
JavaScript
"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;