quiz-interactions
Version:
A React UI component Library for quiz interaction types.
528 lines (448 loc) • 21.4 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 = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
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 _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _uuid = require("uuid");
var _striptags = _interopRequireDefault(require("striptags"));
var _assignIn2 = _interopRequireDefault(require("lodash/fp/assignIn"));
var _set = _interopRequireDefault(require("lodash/fp/set"));
var _last = _interopRequireDefault(require("lodash/fp/last"));
var _omit = _interopRequireDefault(require("lodash/omit"));
var _without = _interopRequireDefault(require("lodash/without"));
var _uiThemeable = require("@instructure/ui-themeable");
var _uiA11y = require("@instructure/ui-a11y");
var _uiLayout = require("@instructure/ui-layout");
var _uiForms = require("@instructure/ui-forms");
var _uiFormField = require("@instructure/ui-form-field");
var _uiElements = require("@instructure/ui-elements");
var _AnswerInput = _interopRequireDefault(require("../../common/edit/components/AnswerInput"));
var _Footer = _interopRequireDefault(require("../../common/edit/components/Footer"));
var _ordering = _interopRequireDefault(require("../../../records/interactions/ordering"));
var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer"));
var _RemoveChoiceButton = _interopRequireDefault(require("../../common/edit/components/RemoveChoiceButton"));
var _theme = _interopRequireDefault(require("./theme"));
var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools"));
var _Card = _interopRequireDefault(require("../../common/components/Card"));
var _ReorderChoiceButton = _interopRequireDefault(require("../common/ReorderChoiceButton"));
var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer"));
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 _dec, _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 styles = {
componentId: 'fEqyF',
template: function template(theme) {
return "\n\n.fEqyF_cloC{border-bottom:1px solid ".concat(theme.stemBorderColor || 'inherit', ";padding-bottom:").concat(theme.stemPadding || 'inherit', "}\n\n.fEqyF_eiZV,.fEqyF_YIqF{padding:").concat(theme.topLabelPadding || 'inherit', " 0}\n\n.fEqyF_cBkl{align-items:center;background-color:").concat(theme.choiceContainerInputColor || 'inherit', ";display:flex;flex:1;margin-right:").concat(theme.choiceContainerInputMargin || 'inherit', ";padding:").concat(theme.choiceContainerInputPadding || 'inherit', "}\n\n.fEqyF_fNII{text-align:center;width:").concat(theme.choiceContainerPositionWidth || 'inherit', "}\n\n[dir=ltr] .fEqyF_fNII,[dir=rtl] .fEqyF_fNII{text-align:center}\n\n.fEqyF_TnZk{flex:1}");
},
'stem': 'fEqyF_cloC',
'bottomLabel': 'fEqyF_eiZV',
'topLabel': 'fEqyF_YIqF',
'choiceContainerInput': 'fEqyF_cBkl',
'choiceContainerPosition': 'fEqyF_fNII',
'choiceContainerAnswerWrapper': 'fEqyF_TnZk'
};
var OrderingEdit = (
/**
---
category: Ordering
---
Ordering Edit component
```jsx_example
const WrappedExample = DragDropContext(HTML5Backend)(OrderingEdit)
function Example (props) {
const exampleProps = {
itemBody: 'Order these characters from tallest to shortest:',
interactionData: {
choices: {
uuid6: { id: 'uuid6', itemBody: 'Gandalf' },
uuid5: { id: 'uuid5', itemBody: 'Legolas' },
uuid4: { id: 'uuid4', itemBody: 'Aragorn' },
uuid3: { id: 'uuid3', itemBody: 'Gimli' },
uuid2: { id: 'uuid2', itemBody: 'Frodo' },
uuid1: { id: 'uuid1', itemBody: 'Gollum' },
}
},
properties: {
displayAnswersParagraph: true,
includeLabels: true,
topLabel: 'Taller',
bottomLabel: 'Shorter'
},
scoringData: {
value: ['uuid6','uuid5','uuid4','uuid3','uuid2','uuid1']
}
}
return (
<WrappedExample {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<EditStateProvider>
<Example />
</EditStateProvider>
</SettingsSwitcher>
```
**/
_dec = (0, _uiThemeable.themeable)(_theme.default, styles), (0, _withEditTools.default)(_class = _dec(_class = (_temp = _class2 =
/*#__PURE__*/
function (_Component) {
(0, _inherits2.default)(OrderingEdit, _Component);
function OrderingEdit() {
var _getPrototypeOf2;
var _this;
(0, _classCallCheck2.default)(this, OrderingEdit);
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)(OrderingEdit)).call.apply(_getPrototypeOf2, [this].concat(args)));
_this.stemElement = null;
_this.topLabelRef = null;
_this.bottomLabelRef = null;
_this.inputRefs = [];
_this.reorderRefs = [];
_this.removeChoiceRefs = [];
_this._timeouts = [];
_this._choiceWasCreated = false;
_this.handleStemRef = function (node) {
_this.stemElement = node;
};
_this.handleTopLabelRef = function (node) {
_this.topLabelRef = node;
};
_this.handleBottomLabelRef = function (node) {
_this.bottomLabelRef = node;
};
_this.handleCalculatorTypeChange = function (e, value) {
_this.props.changeItemState({
calculatorType: value
});
};
_this.handleMoveChoice = function (currI, targetI) {
// Create a new array to preserve immutability
var choices = _this.props.scoringData.value.slice(0); // Use destructuring to swap the two values
var _ref = [choices[currI], choices[targetI]];
choices[targetI] = _ref[0];
choices[currI] = _ref[1];
_this.props.changeItemState({
scoringData: _objectSpread({}, _this.props.scoringData, {
value: choices
})
});
_this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () {
return _this.reorderRefs[targetI].focus();
})]);
};
_this.handleRemoveChoice = function (choiceId, index) {
var choiceOrder = _this.props.scoringData.value;
if (index === 0) {
if (_this.props.properties.includeLabels) {
_this.topLabelRef.focus();
} else {
// added timeout to compensate for RCE sluggishness
_this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () {
return _this.stemElement.focus();
})]);
}
} else {
if (choiceOrder.length > 3) {
_this.removeChoiceRefs[index - 1].focus();
} else {
_this.reorderRefs[index - 1].focus();
}
}
_this.props.changeItemState({
interactionData: _objectSpread({}, _this.props.interactionData, {
choices: (0, _omit.default)(_this.props.interactionData.choices, choiceId)
}),
scoringData: _objectSpread({}, _this.props.scoringData, {
value: (0, _without.default)(choiceOrder, choiceId)
})
});
};
_this.handleCreateChoice = function () {
var id = (0, _uuid.v4)();
var newChoice = {
id: id,
itemBody: ''
};
_this._choiceWasCreated = true;
_this.props.changeItemState({
interactionData: _objectSpread({}, _this.props.interactionData, {
choices: (0, _assignIn2.default)(_this.props.interactionData.choices, (0, _defineProperty2.default)({}, id, newChoice))
}),
scoringData: _objectSpread({}, _this.props.scoringData, {
value: [].concat((0, _toConsumableArray2.default)(_this.props.scoringData.value), [id])
})
});
_this.props.notifyScreenreader((0, _formatMessage.default)('Navigate up to find new choice input'));
};
_this.handleIncludeLabelsChange = function (e) {
var properties = _this.props.properties;
if (!properties.includeLabels) {
_this.props.notifyScreenreader((0, _formatMessage.default)('Navigate up to find label fields'));
}
_this.props.changeItemState({
properties: _objectSpread({}, _this.props.properties, {
includeLabels: !properties['includeLabels']
})
});
};
_this.handleDisplayAnswersParagraphChange = function (e) {
var properties = _this.props.properties;
_this.props.changeItemState({
properties: _objectSpread({}, _this.props.properties, {
displayAnswersParagraph: !properties['displayAnswersParagraph']
})
});
};
_this.handleInputChange = function (choiceId, event, _ref2) {
var editorContent = _ref2.editorContent;
_this.props.changeItemState({
interactionData: (0, _set.default)("choices[".concat(choiceId, "].itemBody"), editorContent, _this.props.interactionData)
});
};
return _this;
}
(0, _createClass2.default)(OrderingEdit, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._timeouts.forEach(clearTimeout);
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
if (this._choiceWasCreated) {
this._choiceWasCreated = false;
(0, _last.default)(this.inputRefs).focus();
}
} // ===========
// HANDLERS
// ===========
}, {
key: "renderChoice",
// ===========
// RENDER
// ===========
value: function renderChoice(choice, isFinalChoice, index) {
var _this2 = this;
var choicesLength = this.props.scoringData.value.length;
var nonEditable = this.props.overrideEditableForRegrading;
return _react.default.createElement(_Card.default, {
id: choice.id,
index: index,
key: "".concat(choice.id, "_").concat(index) // eslint-disable-line react/jsx-no-literals
,
moveCard: this.handleMoveChoice,
margin: "0 0 0.5rem 0"
}, _react.default.createElement(_uiLayout.Grid, {
vAlign: "middle",
colSpacing: "none"
}, _react.default.createElement(_uiLayout.Grid.Row, null, _react.default.createElement(_uiLayout.Grid.Col, null, _react.default.createElement("div", {
className: styles.choiceContainerInput
}, _react.default.createElement("div", {
className: styles.choiceContainerPosition
}, _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('position {position, number}', {
position: index + 1
})), _react.default.createElement(_uiA11y.PresentationContent, null, _react.default.createElement(_uiElements.Text, {
color: "primary"
}, _formatMessage.default.number(index + 1)))), _react.default.createElement("div", {
className: styles.choiceContainerAnswerWrapper
}, _react.default.createElement(_AnswerInput.default, {
disabled: nonEditable,
errors: this.props.getErrors("interactionData.choices[".concat(choice.id, "].itemBody")),
id: choice.id,
itemBody: choice.itemBody,
noRCE: !this.props.enableRichContentEditor,
onChangeHandler: this.handleInputChange,
onModalClose: this.props.onModalClose,
onModalOpen: this.props.onModalOpen,
openImportModal: this.props.openImportModal,
ref: function ref(node) {
_this2.inputRefs[index] = node;
},
renderImportModal: this.props.renderImportModal
})))), _react.default.createElement(_uiLayout.Grid.Col, {
width: "auto"
}, _react.default.createElement(_ReorderChoiceButton.default, {
ref: function ref(node) {
_this2.reorderRefs[index] = node;
},
id: choice.id,
isFinalChoice: isFinalChoice,
isFirstChoice: index === 0,
onMoveChoiceUp: function onMoveChoiceUp() {
return _this2.handleMoveChoice(index, index - 1);
},
onMoveChoiceDown: function onMoveChoiceDown() {
return _this2.handleMoveChoice(index, index + 1);
},
screenReaderText: (0, _formatMessage.default)('Reorder Choice: {choice}', {
choice: (0, _striptags.default)(choice.itemBody)
})
})), _react.default.createElement(_uiLayout.Grid.Col, {
width: "auto"
}, choicesLength > 2 && !nonEditable && _react.default.createElement(_RemoveChoiceButton.default, {
padding: "0",
choiceId: choice.id,
ref: function ref(node) {
_this2.removeChoiceRefs[index] = node;
},
onRemoveChoice: function onRemoveChoice() {
return _this2.handleRemoveChoice(choice.id, index);
},
screenReaderText: (0, _formatMessage.default)('Remove Choice: {choice}', {
choice: (0, _striptags.default)(choice.itemBody)
})
})))));
}
}, {
key: "renderLabel",
value: function renderLabel(labelName, description) {
var _this3 = this;
var automation = 'sdk-ordering-edit-' + labelName;
return _react.default.createElement("div", {
className: styles[labelName]
}, _react.default.createElement(_uiForms.TextInput, {
ref: labelName === 'topLabel' ? this.handleTopLabelRef : this.handleBottomLabelRef,
label: _react.default.createElement(_uiA11y.ScreenReaderContent, null, description),
placeholder: description,
disabled: this.props.overrideEditableForRegrading,
name: labelName,
value: this.props.properties[labelName] || '',
onChange: function onChange(event) {
_this3.props.changeItemState({
properties: _objectSpread({}, _this3.props.properties, (0, _defineProperty2.default)({}, labelName, event.target.value))
});
},
width: "20rem",
messages: this.props.getErrors("properties.".concat(labelName)),
"data-automation": automation
}));
}
}, {
key: "renderOptionsDescription",
value: function renderOptionsDescription() {
return _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Ordering options'));
}
}, {
key: "render",
value: function render() {
var _this4 = this;
var _this$props$propertie = this.props.properties,
includeLabels = _this$props$propertie.includeLabels,
displayAnswersParagraph = _this$props$propertie.displayAnswersParagraph;
var choiceOrder = this.props.scoringData.value;
var choicesLength = choiceOrder.length;
var nonEditable = this.props.overrideEditableForRegrading; // clean up the references
this.inputRefs = [];
this.removeChoiceRefs = [];
this.reorderRefs = [];
return _react.default.createElement("div", null, _react.default.createElement(_QuestionContainer.default, {
disabled: nonEditable,
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
}, includeLabels && this.renderLabel('topLabel', (0, _formatMessage.default)('Top Label')), _react.default.createElement("div", null, choiceOrder.map(function (choiceId, i) {
var isFinalChoice = i + 1 === choicesLength;
var choice = _this4.props.interactionData.choices[choiceId];
return _this4.renderChoice(choice, isFinalChoice, i);
})), !nonEditable && _react.default.createElement(_Footer.default, {
onCreateChoice: this.handleCreateChoice
}), includeLabels && this.renderLabel('bottomLabel', (0, _formatMessage.default)('Bottom Label'))), _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: nonEditable,
calculatorValue: this.props.calculatorType,
onCalculatorTypeChange: this.handleCalculatorTypeChange,
oqaatChecked: this.props.oneQuestionAtATime,
onOqaatChange: this.props.setOneQuestionAtATime
}), _react.default.createElement(_uiForms.Checkbox, {
label: (0, _formatMessage.default)('Display Answers in a Paragraph'),
onChange: this.handleDisplayAnswersParagraphChange,
checked: displayAnswersParagraph || false,
disabled: nonEditable
}), _react.default.createElement(_uiForms.Checkbox, {
label: (0, _formatMessage.default)('Include Labels'),
onChange: this.handleIncludeLabelsChange,
checked: includeLabels || false,
disabled: nonEditable
})))));
}
}]);
OrderingEdit.displayName = "OrderingEdit";
return OrderingEdit;
}(_react.Component), _class2.interactionType = _ordering.default, _class2.propTypes = _objectSpread({
additionalOptions: _QuestionSettingsContainer.default.propTypes.additionalOptions,
calculatorType: _propTypes.default.string,
enableRichContentEditor: _propTypes.default.bool,
hiddenOptions: _propTypes.default.shape({
calculator: _propTypes.default.bool
}),
interactionData: _propTypes.default.shape({
choices: _propTypes.default.objectOf(_propTypes.default.shape({
id: _propTypes.default.string.isRequired,
itemBody: _propTypes.default.string.isRequired
})).isRequired
}).isRequired,
itemBody: _propTypes.default.string,
notifyScreenreader: _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.shape({
displayAnswersParagraph: _propTypes.default.bool,
includeLabels: _propTypes.default.bool,
topLabel: _propTypes.default.string,
bottomLabel: _propTypes.default.string
}).isRequired,
renderImportModal: _propTypes.default.func,
scoringData: _propTypes.default.shape({
value: _propTypes.default.arrayOf(_propTypes.default.string).isRequired
}).isRequired,
setOneQuestionAtATime: _propTypes.default.func
}, _withEditTools.default.injectedProps), _class2.defaultProps = {
additionalOptions: void 0,
calculatorType: 'none',
enableRichContentEditor: true,
hiddenOptions: {
calculator: true
},
itemBody: void 0,
onModalClose: void 0,
onModalOpen: void 0,
oneQuestionAtATime: false,
openImportModal: void 0,
overrideEditableForRegrading: false,
renderImportModal: void 0,
notifyScreenreader: Function.prototype,
setOneQuestionAtATime: Function.prototype
}, _temp)) || _class) || _class);
exports.default = OrderingEdit;