quiz-interactions
Version:
A React UI component Library for quiz interaction types.
612 lines (524 loc) • 21.5 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.getNewValue = getNewValue;
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
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 _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _uuid = require("uuid");
var _uiFormField = require("@instructure/ui-form-field");
var _uiA11y = require("@instructure/ui-a11y");
var _uiLayout = require("@instructure/ui-layout");
var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools"));
var _Errors = _interopRequireDefault(require("../../common/edit/components/Errors"));
var _FocusGroup = _interopRequireDefault(require("../../common/components/FocusGroup"));
var _Footer = _interopRequireDefault(require("../../common/edit/components/Footer"));
var _numeric = _interopRequireDefault(require("../../../records/interactions/numeric"));
var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer"));
var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer"));
var _RemoveChoiceButton = _interopRequireDefault(require("../../common/edit/components/RemoveChoiceButton"));
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/lib/format-message"));
var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel"));
var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert"));
var _ExactResponse = _interopRequireDefault(require("./ExactResponse"));
var _MarginOfError = _interopRequireDefault(require("./MarginOfError"));
var _PreciseResponse = _interopRequireDefault(require("./PreciseResponse"));
var _RequirementHelpModal = _interopRequireDefault(require("./RequirementHelpModal"));
var _WithinARange = _interopRequireDefault(require("./WithinARange"));
var _constants = require("./constants");
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; }
function getNewValue(answer, type) {
var newValue = {
id: answer.id || '',
type: type
};
switch (type) {
case _constants.EXACT_RESPONSE:
Object.assign(newValue, {
value: answer.value || ''
});
break;
case _constants.MARGIN_OF_ERROR:
Object.assign(newValue, {
value: answer.value || '',
margin: answer.margin || '',
marginType: answer.marginType || _constants.PERCENT
});
break;
case _constants.WITHIN_A_RANGE:
Object.assign(newValue, {
start: answer.start || '',
end: answer.end || ''
});
break;
case _constants.PRECISE_RESPONSE:
Object.assign(newValue, {
value: answer.value || '',
precision: answer.precision || '',
precisionType: answer.precisionType || _constants.SIGNIFICANT_DIGITS
});
break;
}
return newValue;
}
/**
---
category: Numeric
---
Numeric Edit component
```jsx_example
class Example extends React.Component {
render () {
const exampleProps = {
itemId: '1',
itemBody: 'x is an integer and 9 < x^2 < 99. What\'s the max value of x, minus the minimum value of x',
scoringData: {
value: [{
type: 'exactResponse',
id: '1',
value: '1800.5'
}, {
type: 'withinARange',
id: '2',
start: '1800.5',
end: '1801.5'
}, {
type: 'preciseResponse',
id: '3',
value: '1800.5',
precision: '1',
precisionType: 'decimals'
}, {
type: 'marginOfError',
id: '4',
value: '1800.5',
margin: '0.4',
marginType: 'absolute'
}]
}
}
return (
<NumericEdit {...exampleProps} {...this.props} />
)
}
}
<SettingsSwitcher locales={LOCALES}>
<EditStateProvider>
<Example />
</EditStateProvider>
</SettingsSwitcher>
```
**/
var NumericEdit = (0, _withEditTools.default)(_class = (_temp = _class2 =
/*#__PURE__*/
function (_Component) {
(0, _inherits2.default)(NumericEdit, _Component);
function NumericEdit() {
var _getPrototypeOf2;
var _this;
(0, _classCallCheck2.default)(this, NumericEdit);
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)(NumericEdit)).call.apply(_getPrototypeOf2, [this].concat(args)));
_this.choicesFocusGroup = null;
_this.stemElement = null;
_this._timeouts = [];
_this.state = {
helpModalOpen: false
};
_this.handleTypeChange = function (event, _ref) {
var id = _ref.id,
type = _ref.type;
var answers = (0, _toConsumableArray2.default)(_this.answers);
var index = answers.findIndex(function (answer) {
return answer.id === id;
});
console.assert(index !== -1); // eslint-disable-line no-console
answers[index] = getNewValue(answers[index], type);
_this.props.changeItemState({
scoringData: _objectSpread({}, _this.props.scoringData, {
value: answers
})
});
};
_this.handleChange = function (event, answer) {
var answers = (0, _toConsumableArray2.default)(_this.answers);
var index = answers.findIndex(function (_ref2) {
var id = _ref2.id;
return id === answer.id;
});
console.assert(index !== -1); // eslint-disable-line no-console
var updatedAnswer = _objectSpread({}, answers[index], {}, answer);
answers[index] = updatedAnswer;
_this.props.changeItemState({
scoringData: _objectSpread({}, _this.props.scoringData, {
value: answers
})
});
};
_this.handleCalculatorTypeChange = function (e, value) {
_this.props.changeItemState({
calculatorType: value
});
};
_this.handleCreateAnswer = function () {
_this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () {
return _this.choicesFocusGroup.focusLast('select');
}, 100)]);
var id = _this.props.newId();
var newValue = getNewValue({
id: id
}, _constants.EXACT_RESPONSE);
_this.props.changeItemState({
scoringData: _objectSpread({}, _this.props.scoringData, {
value: [].concat((0, _toConsumableArray2.default)(_this.answers), [newValue])
})
});
};
_this.handleRemoveChoice = function (id) {
// In FF clicking on a button doesn't focus it
if (document.activeElement !== document.body) {
_this.updateFocusOnRemove();
}
var answers = _this.answers.filter(function (answer) {
return answer.id !== id;
});
_this.props.changeItemState({
scoringData: _objectSpread({}, _this.props.scoringData, {
value: answers
})
});
};
_this.openHelpModal = function () {
return _this.setState({
helpModalOpen: true
});
};
_this.closeHelpModal = function () {
return _this.setState({
helpModalOpen: false
});
};
_this.handleChoicesFocusGroupRef = function (node) {
_this.choicesFocusGroup = node;
};
_this.handleStemRef = function (node) {
_this.stemElement = node;
};
_this.renderAnswerWrapper = function (answer) {
var id = answer.id,
type = answer.type;
return _react.default.createElement(_uiLayout.Grid, {
startAt: "medium",
key: id,
colSpacing: "none"
}, _react.default.createElement(_uiLayout.Grid.Row, null, _react.default.createElement(_uiLayout.Grid.Col, {
rowSpacing: "none"
}, _react.default.createElement(_uiFormField.FormFieldGroup, {
vAlign: "top",
rowSpacing: "none",
name: id,
layout: "columns",
description: _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Possible answer'))
}, _this.renderAnswerContents(answer))), _this.renderRemoveChoiceButton(answer, type)));
};
return _this;
}
(0, _createClass2.default)(NumericEdit, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._timeouts.forEach(clearTimeout);
}
}, {
key: "stringifyAnswer",
value: function stringifyAnswer(_ref3) {
var _EXACT_RESPONSE$MARGI;
var type = _ref3.type,
value = _ref3.value,
start = _ref3.start,
end = _ref3.end,
margin = _ref3.margin,
marginType = _ref3.marginType,
precision = _ref3.precision,
precisionType = _ref3.precisionType;
var orBlank = function orBlank(val) {
return val || 'blank';
};
return (_EXACT_RESPONSE$MARGI = {}, (0, _defineProperty2.default)(_EXACT_RESPONSE$MARGI, _constants.EXACT_RESPONSE, (0, _formatMessage.default)('value of {value}', {
value: orBlank(value)
})), (0, _defineProperty2.default)(_EXACT_RESPONSE$MARGI, _constants.MARGIN_OF_ERROR, (0, _formatMessage.default)('value of {value} with a margin of error of {margin} {marginType}', {
value: orBlank(value),
margin: orBlank(margin),
marginType: marginType
})), (0, _defineProperty2.default)(_EXACT_RESPONSE$MARGI, _constants.WITHIN_A_RANGE, (0, _formatMessage.default)('range from {start} to {end}', {
start: orBlank(start),
end: orBlank(end)
})), (0, _defineProperty2.default)(_EXACT_RESPONSE$MARGI, _constants.PRECISE_RESPONSE, (0, _formatMessage.default)('value of {value} with a precision of {precision} {precisionType}', {
value: orBlank(value),
precision: orBlank(precision),
precisionType: precisionType
})), _EXACT_RESPONSE$MARGI)[type];
}
}, {
key: "updateFocusOnRemove",
value: function updateFocusOnRemove() {
var _this2 = this;
var selector = 'button';
var textContent = 'Remove Answer';
if (!this.choicesFocusGroup.previousExists(selector, textContent)) {
// if removing the first choice, focus on stem
// added timeout to compensate for RCE sluggishness
this._timeouts = [].concat((0, _toConsumableArray2.default)(this._timeouts), [setTimeout(function () {
return _this2.stemElement.focus();
}, 100)]);
} else if (this.answers.length === 2) {
// if removing the second choice out of two choices
this._timeouts = [].concat((0, _toConsumableArray2.default)(this._timeouts), [setTimeout(function () {
return _this2.choicesFocusGroup.focusLast();
}, 100)]);
} else {
// all the other cases
this.choicesFocusGroup.focusPrevious(selector, textContent);
}
} // ===========
// HANDLERS
// ===========
}, {
key: "renderAnswerField",
// ===========
// RENDERS
// ===========
value: function renderAnswerField(_ref4, index) {
var id = _ref4.id,
value = _ref4.value;
return _react.default.createElement(_ExactResponse.default, {
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
onChange: this.handleChange,
onChangeType: this.handleTypeChange,
onClickHelp: this.openHelpModal,
value: value
});
}
}, {
key: "renderPrecisionAnswer",
value: function renderPrecisionAnswer(_ref5, index) {
var id = _ref5.id,
precision = _ref5.precision,
precisionType = _ref5.precisionType,
value = _ref5.value;
return _react.default.createElement(_PreciseResponse.default, {
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
onChange: this.handleChange,
onChangeType: this.handleTypeChange,
onClickHelp: this.openHelpModal,
precision: precision,
precisionType: precisionType,
value: value
});
}
}, {
key: "renderMarginAnswer",
value: function renderMarginAnswer(_ref6, index) {
var id = _ref6.id,
margin = _ref6.margin,
marginType = _ref6.marginType,
value = _ref6.value;
return _react.default.createElement(_MarginOfError.default, {
id: id,
locale: this.locale,
margin: margin,
marginType: marginType,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
onChange: this.handleChange,
onChangeType: this.handleTypeChange,
onClickHelp: this.openHelpModal,
value: value
});
}
}, {
key: "renderRangeAnswer",
value: function renderRangeAnswer(_ref7, index) {
var id = _ref7.id,
start = _ref7.start,
end = _ref7.end;
return _react.default.createElement(_WithinARange.default, {
end: end,
id: id,
locale: this.locale,
messages: this.props.getErrors("scoringData.value[".concat(index, "]"), {}),
onChange: this.handleChange,
onChangeType: this.handleTypeChange,
onClickHelp: this.openHelpModal,
start: start
});
}
}, {
key: "renderAnswerContents",
value: function renderAnswerContents(answer) {
var index = this.answers.findIndex(function (_ref8) {
var id = _ref8.id;
return id === answer.id;
});
console.assert(index !== -1); // eslint-disable-line no-console
switch (answer.type) {
case _constants.WITHIN_A_RANGE:
return this.renderRangeAnswer(answer, index);
case _constants.MARGIN_OF_ERROR:
return this.renderMarginAnswer(answer, index);
case _constants.PRECISE_RESPONSE:
return this.renderPrecisionAnswer(answer, index);
default:
return this.renderAnswerField(answer, index);
}
}
}, {
key: "renderRemoveChoiceButton",
value: function renderRemoveChoiceButton(answer, name) {
var _this3 = this;
if (this.answers.length <= 1) {
return null;
}
return _react.default.createElement(_uiLayout.Grid.Col, {
width: 1
}, _react.default.createElement(_RemoveChoiceButton.default, {
onClick: function onClick() {
return _this3.handleRemoveChoice(answer.id);
},
choiceId: name,
screenReaderText: (0, _formatMessage.default)('Remove Answer: {answer}', {
answer: this.stringifyAnswer(answer)
}),
padding: "2rem 0 0 0"
}));
}
}, {
key: "renderOptionsDescription",
value: function renderOptionsDescription() {
return _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Numeric options'));
}
}, {
key: "render",
value: function render() {
var name = "edit_interaction_".concat(this.props.itemId);
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.props.onDescriptionChange,
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(_FocusGroup.default, {
ref: this.handleChoicesFocusGroupRef,
asComponent: _Errors.default,
asProps: {
errorList: this.props.getErrors('scoringData.errors')
}
}, _react.default.createElement(_uiFormField.FormFieldGroup, {
vAlign: "bottom",
rowSpacing: "medium",
name: name,
description: _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('A list of possible answers'))
}, this.answers.map(this.renderAnswerWrapper))), _react.default.createElement(_Footer.default, {
buttonText: (0, _formatMessage.default)('Possible Answer'),
onCreateChoice: this.handleCreateAnswer,
screenReaderText: (0, _formatMessage.default)('Add Possible Answer')
})), _react.default.createElement(_QuestionSettingsContainer.default, {
additionalOptions: this.props.additionalOptions
}, !this.props.hiddenOptions.calculator && _react.default.createElement(_QuestionSettingsPanel.default, {
label: (0, _formatMessage.default)('Options'),
defaultExpanded: true
}, _react.default.createElement(_uiFormField.FormFieldGroup, {
rowSpacing: "small",
description: this.renderOptionsDescription()
}, _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(_RequirementHelpModal.default, {
onDismiss: this.closeHelpModal,
open: this.state.helpModalOpen
}));
}
}, {
key: "locale",
get: function get() {
return this.props.locale || this.context.locale || 'en-US';
}
}, {
key: "answers",
get: function get() {
return this.props.scoringData.value;
}
}]);
NumericEdit.displayName = "NumericEdit";
return NumericEdit;
}(_react.Component), _class2.interactionType = _numeric.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
}),
itemBody: _propTypes.default.string.isRequired,
itemId: _propTypes.default.string,
locale: _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,
renderImportModal: _propTypes.default.func,
scoringData: _propTypes.default.shape({
value: _propTypes.default.arrayOf(_propTypes.default.shape({
id: _propTypes.default.string.isRequired,
type: _propTypes.default.string.isRequired
})).isRequired
}).isRequired,
setOneQuestionAtATime: _propTypes.default.func
}, _withEditTools.default.injectedProps), _class2.contextTypes = {
locale: _propTypes.default.string
}, _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,
itemId: void 0,
locale: void 0,
onModalClose: void 0,
onModalOpen: void 0,
openImportModal: void 0,
renderImportModal: void 0
}, _temp)) || _class;
exports.default = NumericEdit;